Skip to content

Commit

Permalink
Remove the default use of the legacy signer
Browse files Browse the repository at this point in the history
  • Loading branch information
sugmanue committed Feb 21, 2024
1 parent b0da323 commit 4bd4ae3
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,24 @@
package software.amazon.awssdk.services.polly.internal.presigner;

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

import java.net.URI;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.CredentialUtils;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
import software.amazon.awssdk.awscore.AwsExecutionAttribute;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
Expand All @@ -41,16 +43,25 @@
import software.amazon.awssdk.awscore.presigner.PresignRequest;
import software.amazon.awssdk.awscore.presigner.PresignedRequest;
import software.amazon.awssdk.core.ClientType;
import software.amazon.awssdk.core.SelectedAuthScheme;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
import software.amazon.awssdk.core.signer.Presigner;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner;
import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
import software.amazon.awssdk.identity.spi.Identity;
import software.amazon.awssdk.identity.spi.IdentityProvider;
import software.amazon.awssdk.identity.spi.IdentityProviders;
import software.amazon.awssdk.profiles.ProfileFile;
Expand All @@ -74,8 +85,7 @@
public final class DefaultPollyPresigner implements PollyPresigner {
private static final String SIGNING_NAME = "polly";
private static final String SERVICE_NAME = "polly";
private static final Aws4Signer DEFAULT_SIGNER = Aws4Signer.create();

private final Clock signingClock;
private final Supplier<ProfileFile> profileFile;
private final String profileName;
private final Region region;
Expand All @@ -85,6 +95,8 @@ public final class DefaultPollyPresigner implements PollyPresigner {
private final Boolean fipsEnabled;

private DefaultPollyPresigner(BuilderImpl builder) {
this.signingClock = builder.signingClock != null ? builder.signingClock
: Clock.systemUTC();
this.profileFile = ProfileFile::defaultProfileFile;
this.profileName = ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
this.region = builder.region != null ? builder.region
Expand Down Expand Up @@ -124,22 +136,28 @@ public void close() {
IoUtils.closeIfCloseable(credentialsProvider, null);
}

// For testing
static PollyPresigner.Builder builder(Clock signingClock) {
return new BuilderImpl()
.signingClock(signingClock);
}

public static PollyPresigner.Builder builder() {
return new BuilderImpl();
}

@Override
public PresignedSynthesizeSpeechRequest presignSynthesizeSpeech(
SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest) {
SynthesizeSpeechPresignRequest synthesizeSpeechPresignRequest) {
return presign(PresignedSynthesizeSpeechRequest.builder(),
synthesizeSpeechPresignRequest,
synthesizeSpeechPresignRequest.synthesizeSpeechRequest(),
SynthesizeSpeechRequestMarshaller.getInstance()::marshall)
.build();
.build();
}

private <T extends PollyRequest> SdkHttpFullRequest marshallRequest(
T request, Function<T, SdkHttpFullRequest.Builder> marshalFn) {
T request, Function<T, SdkHttpFullRequest.Builder> marshalFn) {
SdkHttpFullRequest.Builder requestBuilder = marshalFn.apply(request);
applyOverrideHeadersAndQueryParams(requestBuilder, request);
applyEndpoint(requestBuilder);
Expand All @@ -149,14 +167,22 @@ private <T extends PollyRequest> SdkHttpFullRequest marshallRequest(
/**
* Generate a {@link PresignedRequest} from a {@link PresignedRequest} and {@link PollyRequest}.
*/
private <T extends PresignedRequest.Builder, U extends PollyRequest> T presign(T presignedRequest,
PresignRequest presignRequest,
U requestToPresign,
Function<U, SdkHttpFullRequest.Builder> requestMarshaller) {
private <T extends PresignedRequest.Builder, U extends PollyRequest> T presign(
T presignedRequest,
PresignRequest presignRequest,
U requestToPresign,
Function<U, SdkHttpFullRequest.Builder> requestMarshaller
) {
ExecutionAttributes execAttrs = createExecutionAttributes(presignRequest, requestToPresign);

SdkHttpFullRequest marshalledRequest = marshallRequest(requestToPresign, requestMarshaller);
SdkHttpFullRequest signedHttpRequest = presignRequest(requestToPresign, marshalledRequest, execAttrs);
Presigner presigner = resolvePresigner(requestToPresign);
SdkHttpFullRequest signedHttpRequest = null;
if (presigner != null) {
signedHttpRequest = presignRequest(presigner, requestToPresign, marshalledRequest, execAttrs);
} else {
SelectedAuthScheme<AwsCredentialsIdentity> authScheme = selectedAuthScheme(requestToPresign, execAttrs);
signedHttpRequest = doSraPresign(marshalledRequest, authScheme, presignRequest.signatureDuration());
}
initializePresignedRequest(presignedRequest, execAttrs, signedHttpRequest);
return presignedRequest;
}
Expand All @@ -167,54 +193,111 @@ private void initializePresignedRequest(PresignedRequest.Builder presignedReques
List<String> signedHeadersQueryParam = signedHttpRequest.firstMatchingRawQueryParameters("X-Amz-SignedHeaders");

Map<String, List<String>> signedHeaders =
signedHeadersQueryParam.stream()
.flatMap(h -> Stream.of(h.split(";")))
.collect(toMap(h -> h, h -> signedHttpRequest.firstMatchingHeader(h)
.map(Collections::singletonList)
.orElseGet(ArrayList::new)));
signedHeadersQueryParam.stream()
.flatMap(h -> Stream.of(h.split(";")))
.collect(toMap(h -> h, h -> signedHttpRequest.firstMatchingHeader(h)
.map(Collections::singletonList)
.orElseGet(ArrayList::new)));

boolean isBrowserExecutable = signedHttpRequest.method() == SdkHttpMethod.GET &&
(signedHeaders.isEmpty() ||
(signedHeaders.size() == 1 && signedHeaders.containsKey("host")));
(signedHeaders.isEmpty() ||
(signedHeaders.size() == 1 && signedHeaders.containsKey("host")));

presignedRequest.expiration(execAttrs.getAttribute(PRESIGNER_EXPIRATION))
.isBrowserExecutable(isBrowserExecutable)
.httpRequest(signedHttpRequest)
.signedHeaders(signedHeaders);
presignedRequest.expiration(execAttrs.getAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION))
.isBrowserExecutable(isBrowserExecutable)
.httpRequest(signedHttpRequest)
.signedHeaders(signedHeaders);
}

private SdkHttpFullRequest presignRequest(PollyRequest requestToPresign,
private SdkHttpFullRequest presignRequest(Presigner presigner,
PollyRequest requestToPresign,
SdkHttpFullRequest marshalledRequest,
ExecutionAttributes executionAttributes) {
Presigner presigner = resolvePresigner(requestToPresign);
SdkHttpFullRequest presigned = presigner.presign(marshalledRequest, executionAttributes);
List<String> signedHeadersQueryParam = presigned.firstMatchingRawQueryParameters("X-Amz-SignedHeaders");
Validate.validState(!signedHeadersQueryParam.isEmpty(),
"Only SigV4 presigners are supported at this time, but the configured "
+ "presigner (%s) did not seem to generate a SigV4 signature.", presigner);
"Only SigV4 presigners are supported at this time, but the configured "
+ "presigner (%s) did not seem to generate a SigV4 signature.", presigner);
return presigned;
}

private <T extends Identity> SdkHttpFullRequest doSraPresign(SdkHttpFullRequest request,
SelectedAuthScheme<T> selectedAuthScheme,
Duration expirationDuration) {
CompletableFuture<? extends T> identityFuture = selectedAuthScheme.identity();
T identity = CompletableFutureUtils.joinLikeSync(identityFuture);

// presigned url puts auth info in query string, does not sign the payload, and has an expiry.
SignRequest.Builder<T> signRequestBuilder = SignRequest
.builder(identity)
.putProperty(AwsV4FamilyHttpSigner.AUTH_LOCATION, AwsV4FamilyHttpSigner.AuthLocation.QUERY_STRING)
.putProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION, expirationDuration)
.putProperty(HttpSigner.SIGNING_CLOCK, signingClock)
.request(request)
.payload(request.contentStreamProvider().orElse(null));
AuthSchemeOption authSchemeOption = selectedAuthScheme.authSchemeOption();
authSchemeOption.forEachSignerProperty(signRequestBuilder::putProperty);

HttpSigner<T> signer = selectedAuthScheme.signer();
SignedRequest signedRequest = signer.sign(signRequestBuilder.build());
return toSdkHttpFullRequest(signedRequest);
}

private SelectedAuthScheme<AwsCredentialsIdentity> selectedAuthScheme(PollyRequest requestToPresign,
ExecutionAttributes attributes) {
AuthScheme<AwsCredentialsIdentity> authScheme = AwsV4AuthScheme.create();
AwsCredentialsIdentity credentialsIdentity = resolveCredentials(resolveCredentialsProvider(requestToPresign));
AuthSchemeOption.Builder optionBuilder = AuthSchemeOption.builder()
.schemeId(authScheme.schemeId());
optionBuilder.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, SERVICE_NAME);
String region = attributes.getAttribute(AwsExecutionAttribute.AWS_REGION).id();
optionBuilder.putSignerProperty(AwsV4HttpSigner.REGION_NAME, region);
return new SelectedAuthScheme<>(CompletableFuture.completedFuture(credentialsIdentity), authScheme.signer(),
optionBuilder.build());
}

private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) {
SdkHttpRequest request = signedRequest.request();

return SdkHttpFullRequest.builder()
.contentStreamProvider(signedRequest.payload().orElse(null))
.protocol(request.protocol())
.method(request.method())
.host(request.host())
.port(request.port())
.encodedPath(request.encodedPath())
.applyMutation(r -> request.forEachHeader(r::putHeader))
.applyMutation(r -> request.forEachRawQueryParameter(r::putRawQueryParameter))
.build();
}

private ExecutionAttributes createExecutionAttributes(PresignRequest presignRequest, PollyRequest requestToPresign) {
Instant signatureExpiration = Instant.now().plus(presignRequest.signatureDuration());
// A fixed signingClock is used, so that the current time used by the signing logic, as well as to determine expiration
// are the same.
Instant signingInstant = signingClock.instant();
Clock signingClockOverride = Clock.fixed(signingInstant, ZoneOffset.UTC);
Duration expirationDuration = presignRequest.signatureDuration();
Instant signatureExpiration = signingInstant.plus(expirationDuration);

AwsCredentialsIdentity credentials = resolveCredentials(resolveCredentialsProvider(requestToPresign));
Validate.validState(credentials != null, "Credential providers must never return null.");

return new ExecutionAttributes()
.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CredentialUtils.toCredentials(credentials))
.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, SIGNING_NAME)
.putAttribute(AwsExecutionAttribute.AWS_REGION, region)
.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region)
.putAttribute(SdkInternalExecutionAttribute.IS_FULL_DUPLEX, false)
.putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC)
.putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME)
.putAttribute(PRESIGNER_EXPIRATION, signatureExpiration)
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, PollyAuthSchemeProvider.defaultProvider())
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes())
.putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS,
IdentityProviders.builder()
.putIdentityProvider(credentialsProvider())
.build());
.putAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS, CredentialUtils.toCredentials(credentials))
.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, SIGNING_NAME)
.putAttribute(AwsSignerExecutionAttribute.SIGNING_CLOCK, signingClockOverride)
.putAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION, signatureExpiration)
.putAttribute(AwsExecutionAttribute.AWS_REGION, region)
.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region)
.putAttribute(SdkInternalExecutionAttribute.IS_FULL_DUPLEX, false)
.putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC)
.putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME)
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, PollyAuthSchemeProvider.defaultProvider())
.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes())
.putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS,
IdentityProviders.builder()
.putIdentityProvider(credentialsProvider())
.build());
}

