Skip to content

Commit 65f7554

Browse files
authored
Remove the default use of the legacy signer (#4956)
* Remove the default use of the legacy signer * Address PR comments * Fix a small typo
1 parent af1ccb8 commit 65f7554

File tree

2 files changed

+189
-57
lines changed

2 files changed

+189
-57
lines changed

services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java

Lines changed: 149 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,25 @@
1616
package software.amazon.awssdk.services.polly.internal.presigner;
1717

1818
import static java.util.stream.Collectors.toMap;
19-
import static software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION;
2019

2120
import java.net.URI;
21+
import java.time.Clock;
22+
import java.time.Duration;
2223
import java.time.Instant;
24+
import java.time.ZoneOffset;
2325
import java.util.ArrayList;
2426
import java.util.Collections;
2527
import java.util.List;
2628
import java.util.Map;
29+
import java.util.concurrent.CompletableFuture;
2730
import java.util.function.Function;
2831
import java.util.function.Supplier;
2932
import java.util.stream.Stream;
3033
import software.amazon.awssdk.annotations.SdkInternalApi;
34+
import software.amazon.awssdk.annotations.SdkTestInternalApi;
3135
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
3236
import software.amazon.awssdk.auth.credentials.CredentialUtils;
3337
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
34-
import software.amazon.awssdk.auth.signer.Aws4Signer;
3538
import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
3639
import software.amazon.awssdk.awscore.AwsExecutionAttribute;
3740
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
@@ -41,16 +44,25 @@
4144
import software.amazon.awssdk.awscore.presigner.PresignRequest;
4245
import software.amazon.awssdk.awscore.presigner.PresignedRequest;
4346
import software.amazon.awssdk.core.ClientType;
47+
import software.amazon.awssdk.core.SelectedAuthScheme;
4448
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
4549
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
4650
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
4751
import software.amazon.awssdk.core.signer.Presigner;
4852
import software.amazon.awssdk.core.signer.Signer;
4953
import software.amazon.awssdk.http.SdkHttpFullRequest;
5054
import software.amazon.awssdk.http.SdkHttpMethod;
55+
import software.amazon.awssdk.http.SdkHttpRequest;
5156
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
57+
import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner;
58+
import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;
5259
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
60+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
61+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
62+
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
63+
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
5364
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
65+
import software.amazon.awssdk.identity.spi.Identity;
5466
import software.amazon.awssdk.identity.spi.IdentityProvider;
5567
import software.amazon.awssdk.identity.spi.IdentityProviders;
5668
import software.amazon.awssdk.profiles.ProfileFile;
@@ -74,8 +86,7 @@
7486
public final class DefaultPollyPresigner implements PollyPresigner {
7587
private static final String SIGNING_NAME = "polly";
7688
private static final String SERVICE_NAME = "polly";
77-
private static final Aws4Signer DEFAULT_SIGNER = Aws4Signer.create();
78-
89+
private final Clock signingClock;
7990
private final Supplier<ProfileFile> profileFile;
8091
private final String profileName;
8192
private final Region region;
@@ -85,6 +96,8 @@ public final class DefaultPollyPresigner implements PollyPresigner {
8596
private final Boolean fipsEnabled;
8697

8798
private DefaultPollyPresigner(BuilderImpl builder) {
99+
this.signingClock = builder.signingClock != null ? builder.signingClock
100+
: Clock.systemUTC();
88101
this.profileFile = ProfileFile::defaultProfileFile;
89102
this.profileName = ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
90103
this.region = builder.region != null ? builder.region
@@ -124,22 +137,29 @@ public void close() {
124137
IoUtils.closeIfCloseable(credentialsProvider, null);
125138
}
126139

140+
// Builder for testing that allows you to set the signing clock.
141+
@SdkTestInternalApi
142+
static PollyPresigner.Builder builder(Clock signingClock) {
143+
return new BuilderImpl()
144+
.signingClock(signingClock);
145+
}
146+
127147
public static PollyPresigner.Builder builder() {
128148
return new BuilderImpl();
129149
}
130150

131151
@Override
132152
public PresignedSynthesizeSpeechRequest presignSynthesizeSpeech(
133-
SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest) {
153+
SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest) {
134154
return presign(PresignedSynthesizeSpeechRequest.builder(),
135155
synthesizeSpeechPresignRequest,
136156
synthesizeSpeechPresignRequest.synthesizeSpeechRequest(),
137157
SynthesizeSpeechRequestMarshaller.getInstance()::marshall)
138-
.build();
158+
.build();
139159
}
140160

141161
private <T extends PollyRequest> SdkHttpFullRequest marshallRequest(
142-
T request, Function<T, SdkHttpFullRequest.Builder> marshalFn) {
162+
T request, Function<T, SdkHttpFullRequest.Builder> marshalFn) {
143163
SdkHttpFullRequest.Builder requestBuilder = marshalFn.apply(request);
144164
applyOverrideHeadersAndQueryParams(requestBuilder, request);
145165
applyEndpoint(requestBuilder);
@@ -149,14 +169,22 @@ private <T extends PollyRequest> SdkHttpFullRequest marshallRequest(
149169
/**
150170
* Generate a {@link PresignedRequest} from a {@link PresignedRequest} and {@link PollyRequest}.
151171
*/
152-
private <T extends PresignedRequest.Builder, U extends PollyRequest> T presign(T presignedRequest,
153-
PresignRequest presignRequest,
154-
U requestToPresign,
155-
Function<U, SdkHttpFullRequest.Builder> requestMarshaller) {
172+
private <T extends PresignedRequest.Builder, U extends PollyRequest> T presign(
173+
T presignedRequest,
174+
PresignRequest presignRequest,
175+
U requestToPresign,
176+
Function<U, SdkHttpFullRequest.Builder> requestMarshaller
177+
) {
156178
ExecutionAttributes execAttrs = createExecutionAttributes(presignRequest, requestToPresign);
157-
158179
SdkHttpFullRequest marshalledRequest = marshallRequest(requestToPresign, requestMarshaller);
159-
SdkHttpFullRequest signedHttpRequest = presignRequest(requestToPresign, marshalledRequest, execAttrs);
180+
Presigner presigner = resolvePresigner(requestToPresign);
181+
SdkHttpFullRequest signedHttpRequest = null;
182+
if (presigner != null) {
183+
signedHttpRequest = presignRequest(presigner, requestToPresign, marshalledRequest, execAttrs);
184+
} else {
185+
SelectedAuthScheme<AwsCredentialsIdentity> authScheme = selectedAuthScheme(requestToPresign, execAttrs);
186+
signedHttpRequest = doSraPresign(marshalledRequest, authScheme, presignRequest.signatureDuration());
187+
}
160188
initializePresignedRequest(presignedRequest, execAttrs, signedHttpRequest);
161189
return presignedRequest;
162190
}
@@ -167,54 +195,111 @@ private void initializePresignedRequest(PresignedRequest.Builder presignedReques
167195
List<String> signedHeadersQueryParam = signedHttpRequest.firstMatchingRawQueryParameters("X-Amz-SignedHeaders");
168196

169197
Map<String, List<String>> signedHeaders =
170-
signedHeadersQueryParam.stream()
171-
.flatMap(h -> Stream.of(h.split(";")))
172-
.collect(toMap(h -> h, h -> signedHttpRequest.firstMatchingHeader(h)
173-
.map(Collections::singletonList)
174-
.orElseGet(ArrayList::new)));
198+
signedHeadersQueryParam.stream()
199+
.flatMap(h -> Stream.of(h.split(";")))
200+
.collect(toMap(h -> h, h -> signedHttpRequest.firstMatchingHeader(h)
201+
.map(Collections::singletonList)
202+
.orElseGet(ArrayList::new)));
175203

176204
boolean isBrowserExecutable = signedHttpRequest.method() == SdkHttpMethod.GET &&
177-
(signedHeaders.isEmpty() ||
178-
(signedHeaders.size() == 1 && signedHeaders.containsKey("host")));
205+
(signedHeaders.isEmpty() ||
206+
(signedHeaders.size() == 1 && signedHeaders.containsKey("host")));
179207

180-
presignedRequest.expiration(execAttrs.getAttribute(PRESIGNER_EXPIRATION))
181-
.isBrowserExecutable(isBrowserExecutable)
182-
.httpRequest(signedHttpRequest)
183-
.signedHeaders(signedHeaders);
208+
presignedRequest.expiration(execAttrs.getAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION))
209+
.isBrowserExecutable(isBrowserExecutable)
210+
.httpRequest(signedHttpRequest)
211+
.signedHeaders(signedHeaders);
184212
}
185213

186-
private SdkHttpFullRequest presignRequest(PollyRequest requestToPresign,
214+
private SdkHttpFullRequest presignRequest(Presigner presigner,
215+
PollyRequest requestToPresign,
187216
SdkHttpFullRequest marshalledRequest,
188217
ExecutionAttributes executionAttributes) {
189-
Presigner presigner = resolvePresigner(requestToPresign);
190218
SdkHttpFullRequest presigned = presigner.presign(marshalledRequest, executionAttributes);
191219
List<String> signedHeadersQueryParam = presigned.firstMatchingRawQueryParameters("X-Amz-SignedHeaders");
192220
Validate.validState(!signedHeadersQueryParam.isEmpty(),
193-
"Only SigV4 presigners are supported at this time, but the configured "
194-
+ "presigner (%s) did not seem to generate a SigV4 signature.", presigner);
221+
"Only SigV4 presigners are supported at this time, but the configured "
222+
+ "presigner (%s) did not seem to generate a SigV4 signature.", presigner);
195223
return presigned;
196224
}
197225

226+
private <T extends Identity> SdkHttpFullRequest doSraPresign(SdkHttpFullRequest request,
227+
SelectedAuthScheme<T> selectedAuthScheme,
228+
Duration expirationDuration) {
229+
CompletableFuture<? extends T> identityFuture = selectedAuthScheme.identity();
230+
T identity = CompletableFutureUtils.joinLikeSync(identityFuture);
231+
232+
// presigned url puts auth info in query string, does not sign the payload, and has an expiry.
233+
SignRequest.Builder<T> signRequestBuilder = SignRequest
234+
.builder(identity)
235+
.putProperty(AwsV4FamilyHttpSigner.AUTH_LOCATION, AwsV4FamilyHttpSigner.AuthLocation.QUERY_STRING)
236+
.putProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION, expirationDuration)
237+
.putProperty(HttpSigner.SIGNING_CLOCK, signingClock)
238+
.request(request)
239+
.payload(request.contentStreamProvider().orElse(null));
240+
AuthSchemeOption authSchemeOption = selectedAuthScheme.authSchemeOption();
241+
authSchemeOption.forEachSignerProperty(signRequestBuilder::putProperty);
242+
243+
HttpSigner<T> signer = selectedAuthScheme.signer();
244+
SignedRequest signedRequest = signer.sign(signRequestBuilder.build());
245+
return toSdkHttpFullRequest(signedRequest);
246+
}
247+
248+
private SelectedAuthScheme<AwsCredentialsIdentity> selectedAuthScheme(PollyRequest requestToPresign,
249+
ExecutionAttributes attributes) {
250+
AuthScheme<AwsCredentialsIdentity> authScheme = AwsV4AuthScheme.create();
251+
AwsCredentialsIdentity credentialsIdentity = resolveCredentials(resolveCredentialsProvider(requestToPresign));
252+
AuthSchemeOption.Builder optionBuilder = AuthSchemeOption.builder()
253+
.schemeId(authScheme.schemeId());
254+
optionBuilder.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, SERVICE_NAME);
255+
String region = attributes.getAttribute(AwsExecutionAttribute.AWS_REGION).id();
256+
optionBuilder.putSignerProperty(AwsV4HttpSigner.REGION_NAME, region);
257+
return new SelectedAuthScheme<>(CompletableFuture.completedFuture(credentialsIdentity), authScheme.signer(),
258+
optionBuilder.build());
259+
}
260+
261+
private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) {
262+
SdkHttpRequest request = signedRequest.request();
263+
264+
return SdkHttpFullRequest.builder()
265+
.contentStreamProvider(signedRequest.payload().orElse(null))
266+
.protocol(request.protocol())
267+
.method(request.method())
268+
.host(request.host())
269+
.port(request.port())
270+
.encodedPath(request.encodedPath())
271+
.applyMutation(r -> request.forEachHeader(r::putHeader))
272+
.applyMutation(r -> request.forEachRawQueryParameter(r::putRawQueryParameter))
273+
.build();
274+
}
275+
198276
private ExecutionAttributes createExecutionAttributes(PresignRequest presignRequest, PollyRequest requestToPresign) {
199-
Instant signatureExpiration = Instant.now().plus(presignRequest.signatureDuration());
277+
// A fixed signingClock is used, so that the current time used by the signing logic, as well as to determine expiration
278+
// are the same.
279+
Instant signingInstant = signingClock.instant();
280+
Clock signingClockOverride = Clock.fixed(signingInstant, ZoneOffset.UTC);
281+
Duration expirationDuration = presignRequest.signatureDuration();
282+
Instant signatureExpiration = signingInstant.plus(expirationDuration);
283+
200284
AwsCredentialsIdentity credentials = resolveCredentials(resolveCredentialsProvider(requestToPresign));
201285
Validate.validState(credentials != null, "Credential providers must never return null.");
202286

203287
return new ExecutionAttributes()
204-
.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CredentialUtils.toCredentials(credentials))
205-
.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, SIGNING_NAME)
206-
.putAttribute(AwsExecutionAttribute.AWS_REGION, region)
207-
.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region)
208-
.putAttribute(SdkInternalExecutionAttribute.IS_FULL_DUPLEX, false)
209-
.putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC)
210-
.putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME)
211-
.putAttribute(PRESIGNER_EXPIRATION, signatureExpiration)
212-
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, PollyAuthSchemeProvider.defaultProvider())
213-
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes())
214-
.putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS,
215-
IdentityProviders.builder()
216-
.putIdentityProvider(credentialsProvider())
217-
.build());
288+
.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CredentialUtils.toCredentials(credentials))
289+
.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, SIGNING_NAME)
290+
.putAttribute(AwsSignerExecutionAttribute.SIGNING_CLOCK, signingClockOverride)
291+
.putAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION, signatureExpiration)
292+
.putAttribute(AwsExecutionAttribute.AWS_REGION, region)
293+
.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region)
294+
.putAttribute(SdkInternalExecutionAttribute.IS_FULL_DUPLEX, false)
295+
.putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC)
296+
.putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME)
297+
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, PollyAuthSchemeProvider.defaultProvider())
298+
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes())
299+
.putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS,
300+
IdentityProviders.builder()
301+
.putIdentityProvider(credentialsProvider())
302+
.build());
218303
}
219304

