1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.auth.credentials;
17
18 import java.util.Optional;
19 import java.util.function.Consumer;
20 import java.util.function.Supplier;
21 import software.amazon.awssdk.annotations.SdkPublicApi;
22 import software.amazon.awssdk.annotations.SdkTestInternalApi;
23 import software.amazon.awssdk.auth.credentials.internal.ProfileCredentialsUtils;
24 import software.amazon.awssdk.core.exception.SdkClientException;
25 import software.amazon.awssdk.profiles.ProfileFile;
26 import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
27 import software.amazon.awssdk.utils.IoUtils;
28 import software.amazon.awssdk.utils.SdkAutoCloseable;
29 import software.amazon.awssdk.utils.ToString;
30
31 /**
32  * Credentials provider based on AWS configuration profiles. This loads credentials from a {@link ProfileFile}, allowing you to
33  * share multiple sets of AWS security credentials between different tools like the AWS SDK for Java and the AWS CLI.
34  *
35  * <p>See http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html</p>
36  *
37  * <p>If this credentials provider is loading assume-role credentials from STS, it should be cleaned up with {@link #close()} if
38  * it is no longer being used.</p>
39  *
40  * @see ProfileFile
41  */

42 @SdkPublicApi
43 public final class ProfileCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {
44     private final AwsCredentialsProvider credentialsProvider;
45     private final RuntimeException loadException;
46
47     private final ProfileFile profileFile;
48     private final String profileName;
49
50     /**
51      * @see #builder()
52      */

53     private ProfileCredentialsProvider(BuilderImpl builder) {
54         AwsCredentialsProvider credentialsProvider = null;
55         RuntimeException loadException = null;
56         ProfileFile profileFile = null;
57         String profileName = null;
58
59         try {
60             profileName = builder.profileName != null ? builder.profileName
61                                                       : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
62
63             // Load the profiles file
64             profileFile = Optional.ofNullable(builder.profileFile)
65                                   .orElseGet(builder.defaultProfileFileLoader);
66
67             // Load the profile and credentials provider
68             String finalProfileName = profileName;
69             ProfileFile finalProfileFile = profileFile;
70             credentialsProvider =
71                     profileFile.profile(profileName)
72                                .flatMap(p -> new ProfileCredentialsUtils(p, finalProfileFile::profile).credentialsProvider())
73                                .orElseThrow(() -> {
74                                    String errorMessage = String.format("Profile file contained no credentials for " +
75                                                                        "profile '%s': %s", finalProfileName, finalProfileFile);
76                                    return SdkClientException.builder().message(errorMessage).build();
77                                });
78         } catch (RuntimeException e) {
79             // If we couldn't load the credentials provider for some reason, save an exception describing why. This exception
80             // will only be raised on calls to getCredentials. We don't want to raise an exception here because it may be
81             // expected (eg. in the default credential chain).
82             loadException = e;
83         }
84
85         if (loadException != null) {
86             this.loadException = loadException;
87             this.credentialsProvider = null;
88             this.profileFile = null;
89             this.profileName = null;
90         } else {
91             this.loadException = null;
92             this.credentialsProvider = credentialsProvider;
93             this.profileFile = profileFile;
94             this.profileName = profileName;
95         }
96     }
97
98     /**
99      * Create a {@link ProfileCredentialsProvider} using the {@link ProfileFile#defaultProfileFile()} and default profile name.
100      * Use {@link #builder()} for defining a custom {@link ProfileCredentialsProvider}.
101      */

102     public static ProfileCredentialsProvider create() {
103         return builder().build();
104     }
105
106     /**
107      * Create a {@link ProfileCredentialsProvider} using the given profile name and {@link ProfileFile#defaultProfileFile()}. Use
108      * {@link #builder()} for defining a custom {@link ProfileCredentialsProvider}.
109      *
110      * @param profileName the name of the profile to use from the {@link ProfileFile#defaultProfileFile()}
111      */

112     public static ProfileCredentialsProvider create(String profileName) {
113         return builder().profileName(profileName).build();
114     }
115
116     /**
117      * Get a builder for creating a custom {@link ProfileCredentialsProvider}.
118      */

119     public static Builder builder() {
120         return new BuilderImpl();
121     }
122
123     @Override
124     public AwsCredentials resolveCredentials() {
125         if (loadException != null) {
126             throw loadException;
127         }
128         return credentialsProvider.resolveCredentials();
129     }
130
131     @Override
132     public String toString() {
133         return ToString.builder("ProfileCredentialsProvider")
134                        .add("profileName", profileName)
135                        .add("profileFile", profileFile)
136                        .build();
137     }
138
139     @Override
140     public void close() {
141         // The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should
142         // clean it up when this credentials provider is closed.
143         IoUtils.closeIfCloseable(credentialsProvider, null);
144     }
145
146     /**
147      * A builder for creating a custom {@link ProfileCredentialsProvider}.
148      */

149     public interface Builder {
150
151         /**
152          * Define the profile file that should be used by this credentials provider. By default, the
153          * {@link ProfileFile#defaultProfileFile()} is used.
154          */

155         Builder profileFile(ProfileFile profileFile);
156
157         /**
158          * Similar to {@link #profileFile(ProfileFile)}, but takes a lambda to configure a new {@link ProfileFile.Builder}. This
159          * removes the need to called {@link ProfileFile#builder()} and {@link ProfileFile.Builder#build()}.
160          */

161         Builder profileFile(Consumer<ProfileFile.Builder> profileFile);
162
163         /**
164          * Define the name of the profile that should be used by this credentials provider. By default, the value in
165          * {@link ProfileFileSystemSetting#AWS_PROFILE} is used.
166          */

167         Builder profileName(String profileName);
168
169         /**
170          * Create a {@link ProfileCredentialsProvider} using the configuration applied to this builder.
171          */

172         ProfileCredentialsProvider build();
173     }
174
175     static final class BuilderImpl implements Builder {
176         private ProfileFile profileFile;
177         private String profileName;
178
179         private Supplier<ProfileFile> defaultProfileFileLoader = ProfileFile::defaultProfileFile;
180
181         BuilderImpl() {
182         }
183
184         @Override
185         public Builder profileFile(ProfileFile profileFile) {
186             this.profileFile = profileFile;
187             return this;
188         }
189
190         public void setProfileFile(ProfileFile profileFile) {
191             profileFile(profileFile);
192         }
193
194         @Override
195         public Builder profileFile(Consumer<ProfileFile.Builder> profileFile) {
196             return profileFile(ProfileFile.builder().applyMutation(profileFile).build());
197         }
198
199         @Override
200         public Builder profileName(String profileName) {
201             this.profileName = profileName;
202             return this;
203         }
204
205         public void setProfileName(String profileName) {
206             profileName(profileName);
207         }
208
209         @Override
210         public ProfileCredentialsProvider build() {
211             return new ProfileCredentialsProvider(this);
212         }
213
214         /**
215          * Override the default configuration file to be used when the customer does not explicitly set
216          * profileName(profileName);
217          * {@link #profileFile(ProfileFile)}. Use of this method is only useful for testing the default behavior.
218          */

219         @SdkTestInternalApi
220         Builder defaultProfileFileLoader(Supplier<ProfileFile> defaultProfileFileLoader) {
221             this.defaultProfileFileLoader = defaultProfileFileLoader;
222             return this;
223         }
224     }
225 }
226