Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the default use of the legacy signer #4956

Merged
merged 6 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@
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.annotations.SdkTestInternalApi;
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 +44,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 +86,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 +96,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 +137,29 @@ public void close() {
IoUtils.closeIfCloseable(credentialsProvider, null);
}

// Builder for testing that allows you to set the signing clock.
@SdkTestInternalApi
static PollyPresigner.Builder builder(Clock signingClock) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: instead of exposing setter method for testing, can we use dependency injection for testing, something like below

@SdkTestApi 
DefaultPollyPresigner(BuilderImpl builder, Clock clock) {
        this.signingClock = builder.signingClock != null ? builder.signingClock
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: instead of exposing setter method for testing, can we use dependency injection for testing, something like below

Sure, I can change it, but just to clarify, the setter is not ever exposed as is not part of the type interface returned by this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I tried your suggestion but it does not work or I'm misunderstanding something, I tried added a constructor as:

    @SdkTestInternalApi
    DefaultPollyPresigner(BuilderImpl builder, Clock signingClock) {
        ⋯
    }

but when I use it on my tests as:

        PollyPresigner presigner = new DefaultPollyPresigner(
            DefaultPollyPresigner.builder()
                                 .region(Region.US_EAST_1)
                                 .credentialsProvider(credentialsProvider),
            Clock.fixed(fixedTime, ZoneId.of("UTC")));

I get the error that the constructor expects BuilderImpl but got Builder. Any ideas?

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 +169,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 +195,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 +309,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 +318,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().getName());
}

private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpRequestBuilder, PollyRequest request) {
Expand All @@ -249,8 +337,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 +347,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
Loading