220305
private Map<String, AuthScheme<?>> authSchemes() {
@@ -224,7 +309,7 @@ private Map<String, AuthScheme<?>> authSchemes() {
224309

225310
private IdentityProvider<? extends AwsCredentialsIdentity> resolveCredentialsProvider(PollyRequest request) {
226311
return request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider)
227-
.orElse(credentialsProvider);
312+
.orElse(credentialsProvider);
228313
}
229314

230315
private AwsCredentialsIdentity resolveCredentials(IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider) {
@@ -233,10 +318,13 @@ private AwsCredentialsIdentity resolveCredentials(IdentityProvider<? extends Aws
233318

234319
private Presigner resolvePresigner(PollyRequest request) {
235320
Signer signer = request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::signer)
236-
.orElse(DEFAULT_SIGNER);
237-
321+
.orElse(null);
322+
if (signer == null) {
323+
return null;
324+
}
238325
return Validate.isInstanceOf(Presigner.class, signer,
239-
"Signer of type %s given in request override is not a Presigner", signer.getClass().getSimpleName());
326+
"Signer of type %s given in request override is not a Presigner",
327+
signer.getClass().getName());
240328
}
241329

242330
private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpRequestBuilder, PollyRequest request) {
@@ -249,8 +337,8 @@ private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpR
249337
private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) {
250338
URI uri = resolveEndpoint();
251339
httpRequestBuilder.protocol(uri.getScheme())
252-
.host(uri.getHost())
253-
.port(uri.getPort());
340+
.host(uri.getHost())
341+
.port(uri.getPort());
254342
}
255343

