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