private Map<String, AuthScheme<?>> authSchemes() {
Expand All @@ -224,7 +307,7 @@ private Map<String, AuthScheme<?>> authSchemes() {

private IdentityProvider<? extends AwsCredentialsIdentity> resolveCredentialsProvider(PollyRequest request) {
return request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::credentialsIdentityProvider)
.orElse(credentialsProvider);
.orElse(credentialsProvider);
}

private AwsCredentialsIdentity resolveCredentials(IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider) {
Expand All @@ -233,10 +316,13 @@ private AwsCredentialsIdentity resolveCredentials(IdentityProvider<? extends Aws

private Presigner resolvePresigner(PollyRequest request) {
Signer signer = request.overrideConfiguration().flatMap(AwsRequestOverrideConfiguration::signer)
.orElse(DEFAULT_SIGNER);

.orElse(null);
if (signer == null) {
return null;
}
return Validate.isInstanceOf(Presigner.class, signer,
"Signer of type %s given in request override is not a Presigner", signer.getClass().getSimpleName());
"Signer of type %s given in request override is not a Presigner",
signer.getClass().getSimpleName());
}

private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpRequestBuilder, PollyRequest request) {
Expand All @@ -249,8 +335,8 @@ private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpR
private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) {
URI uri = resolveEndpoint();
httpRequestBuilder.protocol(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort());
.host(uri.getHost())
.port(uri.getPort());
}

private URI resolveEndpoint() {
Expand All @@ -259,21 +345,27 @@ private URI resolveEndpoint() {
}

return new DefaultServiceEndpointBuilder(SERVICE_NAME, "https")
.withRegion(region)
.withProfileFile(profileFile)
.withProfileName(profileName)
.withDualstackEnabled(dualstackEnabled)
.withFipsEnabled(fipsEnabled)
.getServiceEndpoint();
.withRegion(region)
.withProfileFile(profileFile)
.withProfileName(profileName)
.withDualstackEnabled(dualstackEnabled)
.withFipsEnabled(fipsEnabled)
.getServiceEndpoint();
}

public static class BuilderImpl implements PollyPresigner.Builder {
private Clock signingClock;
private Region region;
private IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider;
private URI endpointOverride;
private Boolean dualstackEnabled;
private Boolean fipsEnabled;

public Builder signingClock(Clock signingClock) {
this.signingClock = signingClock;
return this;
}

@Override
public Builder region(Region region) {
this.region = region;
Expand Down
Loading

0 comments on commit 4bd4ae3

Please sign in to comment.