-
Notifications
You must be signed in to change notification settings - Fork 792
/
ProfileCredentialsProvider.java
284 lines (241 loc) · 11.1 KB
/
ProfileCredentialsProvider.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.auth.credentials;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.auth.credentials.internal.ProfileCredentialsUtils;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.profiles.ProfileFileSupplier;
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.SdkAutoCloseable;
import software.amazon.awssdk.utils.ToString;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
/**
* Credentials provider based on AWS configuration profiles. This loads credentials from a {@link ProfileFile}, allowing you to
* share multiple sets of AWS security credentials between different tools like the AWS SDK for Java and the AWS CLI.
*
* <p>See http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html</p>
*
* <p>If this credentials provider is loading assume-role credentials from STS, it should be cleaned up with {@link #close()} if
* it is no longer being used.</p>
*
* @see ProfileFile
*/
@SdkPublicApi
public final class ProfileCredentialsProvider
implements AwsCredentialsProvider,
SdkAutoCloseable,
ToCopyableBuilder<ProfileCredentialsProvider.Builder, ProfileCredentialsProvider> {
private volatile AwsCredentialsProvider credentialsProvider;
private final RuntimeException loadException;
private final Supplier<ProfileFile> profileFile;
private volatile ProfileFile currentProfileFile;
private final String profileName;
private final Supplier<ProfileFile> defaultProfileFileLoader;
private final Object credentialsProviderLock = new Object();
/**
* @see #builder()
*/
private ProfileCredentialsProvider(BuilderImpl builder) {
this.defaultProfileFileLoader = builder.defaultProfileFileLoader;
RuntimeException thrownException = null;
String selectedProfileName = null;
Supplier<ProfileFile> selectedProfileSupplier = null;
try {
selectedProfileName = Optional.ofNullable(builder.profileName)
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
selectedProfileSupplier = Optional.ofNullable(builder.profileFile)
.orElseGet(() -> builder.defaultProfileFileLoader);
} catch (RuntimeException e) {
// If we couldn't load the credentials provider for some reason, save an exception describing why. This exception
// will only be raised on calls to resolveCredentials. We don't want to raise an exception here because it may be
// expected (eg. in the default credential chain).
thrownException = e;
}
this.loadException = thrownException;
this.profileName = selectedProfileName;
this.profileFile = selectedProfileSupplier;
}
/**
* Create a {@link ProfileCredentialsProvider} using the {@link ProfileFile#defaultProfileFile()} and default profile name.
* Use {@link #builder()} for defining a custom {@link ProfileCredentialsProvider}.
*/
public static ProfileCredentialsProvider create() {
return builder().build();
}
/**
* Create a {@link ProfileCredentialsProvider} using the given profile name and {@link ProfileFile#defaultProfileFile()}. Use
* {@link #builder()} for defining a custom {@link ProfileCredentialsProvider}.
*
* @param profileName the name of the profile to use from the {@link ProfileFile#defaultProfileFile()}
*/
public static ProfileCredentialsProvider create(String profileName) {
return builder().profileName(profileName).build();
}
/**
* Get a builder for creating a custom {@link ProfileCredentialsProvider}.
*/
public static Builder builder() {
return new BuilderImpl();
}
@Override
public AwsCredentials resolveCredentials() {
if (loadException != null) {
throw loadException;
}
ProfileFile cachedOrRefreshedProfileFile = refreshProfileFile();
if (shouldUpdateCredentialsProvider(cachedOrRefreshedProfileFile)) {
synchronized (credentialsProviderLock) {
if (shouldUpdateCredentialsProvider(cachedOrRefreshedProfileFile)) {
currentProfileFile = cachedOrRefreshedProfileFile;
handleProfileFileReload(cachedOrRefreshedProfileFile);
}
}
}
return credentialsProvider.resolveCredentials();
}
private void handleProfileFileReload(ProfileFile profileFile) {
credentialsProvider = createCredentialsProvider(profileFile, profileName);
}
private ProfileFile refreshProfileFile() {
return profileFile.get();
}
private boolean shouldUpdateCredentialsProvider(ProfileFile profileFile) {
return credentialsProvider == null || !Objects.equals(currentProfileFile, profileFile);
}
@Override
public String toString() {
return ToString.builder("ProfileCredentialsProvider")
.add("profileName", profileName)
.add("profileFile", currentProfileFile)
.build();
}
@Override
public void close() {
// The delegate credentials provider may be closeable (eg. if it's an STS credentials provider). In this case, we should
// clean it up when this credentials provider is closed.
IoUtils.closeIfCloseable(credentialsProvider, null);
}
@Override
public Builder toBuilder() {
return new BuilderImpl(this);
}
private AwsCredentialsProvider createCredentialsProvider(ProfileFile profileFile, String profileName) {
// Load the profile and credentials provider
return profileFile.profile(profileName)
.flatMap(p -> new ProfileCredentialsUtils(profileFile, p, profileFile::profile).credentialsProvider())
.orElseThrow(() -> {
String errorMessage = String.format("Profile file contained no credentials for " +
"profile '%s': %s", profileName, profileFile);
return SdkClientException.builder().message(errorMessage).build();
});
}
/**
* A builder for creating a custom {@link ProfileCredentialsProvider}.
*/
public interface Builder extends CopyableBuilder<Builder, ProfileCredentialsProvider> {
/**
* Define the profile file that should be used by this credentials provider. By default, the
* {@link ProfileFile#defaultProfileFile()} is used.
* @see #profileFile(Supplier)
*/
Builder profileFile(ProfileFile profileFile);
/**
* Similar to {@link #profileFile(ProfileFile)}, but takes a lambda to configure a new {@link ProfileFile.Builder}. This
* removes the need to called {@link ProfileFile#builder()} and {@link ProfileFile.Builder#build()}.
*/
Builder profileFile(Consumer<ProfileFile.Builder> profileFile);
/**
* Define the mechanism for loading profile files.
*
* @param profileFileSupplier Supplier interface for generating a ProfileFile instance.
* @see #profileFile(ProfileFile)
*/
Builder profileFile(Supplier<ProfileFile> profileFileSupplier);
/**
* Define the name of the profile that should be used by this credentials provider. By default, the value in
* {@link ProfileFileSystemSetting#AWS_PROFILE} is used.
*/
Builder profileName(String profileName);
/**
* Create a {@link ProfileCredentialsProvider} using the configuration applied to this builder.
*/
@Override
ProfileCredentialsProvider build();
}
static final class BuilderImpl implements Builder {
private Supplier<ProfileFile> profileFile;
private String profileName;
private Supplier<ProfileFile> defaultProfileFileLoader = ProfileFile::defaultProfileFile;
BuilderImpl() {
}
BuilderImpl(ProfileCredentialsProvider provider) {
this.profileName = provider.profileName;
this.defaultProfileFileLoader = provider.defaultProfileFileLoader;
this.profileFile = provider.profileFile;
}
@Override
public Builder profileFile(ProfileFile profileFile) {
return profileFile(Optional.ofNullable(profileFile)
.map(ProfileFileSupplier::fixedProfileFile)
.orElse(null));
}
public void setProfileFile(ProfileFile profileFile) {
profileFile(profileFile);
}
@Override
public Builder profileFile(Consumer<ProfileFile.Builder> profileFile) {
return profileFile(ProfileFile.builder().applyMutation(profileFile).build());
}
@Override
public Builder profileFile(Supplier<ProfileFile> profileFileSupplier) {
this.profileFile = profileFileSupplier;
return this;
}
public void setProfileFile(Supplier<ProfileFile> supplier) {
profileFile(supplier);
}
@Override
public Builder profileName(String profileName) {
this.profileName = profileName;
return this;
}
public void setProfileName(String profileName) {
profileName(profileName);
}
@Override
public ProfileCredentialsProvider build() {
return new ProfileCredentialsProvider(this);
}
/**
* Override the default configuration file to be used when the customer does not explicitly set
* profileFile(ProfileFile) or profileFileSupplier(supplier);
* {@link #profileFile(ProfileFile)}. Use of this method is
* only useful for testing the default behavior.
*/
@SdkTestInternalApi
Builder defaultProfileFileLoader(Supplier<ProfileFile> defaultProfileFileLoader) {
this.defaultProfileFileLoader = defaultProfileFileLoader;
return this;
}
}
}