256344
private URI resolveEndpoint() {
@@ -259,21 +347,27 @@ private URI resolveEndpoint() {
259347
}
260348

261349
return new DefaultServiceEndpointBuilder(SERVICE_NAME, "https")
262-
.withRegion(region)
263-
.withProfileFile(profileFile)
264-
.withProfileName(profileName)
265-
.withDualstackEnabled(dualstackEnabled)
266-
.withFipsEnabled(fipsEnabled)
267-
.getServiceEndpoint();
350+
.withRegion(region)
351+
.withProfileFile(profileFile)
352+
.withProfileName(profileName)
353+
.withDualstackEnabled(dualstackEnabled)
354+
.withFipsEnabled(fipsEnabled)
355+
.getServiceEndpoint();
268356
}
269357

270358
public static class BuilderImpl implements PollyPresigner.Builder {
359+
private Clock signingClock;
271360
private Region region;
272361
private IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider;
273362
private URI endpointOverride;
274363
private Boolean dualstackEnabled;
275364
private Boolean fipsEnabled;
276365

366+
public Builder signingClock(Clock signingClock) {
367+
this.signingClock = signingClock;
368+
return this;
369+
}
370+
277371
@Override
278372
public Builder region(Region region) {
279373
this.region = region;

0 commit comments

Comments
 (0)