1
15
16 package software.amazon.awssdk.auth.credentials;
17
18 import com.fasterxml.jackson.databind.JsonMappingException;
19 import com.fasterxml.jackson.databind.JsonNode;
20 import java.io.IOException;
21 import java.time.Duration;
22 import java.time.Instant;
23 import java.util.Optional;
24 import software.amazon.awssdk.annotations.SdkProtectedApi;
25 import software.amazon.awssdk.core.exception.SdkClientException;
26 import software.amazon.awssdk.core.util.json.JacksonUtils;
27 import software.amazon.awssdk.regions.util.HttpResourcesUtils;
28 import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
29 import software.amazon.awssdk.utils.ComparableUtils;
30 import software.amazon.awssdk.utils.DateUtils;
31 import software.amazon.awssdk.utils.SdkAutoCloseable;
32 import software.amazon.awssdk.utils.Validate;
33 import software.amazon.awssdk.utils.cache.CachedSupplier;
34 import software.amazon.awssdk.utils.cache.NonBlocking;
35 import software.amazon.awssdk.utils.cache.RefreshResult;
36
37
41 @SdkProtectedApi
42 public abstract class HttpCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {
43 private final Optional<CachedSupplier<AwsCredentials>> credentialsCache;
44
45 protected HttpCredentialsProvider(BuilderImpl<?, ?> builder) {
46 this(builder.asyncCredentialUpdateEnabled, builder.asyncThreadName);
47 }
48
49 HttpCredentialsProvider(boolean asyncCredentialUpdateEnabled, String asyncThreadName) {
50 if (isLocalCredentialLoadingDisabled()) {
51 this.credentialsCache = Optional.empty();
52 } else {
53 CachedSupplier.Builder<AwsCredentials> cacheBuilder = CachedSupplier.builder(this::refreshCredentials);
54 if (asyncCredentialUpdateEnabled) {
55 cacheBuilder.prefetchStrategy(new NonBlocking(asyncThreadName));
56 }
57 this.credentialsCache = Optional.of(cacheBuilder.build());
58 }
59 }
60
61 protected abstract ResourcesEndpointProvider getCredentialsEndpointProvider();
62
63
68 protected boolean isLocalCredentialLoadingDisabled() {
69 return false;
70 }
71
72 private RefreshResult<AwsCredentials> refreshCredentials() {
73 try {
74 String credentialsResponse = HttpResourcesUtils.instance().readResource(getCredentialsEndpointProvider());
75
76 JsonNode node = JacksonUtils.sensitiveJsonNodeOf(credentialsResponse);
77 JsonNode accessKey = node.get("AccessKeyId");
78 JsonNode secretKey = node.get("SecretAccessKey");
79 JsonNode token = node.get("Token");
80 JsonNode expirationNode = node.get("Expiration");
81
82 Validate.notNull(accessKey, "Failed to load access key.");
83 Validate.notNull(secretKey, "Failed to load secret key.");
84
85 AwsCredentials credentials =
86 token == null ? AwsBasicCredentials.create(accessKey.asText(), secretKey.asText())
87 : AwsSessionCredentials.create(accessKey.asText(), secretKey.asText(), token.asText());
88
89 Instant expiration = getExpiration(expirationNode).orElse(null);
90 if (expiration != null && Instant.now().isAfter(expiration)) {
91 throw SdkClientException.builder()
92 .message("Credentials obtained from metadata service are already expired.")
93 .build();
94 }
95 return RefreshResult.builder(credentials)
96 .staleTime(getStaleTime(expiration))
97 .prefetchTime(getPrefetchTime(expiration))
98 .build();
99 } catch (SdkClientException e) {
100 throw e;
101 } catch (JsonMappingException e) {
102 throw SdkClientException.builder()
103 .message("Unable to parse response returned from service endpoint.")
104 .cause(e)
105 .build();
106 } catch (RuntimeException | IOException e) {
107 throw SdkClientException.builder()
108 .message("Unable to load credentials from service endpoint.")
109 .cause(e)
110 .build();
111 }
112 }
113
114 private Optional<Instant> getExpiration(JsonNode expirationNode) {
115 return Optional.ofNullable(expirationNode).map(node -> {
116
117 String expirationValue = node.asText().replaceAll("\\+0000$", "Z");
118
119 try {
120 return DateUtils.parseIso8601Date(expirationValue);
121 } catch (RuntimeException e) {
122 throw new IllegalStateException("Unable to parse credentials expiration date from metadata service.", e);
123 }
124 });
125 }
126
127 private Instant getStaleTime(Instant expiration) {
128 return expiration == null ? null
129 : expiration.minus(Duration.ofMinutes(1));
130 }
131
132 private Instant getPrefetchTime(Instant expiration) {
133 Instant oneHourFromNow = Instant.now().plus(Duration.ofHours(1));
134 return expiration == null ? oneHourFromNow
135 : ComparableUtils.minimum(oneHourFromNow, expiration.minus(Duration.ofMinutes(15)));
136 }
137
138 @Override
139 public AwsCredentials resolveCredentials() {
140 if (isLocalCredentialLoadingDisabled()) {
141 throw SdkClientException.builder()
142 .message("Loading credentials from local endpoint is disabled. Unable to load " +
143 "credentials from service endpoint.")
144 .build();
145 }
146 return credentialsCache.map(CachedSupplier::get).orElseThrow(() ->
147 SdkClientException.builder().message("Unable to load credentials from service endpoint").build());
148 }
149
150 @Override
151 public void close() {
152 credentialsCache.ifPresent(CachedSupplier::close);
153 }
154
155 public interface Builder<TypeToBuildT extends HttpCredentialsProvider, BuilderT extends Builder> {
156
164 BuilderT asyncCredentialUpdateEnabled(Boolean asyncCredentialUpdateEnabled);
165
166 BuilderT asyncThreadName(String asyncThreadName);
167
168 TypeToBuildT build();
169 }
170
171
174 protected abstract static class BuilderImpl<TypeToBuildT extends HttpCredentialsProvider, BuilderT extends Builder>
175 implements Builder<TypeToBuildT, BuilderT> {
176 private boolean asyncCredentialUpdateEnabled = false;
177 private String asyncThreadName;
178
179 protected BuilderImpl() {
180 }
181
182 @Override
183 public BuilderT asyncCredentialUpdateEnabled(Boolean asyncCredentialUpdateEnabled) {
184 this.asyncCredentialUpdateEnabled = asyncCredentialUpdateEnabled;
185 return (BuilderT) this;
186 }
187
188 public void setAsyncCredentialUpdateEnabled(boolean asyncCredentialUpdateEnabled) {
189 asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled);
190 }
191
192 @Override
193 public BuilderT asyncThreadName(String asyncThreadName) {
194 this.asyncThreadName = asyncThreadName;
195 return (BuilderT) this;
196 }
197
198 public void setAsyncThreadName(String asyncThreadName) {
199 asyncThreadName(asyncThreadName);
200 }
201 }
202 }
203