diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java index f66ff4d6fd84..56ed58996ac8 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java @@ -74,13 +74,14 @@ public static Metadata constructMetadata(ServiceModel serviceModel, .withJsonVersion(serviceMetadata.getJsonVersion()) .withEndpointPrefix(serviceMetadata.getEndpointPrefix()) .withSigningName(serviceMetadata.getSigningName()) - .withAuthType(AuthType.fromValue(serviceMetadata.getSignatureVersion())) + .withAuthType(serviceMetadata.getSignatureVersion() != null ? + AuthType.fromValue(serviceMetadata.getSignatureVersion()) : null) .withUid(serviceMetadata.getUid()) .withServiceId(serviceMetadata.getServiceId()) .withSupportsH2(supportsH2(serviceMetadata)) .withJsonVersion(getJsonVersion(metadata, serviceMetadata)) .withAwsQueryCompatible(serviceMetadata.getAwsQueryCompatible()) - .withAuth(getAuthFromServiceMetadata(serviceMetadata, customizationConfig.useMultiAuth())); + .withAuth(getAuthFromServiceMetadata(serviceMetadata)); return metadata; } @@ -136,18 +137,14 @@ private static String getJsonVersion(Metadata metadata, ServiceMetadata serviceM } /** - * Converts service metadata into a list of AuthTypes. If useMultiAuth is enabled, then - * {@code metadata.auth} will be used in the conversion if present. Otherwise, use - * {@code metadata.signatureVersion}. + * Converts a list of authentication type strings from the given {@link ServiceMetadata} into a list of + * {@link AuthType} objects. */ - private static List getAuthFromServiceMetadata(ServiceMetadata serviceMetadata, - boolean useMultiAuth) { - if (useMultiAuth) { - List serviceAuth = serviceMetadata.getAuth(); - if (serviceAuth != null) { - return serviceAuth.stream().map(AuthType::fromValue).collect(Collectors.toList()); - } + private static List getAuthFromServiceMetadata(ServiceMetadata serviceMetadata) { + List serviceAuth = serviceMetadata.getAuth(); + if (serviceAuth != null) { + return serviceAuth.stream().map(AuthType::fromValue).collect(Collectors.toList()); } - return Collections.singletonList(AuthType.fromValue(serviceMetadata.getSignatureVersion())); + return Collections.emptyList(); } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java index 5fc680eccb48..1f247fe61b9f 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java @@ -47,14 +47,12 @@ final class AddOperations { private final NamingStrategy namingStrategy; private final Map paginators; private final List deprecatedShapes; - private final boolean useMultiAuth; AddOperations(IntermediateModelBuilder builder) { this.serviceModel = builder.getService(); this.namingStrategy = builder.getNamingStrategy(); this.paginators = builder.getPaginators().getPagination(); this.deprecatedShapes = builder.getCustomConfig().getDeprecatedShapes(); - this.useMultiAuth = builder.getCustomConfig().useMultiAuth(); } private static boolean isAuthenticated(Operation op) { @@ -182,6 +180,7 @@ public Map constructOperations() { operationModel.setStaticContextParams(op.getStaticContextParams()); operationModel.setOperationContextParams(op.getOperationContextParams()); operationModel.setAuth(getAuthFromOperation(op)); + operationModel.setUnsignedPayload(op.isUnsignedPayload()); Input input = op.getInput(); if (input != null) { @@ -237,21 +236,23 @@ public Map constructOperations() { } /** - * Returns the list of authTypes defined for an operation. If useMultiAuth is enabled, then - * {@code operation.auth} will be used in the conversion if present. Otherwise, use - * {@code operation.authtype} if present. + * Retrieves the list of {@link AuthType} for the given operation. + *

+ * If {@link Operation#getAuth()}is available, it is converted to a list of {@link AuthType}. + * Otherwise, {@link Operation#getAuthtype()} is returned as a single-element list if present. + * If neither is available, an empty list is returned. */ private List getAuthFromOperation(Operation op) { - if (useMultiAuth) { - List opAuth = op.getAuth(); - if (opAuth != null) { - return opAuth.stream().map(AuthType::fromValue).collect(Collectors.toList()); - } - } + + // First we check for legacy AuthType to support backward compatibility AuthType legacyAuthType = op.getAuthtype(); if (legacyAuthType != null) { return Collections.singletonList(legacyAuthType); } + List opAuth = op.getAuth(); + if (opAuth != null) { + return opAuth.stream().map(AuthType::fromValue).collect(Collectors.toList()); + } return Collections.emptyList(); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java index 502f07cb7c8e..32cecd79feb5 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java @@ -324,13 +324,6 @@ public class CustomizationConfig { */ private String rootPackageName; - /** - * Set to true to read from c2j multi-auth values. Currently defaults to false. - * - * TODO(multi-auth): full multi-auth support is not implemented - */ - private boolean useMultiAuth; - /** * Special case for a service where model changes for endpoint params were not updated . * This should be removed once the service updates its models @@ -891,14 +884,6 @@ public CustomizationConfig withRootPackageName(String packageName) { return this; } - public void setUseMultiAuth(boolean useMultiAuth) { - this.useMultiAuth = useMultiAuth; - } - - public boolean useMultiAuth() { - return useMultiAuth; - } - public Map getEndpointParameters() { return endpointParameters; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java index 76a46d697abf..a2a060c7a915 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java @@ -87,6 +87,8 @@ public class OperationModel extends DocumentationModel { @JsonIgnore private Map operationContextParams; + private boolean unsignedPayload; + public String getOperationName() { return operationName; } @@ -369,4 +371,12 @@ public Map getOperationContextParams() { public void setOperationContextParams(Map operationContextParams) { this.operationContextParams = operationContextParams; } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java index 452ee4b05648..0bf52867952a 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/AuthType.java @@ -24,6 +24,7 @@ public enum AuthType { CUSTOM("custom"), IAM("iam"), V4("v4"), + V4A("v4a"), V4_UNSIGNED_BODY("v4-unsigned-body"), S3("s3"), S3V4("s3v4"), @@ -49,6 +50,8 @@ public static AuthType fromValue(String value) { return NONE; case "aws.auth#sigv4": return V4; + case "aws.auth#sigv4a": + return V4A; default: String normalizedValue = StringUtils.lowerCase(value); return Arrays.stream(values()) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java index bf74350bd925..5f79a9af0ed6 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java @@ -61,6 +61,8 @@ public class Operation { private Map operationContextParams; + private boolean unsignedPayload; + public String getName() { return name; } @@ -227,4 +229,12 @@ public Map getOperationContextParams() { public void setOperationContextParams(Map operationContextParams) { this.operationContextParams = operationContextParams; } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java index 2063aa506771..0496323d84f2 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java @@ -25,7 +25,10 @@ import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeCodegenMetadata.SignerPropertyValueProvider; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; @@ -59,6 +62,24 @@ public final class AuthSchemeCodegenMetadataExt { .authSchemeClass(BearerAuthScheme.class) .build(); + static final AuthSchemeCodegenMetadata SIGV4A = + builder() + .schemeId(AwsV4aAuthScheme.SCHEME_ID) + .authSchemeClass(AwsV4aAuthScheme.class) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName( + "SERVICE_SIGNING_NAME") + .valueEmitter((spec, utils) -> spec.add("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName( + "REGION_SET") + .valueEmitter((spec, utils) -> spec.add("$L", "params.regionSet()")) + .build()) + .build(); + static final AuthSchemeCodegenMetadata NO_AUTH = builder() .schemeId(NoAuthAuthScheme.SCHEME_ID) .authSchemeClass(NoAuthAuthScheme.class) @@ -71,20 +92,32 @@ private AuthSchemeCodegenMetadataExt() { /** * Creates a new auth scheme codegen metadata instance using the defaults for the given {@link AuthType} defaults. */ - public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { - switch (type) { + public static AuthSchemeCodegenMetadata fromAuthType(AuthTrait authTrait) { + switch (authTrait.authType()) { case BEARER: return BEARER; case NONE: return NO_AUTH; + case V4A: + return getSigv4aAuthSchemeBuilder(authTrait).build(); default: - String authTypeName = type.value(); - SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); - if (defaults == null) { - throw new IllegalArgumentException("Unknown auth type: " + type); - } - return fromConstants(defaults); + return resolveAuthSchemeForType(authTrait); + } + } + + private static AuthSchemeCodegenMetadata resolveAuthSchemeForType(AuthTrait authTrait) { + String authTypeName = authTrait.authType().value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + + if (defaults == null) { + throw new IllegalArgumentException("Unknown auth option: " + authTrait + " with type " + authTypeName); } + if (authTrait.isUnsignedPayload()) { + defaults = defaults.toBuilder() + .payloadSigningEnabled(false) + .build(); + } + return fromConstants(defaults); } /** @@ -149,25 +182,54 @@ public static CodeBlock codegenSignerPropertiesIfAbsent( private static List propertiesFromConstants(SigV4SignerDefaults constants) { List properties = new ArrayList<>(); if (constants.payloadSigningEnabled() != null) { - properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); + properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled, AwsV4HttpSigner.class)); } if (constants.doubleUrlEncode() != null) { - properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); + properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode, AwsV4HttpSigner.class)); } if (constants.normalizePath() != null) { - properties.add(from("NORMALIZE_PATH", constants::normalizePath)); + properties.add(from("NORMALIZE_PATH", constants::normalizePath, AwsV4HttpSigner.class)); } if (constants.chunkEncodingEnabled() != null) { - properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); + properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled, AwsV4HttpSigner.class)); } return properties; } - private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { + private static SignerPropertyValueProvider from(String name, + Supplier valueSupplier, + Class containingClass) { return SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) + .containingClass(containingClass) .fieldName(name) .constantValueSupplier(valueSupplier) .build(); } + + private static Builder getSigv4aAuthSchemeBuilder(AuthTrait authTrait) { + Builder sigv4aBuilder = builder() + .schemeId(AwsV4aAuthScheme.SCHEME_ID) + .authSchemeClass(AwsV4aAuthScheme.class); + + addCommonSigv4aProperties(sigv4aBuilder); + + if (authTrait.isUnsignedPayload()) { + sigv4aBuilder.addProperty(from("PAYLOAD_SIGNING_ENABLED", () -> false, AwsV4aHttpSigner.class)); + } + return sigv4aBuilder; + } + + private static void addCommonSigv4aProperties(Builder builder) { + builder.addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("SERVICE_SIGNING_NAME") + .valueEmitter((spec, utils) -> spec.add("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("REGION_SET") + .valueEmitter((spec, utils) -> spec.add("$L", "params.regionSet()")) + .build()); + } + } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java index 6ab69cb24a92..7686ef132271 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java @@ -50,6 +50,7 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; 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; @@ -62,6 +63,7 @@ import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.SdkMetric; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -148,20 +150,17 @@ private MethodSpec generateAuthSchemeParams() { if (!authSchemeSpecUtils.useEndpointBasedAuthProvider()) { builder.addStatement("$T operation = executionAttributes.getAttribute($T.OPERATION_NAME)", String.class, SdkExecutionAttribute.class); + builder.addStatement("$T.Builder builder = $T.builder().operation(operation)", + authSchemeSpecUtils.parametersInterfaceName(), + authSchemeSpecUtils.parametersInterfaceName()); + if (authSchemeSpecUtils.usesSigV4()) { builder.addStatement("$T region = executionAttributes.getAttribute($T.AWS_REGION)", Region.class, AwsExecutionAttribute.class); - builder.addStatement("return $T.builder()" - + ".operation(operation)" - + ".region(region)" - + ".build()", - authSchemeSpecUtils.parametersInterfaceName()); - } else { - builder.addStatement("return $T.builder()" - + ".operation(operation)" - + ".build()", - authSchemeSpecUtils.parametersInterfaceName()); + builder.addStatement("builder.region(region)"); } + generateSigv4aSigningRegionSet(builder); + builder.addStatement("return builder.build()"); return builder.build(); } @@ -187,6 +186,7 @@ private MethodSpec generateAuthSchemeParams() { AwsExecutionAttribute.class); builder.addStatement("builder.region(region)"); } + generateSigv4aSigningRegionSet(builder); ClassName paramsBuilderClass = authSchemeSpecUtils.parametersEndpointAwareDefaultImplName().nestedClass("Builder"); builder.beginControlFlow("if (builder instanceof $T)", paramsBuilderClass); @@ -449,4 +449,17 @@ private TypeName toTypeName(Object valueType) { } return result; } + + private void generateSigv4aSigningRegionSet(MethodSpec.Builder builder) { + if (authSchemeSpecUtils.hasSigV4aSupport()) { + builder.addStatement( + "executionAttributes.getOptionalAttribute($T.AWS_SIGV4A_SIGNING_REGION_SET)\n" + + " .filter(regionSet -> !$T.isNullOrEmpty(regionSet))\n" + + " .ifPresent(nonEmptyRegionSet -> builder.regionSet($T.create(nonEmptyRegionSet)))", + AwsExecutionAttribute.class, + CollectionUtils.class, + RegionSet.class + ); + } + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java index 7b61a4eb70f0..848182945524 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java @@ -31,6 +31,8 @@ import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -117,7 +119,16 @@ private void addAccessorMethods(TypeSpec.Builder b) { .addJavadoc("Returns the region. The region parameter may be used with the $S auth scheme.", AwsV4AuthScheme.SCHEME_ID) .build()); + } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(RegionSet.class) + .addJavadoc("Returns the RegionSet. The regionSet parameter may be used with the $S auth " + + "scheme.", + AwsV4aAuthScheme.SCHEME_ID) + .build()); } if (authSchemeSpecUtils.generateEndpointBasedParams()) { @@ -162,6 +173,17 @@ private void addBuilderSetterMethods(TypeSpec.Builder b) { } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterSpec.builder(RegionSet.class, "regionSet").build()) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addJavadoc("Set the RegionSet. The regionSet parameter may be used with the $S auth scheme.", + AwsV4aAuthScheme.SCHEME_ID) + .build()); + + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java index 5724e2b78f57..a02f3e8bc893 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java @@ -109,6 +109,10 @@ public boolean usesSigV4() { return AuthUtils.usesAwsAuth(intermediateModel); } + public boolean usesSigV4a() { + return AuthUtils.usesSigv4aAuth(intermediateModel); + } + public boolean useEndpointBasedAuthProvider() { // Endpoint based auth provider is gated using the same setting that enables the use of auth scheme params. One does // not make sense without the other so there's no much point on creating another setting if both have to be at the same @@ -151,6 +155,10 @@ public String signingName() { return intermediateModel.getMetadata().getSigningName(); } + public boolean hasSigV4aSupport() { + return usesSigV4a() || generateEndpointBasedParams(); + } + private static Set setOf(String val1, String val2) { Set result = new HashSet<>(); result.add(val1); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java new file mode 100644 index 000000000000..dee3d4f35f55 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java @@ -0,0 +1,100 @@ +/* + * 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.codegen.poet.auth.scheme; + +import java.util.Objects; +import software.amazon.awssdk.codegen.model.service.AuthType; + +/** + * Represents an authentication option, encapsulating attributes such as the authentication type + * and whether the payload should be unsigned. This class provides a clean and immutable way + * to model these attributes as separate traits, as specified in the service models. + * + *

The primary purpose of this class is to hold authentication-related attributes, such as: + *

    + *
  • authType: Specifies the type of authentication to be used (e.g., SigV4, SigV4A etc).
  • + *
  • unsignedPayload: Indicates whether the payload should be unsigned.
  • + *
+ */ +public final class AuthTrait { + + private final AuthType authType; + private final boolean unsignedPayload; + + private AuthTrait(AuthType authType, boolean unsignedPayload) { + this.authType = Objects.requireNonNull(authType, "authType must not be null"); + this.unsignedPayload = unsignedPayload; + } + + public static Builder builder() { + return new Builder(); + } + + public AuthType authType() { + return authType; + } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuthTrait that = (AuthTrait) o; + return unsignedPayload == that.unsignedPayload && authType == that.authType; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(authType); + hashCode = 31 * hashCode + (unsignedPayload ? 1 : 0); + return hashCode; + } + + @Override + public String toString() { + return "AuthTrait{" + + "authType=" + authType + + ", unsignedPayload=" + unsignedPayload + + '}'; + } + + public static class Builder { + private AuthType authType; + private boolean unsignedPayload = false; + + public Builder authType(AuthType authType) { + this.authType = authType; + return this; + } + + public Builder unsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + return this; + } + + public AuthTrait build() { + return new AuthTrait(authType, unsignedPayload); + } + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java index 3592f0dbf5a9..28b62024a00d 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.Validate; @@ -79,6 +80,10 @@ private MethodSpec constructor() { b.addStatement("this.region = builder.region"); } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + b.addStatement("this.regionSet = builder.regionSet"); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -145,6 +150,9 @@ private void addBuilderConstructors(TypeSpec.Builder b) { if (authSchemeSpecUtils.usesSigV4()) { builderFromInstance.addStatement("this.region = params.region"); } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + builderFromInstance.addStatement("this.regionSet = params.regionSet"); + } if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -181,6 +189,19 @@ private void addFieldsAndAccessors(TypeSpec.Builder b) { .build()); } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + b.addField(FieldSpec.builder(RegionSet.class, "regionSet") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build()); + + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(RegionSet.class) + .addStatement("return regionSet") + .build()); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -227,6 +248,13 @@ private void addBuilderFieldsAndSetter(TypeSpec.Builder b) { b.addMethod(builderSetterMethod("region", TypeName.get(Region.class))); } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + b.addField(FieldSpec.builder(RegionSet.class, "regionSet") + .addModifiers(Modifier.PRIVATE) + .build()); + b.addMethod(builderSetterMethod("regionSet", TypeName.get(RegionSet.class))); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java index c921153ac395..4586d25a9b2b 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/EndpointBasedAuthSchemeProviderSpec.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; @@ -44,6 +45,7 @@ import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; @@ -217,7 +219,10 @@ private void addAuthSchemeSwitchSigV4aCase(MethodSpec.Builder spec) { SigV4aAuthScheme.class, Validate.class, SigV4aAuthScheme.class, "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s"); - spec.addStatement("$1T regionSet = $1T.create(sigv4aAuthScheme.signingRegionSet())", RegionSet.class); + spec.addStatement("$1T regionSet = $2T.ofNullable(params.regionSet())" + + ".orElseGet(() -> $2T.ofNullable(sigv4aAuthScheme.signingRegionSet())" + + ".filter(set -> !$3T.isNullOrEmpty(set)).map($1T::create).orElse(null))", + RegionSet.class, Optional.class, CollectionUtils.class); CodeBlock.Builder block = CodeBlock.builder(); block.add("$1T.builder().schemeId($2T.SCHEME_ID)", AuthSchemeOption.class, diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java index 2b0a39145e43..e3c247bed068 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -72,26 +72,32 @@ public Map, List> operationsToMetadata() * {@link Collections#emptyList()}. */ private Map, List> operationsToModeledMetadata() { - Map, List> operationsToAuthType = operationsToAuthType(); + Map, List> operationsToAuthOption = operationsToAuthOptions(); Map, List> operationsToMetadata = new LinkedHashMap<>(); - operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); + operationsToAuthOption.forEach((k, v) -> operationsToMetadata.put(k, authOptionToCodegenMetadata(v))); return operationsToMetadata; } /** - * Returns a map from list of operations to the list of auth-types modeled for those operations. The values are taken directly - * from the model {@link OperationModel#getAuth()} method. + * Returns a map from a list of operations to the list of auth-types modeled for those operations. + * The {@link AuthTrait} values are taken directly from the {@link OperationModel} */ - private Map, List> operationsToAuthType() { - Map, List> authSchemesToOperations = + private Map, List> operationsToAuthOptions() { + // Group operations by their shared AuthTraits. + // The map's keys are AuthTrait lists, and the values are lists of operation names. + Map, List> authSchemesToOperations = intermediateModel.getOperations() .entrySet() .stream() .filter(kvp -> !kvp.getValue().getAuth().isEmpty()) - .collect(Collectors.groupingBy(kvp -> kvp.getValue().getAuth(), - Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); - - Map, List> operationsToAuthType = authSchemesToOperations + .collect(Collectors.groupingBy( + kvp -> toAuthTrait(kvp.getValue()), + Collectors.mapping(Map.Entry::getKey, Collectors.toList()) + )); + + // Convert the map to have operation names as keys and AuthTrait options as values, + // sorted by the first operation name in each group. + Map, List> operationsToAuthTrait = authSchemesToOperations .entrySet() .stream() .sorted(Comparator.comparing(kvp -> kvp.getValue().get(0))) @@ -99,16 +105,29 @@ private Map, List> operationsToAuthType() { Map.Entry::getKey, (a, b) -> b, LinkedHashMap::new)); - List serviceDefaults = serviceDefaultAuthTypes(); + List serviceDefaults = serviceDefaultAuthOption(); - // Get the list of operations that share the same auth schemes as the system defaults and remove it from the result. We - // will take care of all of these in the fallback `default` case. + // Handle operations with defaults List operationsWithDefaults = authSchemesToOperations.remove(serviceDefaults); - operationsToAuthType.remove(operationsWithDefaults); - operationsToAuthType.put(Collections.emptyList(), serviceDefaults); - return operationsToAuthType; + if (operationsWithDefaults != null) { + operationsToAuthTrait.remove(operationsWithDefaults); + } + operationsToAuthTrait.put(Collections.emptyList(), serviceDefaults); + return operationsToAuthTrait; } + /** + * Converts an {@link OperationModel} to a list of {@link AuthTrait} instances based on the authentication related traits + * defined in the {@link OperationModel}. + */ + private List toAuthTrait(OperationModel operation) { + return operation.getAuth().stream() + .map(authType -> AuthTrait.builder() + .authType(authType) + .unsignedPayload(operation.isUnsignedPayload()) + .build()) + .collect(Collectors.toList()); + } /** * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The service @@ -146,6 +165,13 @@ private Map, List> operationsToModeledMe * Returns the list of modeled top-level auth-types. */ private List serviceDefaultAuthTypes() { + + // First, look at legacy signature versions. + if (intermediateModel.getMetadata().getAuthType() != null + && intermediateModel.getMetadata().getAuthType() != AuthType.V4) { + return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); + } + List modeled = intermediateModel.getMetadata().getAuth(); if (!modeled.isEmpty()) { return modeled; @@ -153,7 +179,18 @@ private List serviceDefaultAuthTypes() { return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); } - private List authTypeToCodegenMetadata(List authTypes) { + /** + * Returns the list of modeled top-level auth-options. + */ + private List serviceDefaultAuthOption() { + List modeled = intermediateModel.getMetadata().getAuth(); + if (!modeled.isEmpty()) { + return modeled.stream().map(r -> AuthTrait.builder().authType(r).build()).collect(Collectors.toList()); + } + return Collections.singletonList(AuthTrait.builder().authType(intermediateModel.getMetadata().getAuthType()).build()); + } + + private List authOptionToCodegenMetadata(List authTypes) { return authTypes.stream().map(AuthSchemeCodegenMetadataExt::fromAuthType).collect(Collectors.toList()); } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index a28f4eb48ddc..1ca3f9b38ba5 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -76,6 +76,7 @@ import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.ProtocolNegotiation; import software.amazon.awssdk.http.SdkHttpConfigurationOption; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; @@ -214,6 +215,10 @@ public TypeSpec poetSpec() { builder.addMethod(validateClientOptionsMethod()); + if (authSchemeSpecUtils.hasSigV4aSupport()) { + builder.addMethod(sigv4aSigningRegionSetMethod()); + } + return builder.build(); } @@ -811,6 +816,18 @@ private MethodSpec authSchemeProviderMethod() { .build(); } + private MethodSpec sigv4aSigningRegionSetMethod() { + return MethodSpec.methodBuilder("sigv4aSigningRegionSet") + .addModifiers(Modifier.PUBLIC) + .returns(TypeVariableName.get("B")) + .addParameter(RegionSet.class, "sigv4aSigningRegionSet") + .addStatement("clientConfiguration.option($T.AWS_SIGV4A_SIGNING_REGION_SET, " + + "sigv4aSigningRegionSet == null ? $T.emptySet() : sigv4aSigningRegionSet.asSet())", + AwsClientOption.class, Collections.class) + .addStatement("return thisBuilder()") + .build(); + } + private MethodSpec defaultAuthSchemeProviderMethod() { return MethodSpec.methodBuilder("defaultAuthSchemeProvider") .addModifiers(PRIVATE) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java index 2f1b735fa939..8e70214eb4eb 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java @@ -39,9 +39,11 @@ import software.amazon.awssdk.codegen.poet.rules.EndpointParamsKnowledgeIndex; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.codegen.utils.AuthUtils; +import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.utils.internal.CodegenNamingUtils; @@ -116,6 +118,10 @@ public TypeSpec poetSpec() { builder.addMethod(responseChecksumValidationMethod()); } + if (authSchemeSpecUtils.hasSigV4aSupport()) { + builder.addMethod(sigv4aSigningRegionSetMethod()); + } + return builder.build(); } @@ -286,6 +292,24 @@ private boolean hasSdkClientContextParams() { && !model.getCustomizationConfig().getCustomClientContextParams().isEmpty(); } + private MethodSpec sigv4aSigningRegionSetMethod() { + return MethodSpec.methodBuilder("sigv4aSigningRegionSet") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(RegionSet.class, "sigv4aSigningRegionSet") + .addJavadoc("Sets the {@link $T} to be used for operations using Sigv4a signing requests.\n" + + "This is optional; if not provided, the following precedence is used:\n" + + "
    \n" + + "
  1. {@link $T#AWS_SIGV4A_SIGNING_REGION_SET}.
  2. \n" + + "
  3. as sigv4a_signing_region_set in the configuration file.
  4. \n" + + "
  5. The region configured for the client.
  6. \n" + + "
\n", + RegionSet.class, + SdkSystemSetting.class) + .returns(TypeVariableName.get("B")) + .addStatement("throw new $T()", UnsupportedOperationException.class) + .build(); + } + private boolean hasRequestAlgorithmMember(IntermediateModel model) { return model.getOperations().values().stream() .anyMatch(opModel -> opModel.getHttpChecksum() != null diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java index 78ec4b6f9ce8..f03f615b57bc 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java @@ -99,6 +99,8 @@ public class EndpointResolverInterceptorSpec implements ClassSpec { private final JmesPathAcceptorGenerator jmesPathGenerator; private final boolean dependsOnHttpAuthAws; private final boolean useSraAuth; + private final boolean multiAuthSigv4a; + private final boolean legacyAuthFromEndpointRulesService; public EndpointResolverInterceptorSpec(IntermediateModel model) { @@ -116,6 +118,8 @@ public EndpointResolverInterceptorSpec(IntermediateModel model) { supportedAuthSchemes.contains(AwsV4aAuthScheme.class); this.useSraAuth = new AuthSchemeSpecUtils(model).useSraAuth(); + this.multiAuthSigv4a = new AuthSchemeSpecUtils(model).usesSigV4a(); + this.legacyAuthFromEndpointRulesService = new AuthSchemeSpecUtils(model).generateEndpointBasedParams(); } @Override @@ -192,7 +196,9 @@ private MethodSpec modifyRequestMethod(String endpointAuthSchemeStrategyFieldNam endpointRulesSpecUtils.providerInterfaceName(), providerVar, SdkInternalExecutionAttribute.class); b.beginControlFlow("try"); b.addStatement("long resolveEndpointStart = $T.nanoTime()", System.class); - b.addStatement("$T endpoint = $N.resolveEndpoint(ruleParams(result, executionAttributes)).join()", + b.addStatement("$T endpointParams = ruleParams(result, executionAttributes)", + endpointRulesSpecUtils.parametersClassName()); + b.addStatement("$T endpoint = $N.resolveEndpoint(endpointParams).join()", Endpoint.class, providerVar); b.addStatement("$1T resolveEndpointDuration = $1T.ofNanos($2T.nanoTime() - resolveEndpointStart)", Duration.class, System.class); @@ -219,7 +225,20 @@ private MethodSpec modifyRequestMethod(String endpointAuthSchemeStrategyFieldNam SelectedAuthScheme.class, SdkInternalExecutionAttribute.class); b.beginControlFlow("if (endpointAuthSchemes != null && selectedAuthScheme != null)"); b.addStatement("selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme)"); - + if (multiAuthSigv4a || legacyAuthFromEndpointRulesService) { + b.addComment("Precedence of SigV4a RegionSet is set according to multi-auth SigV4a specifications"); + b.beginControlFlow("if(selectedAuthScheme.authSchemeOption().schemeId().equals($T.SCHEME_ID) " + + "&& selectedAuthScheme.authSchemeOption().signerProperty($T.REGION_SET) == null)", + AwsV4aAuthScheme.class, AwsV4aHttpSigner.class); + b.addStatement("$T optionBuilder = selectedAuthScheme.authSchemeOption().toBuilder()", + AuthSchemeOption.Builder.class); + b.addStatement("$T regionSet = $T.create(endpointParams.region().id())", + RegionSet.class, RegionSet.class); + b.addStatement("optionBuilder.putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class); + b.addStatement("selectedAuthScheme = new $T(selectedAuthScheme.identity(), selectedAuthScheme.signer(), " + + "optionBuilder.build())", SelectedAuthScheme.class); + b.endControlFlow(); + } b.addStatement("executionAttributes.putAttribute($T.SELECTED_AUTH_SCHEME, selectedAuthScheme)", SdkInternalExecutionAttribute.class); b.endControlFlow(); @@ -774,7 +793,7 @@ private static CodeBlock copyV4EndpointSignerPropertiesToAuth() { return code.build(); } - private static CodeBlock copyV4aEndpointSignerPropertiesToAuth() { + private CodeBlock copyV4aEndpointSignerPropertiesToAuth() { CodeBlock.Builder code = CodeBlock.builder(); code.beginControlFlow("if (endpointAuthScheme instanceof $T)", SigV4aAuthScheme.class); @@ -784,10 +803,15 @@ private static CodeBlock copyV4aEndpointSignerPropertiesToAuth() { code.addStatement("option.putSignerProperty($T.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding())", AwsV4aHttpSigner.class); code.endControlFlow(); - - code.beginControlFlow("if (v4aAuthScheme.signingRegionSet() != null)"); + if (multiAuthSigv4a || legacyAuthFromEndpointRulesService) { + code.beginControlFlow("if (!(selectedAuthScheme.authSchemeOption().schemeId().equals($T.SCHEME_ID) " + + "&& selectedAuthScheme.authSchemeOption().signerProperty($T.REGION_SET) != null) " + + "&& !$T.isNullOrEmpty(v4aAuthScheme.signingRegionSet()))", + AwsV4aAuthScheme.class, AwsV4aHttpSigner.class, CollectionUtils.class); + } else { + code.beginControlFlow("if (!$T.isNullOrEmpty(v4aAuthScheme.signingRegionSet()))", CollectionUtils.class); + } code.addStatement("$1T regionSet = $1T.create(v4aAuthScheme.signingRegionSet())", RegionSet.class); - code.addStatement("option.putSignerProperty($T.REGION_SET, regionSet)", AwsV4aHttpSigner.class); code.endControlFlow(); @@ -881,5 +905,4 @@ private MethodSpec constructorMethodSpec(String endpointAuthSchemeFieldName) { b.addStatement("this.$N = $N.endpointAuthSchemeStrategy()", endpointAuthSchemeFieldName, factoryLocalVarName); return b.build(); } - } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java index d21434df30ba..f870ceea284d 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java @@ -18,6 +18,7 @@ import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.OperationModel; import software.amazon.awssdk.codegen.model.service.AuthType; +import software.amazon.awssdk.utils.CollectionUtils; public final class AuthUtils { private AuthUtils() { @@ -36,6 +37,16 @@ public static boolean usesBearerAuth(IntermediateModel model) { .anyMatch(authType -> authType == AuthType.BEARER); } + public static boolean usesSigv4aAuth(IntermediateModel model) { + if (isServiceSigv4a(model)) { + return true; + } + return model.getOperations() + .values() + .stream() + .anyMatch(operationModel -> operationModel.getAuth().stream().anyMatch(authType -> authType == AuthType.V4A)); + } + public static boolean usesAwsAuth(IntermediateModel model) { if (isServiceAwsAuthType(model)) { return true; @@ -60,8 +71,18 @@ private static boolean isServiceBearerAuth(IntermediateModel model) { return model.getMetadata().getAuthType() == AuthType.BEARER; } + private static boolean isServiceSigv4a(IntermediateModel model) { + return model.getMetadata().getAuth().stream().anyMatch(authType -> authType == AuthType.V4A); + } + private static boolean isServiceAwsAuthType(IntermediateModel model) { AuthType authType = model.getMetadata().getAuthType(); + if (authType == null && !CollectionUtils.isNullOrEmpty(model.getMetadata().getAuth())) { + return model.getMetadata().getAuth().stream() + .map(AuthType::value) + .map(AuthType::fromValue) + .anyMatch(AuthUtils::isAuthTypeAws); + } return isAuthTypeAws(authType); } @@ -71,6 +92,7 @@ private static boolean isAuthTypeAws(AuthType authType) { } switch (authType) { + case V4A: case V4: case S3: case S3V4: diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java index a75232114d83..308aa69ea487 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java @@ -365,6 +365,21 @@ public static IntermediateModel serviceMiniS3() { return new IntermediateModelBuilder(models).build(); } + public static IntermediateModel opsWithSigv4a() { + File serviceModel = + new File(ClientTestModels.class.getResource("client/c2j/ops-with-auth-sigv4a-value/service-2.json").getFile()); + File customizationModel = + new File(ClientTestModels.class.getResource("client/c2j/ops-with-auth-sigv4a-value/customization.config") + .getFile()); + C2jModels models = C2jModels + .builder() + .serviceModel(getServiceModel(serviceModel)) + .customizationConfig(getCustomizationConfig(customizationModel)) + .build(); + + return new IntermediateModelBuilder(models).build(); + } + public static IntermediateModel xmlServiceModels() { File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/xml/service-2.json").getFile()); File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/xml/customization.config").getFile()); diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java index 15b061f9a835..dba6bca98c74 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java @@ -189,6 +189,31 @@ static List parameters() { .classSpecProvider(AuthSchemeInterceptorSpec::new) .caseName("query-endpoint-auth-params-without-allowlist") .outputFileSuffix("interceptor") + .build(), + // Service with auth trait with Sigv4a + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("default-provider") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(AuthSchemeParamsSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("params") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(DefaultAuthSchemeParamsSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("default-params") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(AuthSchemeInterceptorSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("interceptor") .build() ); } @@ -199,6 +224,14 @@ static class TestCase { private final String outputFileSuffix; private final String caseName; + + @Override + public String toString() { + return "TestCase{" + + "caseName='" + caseName + "-" + outputFileSuffix + '\'' + + '}'; + } + TestCase(Builder builder) { this.modelProvider = builder.modelProvider; this.classSpecProvider = builder.classSpecProvider; diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java index 3250523f6695..a09271f4001a 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java @@ -19,6 +19,7 @@ import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.internalConfigModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.operationWithNoAuth; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModelsEndpointAuthParamsWithAllowList; import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; @@ -91,6 +92,11 @@ void baseClientBuilderClassWithNoAuthOperation_sra() { validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java", true); } + @Test + void baseClientBuilderClassWithMultiAuthSigv4a() { + validateBaseClientBuilderClassGeneration(opsWithSigv4a(), "test-multi-auth-sigv4a-client-builder-class.java", true); + } + @Test void baseClientBuilderClassWithNoAuthService_sra() { validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java", true); diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java index 1430ba2e779d..b67460a2690e 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java @@ -17,6 +17,7 @@ import static software.amazon.awssdk.codegen.poet.ClientTestModels.bearerAuthServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; @@ -49,6 +50,11 @@ public void syncHasCrossRegionAccessEnabledPropertyBuilderClass() { "test-customcontextparams-sync-client-builder-class.java"); } + @Test + void baseClientBuilderInterfaceWithMultiAuth() { + validateBaseClientBuilderInterfaceGeneration(opsWithSigv4a(), "test-multi-auth-sigv4a-client-builder-interface.java"); + } + private void validateBaseClientBuilderInterfaceGeneration(IntermediateModel model, String expectedClassName) { validateGeneration(BaseClientBuilderInterface::new, model, expectedClassName); } diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java index f3cf566962d1..77c5a44e3203 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpecTest.java @@ -42,4 +42,16 @@ private static IntermediateModel getModel(boolean useSraAuth) { model.getCustomizationConfig().setUseSraAuth(useSraAuth); return model; } + + @Test + void endpointResolverInterceptorClassWithSigv4aMultiAuth() { + ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(ClientTestModels.opsWithSigv4a()); + assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor-with-multiauthsigv4a.java")); + } + + @Test + void endpointResolverInterceptorClassWithEndpointBasedAuth() { + ClassSpec endpointProviderInterceptor = new EndpointResolverInterceptorSpec(ClientTestModels.queryServiceModelsEndpointAuthParamsWithoutAllowList()); + assertThat(endpointProviderInterceptor, generatesTo("endpoint-resolve-interceptor-with-endpointsbasedauth.java")); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java new file mode 100644 index 000000000000..8fc91e2069fe --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java @@ -0,0 +1,88 @@ +package software.amazon.awssdk.services.database.auth.scheme.internal; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeParams implements DatabaseAuthSchemeParams { + private final String operation; + + private final Region region; + + private final RegionSet regionSet; + + private DefaultDatabaseAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + this.regionSet = builder.regionSet; + } + + public static DatabaseAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Region region() { + return region; + } + + @Override + public RegionSet regionSet() { + return regionSet; + } + + @Override + public DatabaseAuthSchemeParams.Builder toBuilder() { + return new Builder(this); + } + + private static final class Builder implements DatabaseAuthSchemeParams.Builder { + private String operation; + + private Region region; + + private RegionSet regionSet; + + Builder() { + } + + Builder(DefaultDatabaseAuthSchemeParams params) { + this.operation = params.operation; + this.region = params.region; + this.regionSet = params.regionSet; + } + + @Override + public Builder operation(String operation) { + this.operation = operation; + return this; + } + + @Override + public Builder region(Region region) { + this.region = region; + return this; + } + + @Override + public Builder regionSet(RegionSet regionSet) { + this.regionSet = regionSet; + return this; + } + + @Override + public DatabaseAuthSchemeParams build() { + return new DefaultDatabaseAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java new file mode 100644 index 000000000000..8d2f50135504 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java @@ -0,0 +1,84 @@ +package software.amazon.awssdk.services.database.auth.scheme.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeProvider implements DatabaseAuthSchemeProvider { + private static final DefaultDatabaseAuthSchemeProvider DEFAULT = new DefaultDatabaseAuthSchemeProvider(); + + private DefaultDatabaseAuthSchemeProvider() { + } + + public static DefaultDatabaseAuthSchemeProvider create() { + return DEFAULT; + } + + @Override + public List resolveAuthScheme(DatabaseAuthSchemeParams params) { + List options = new ArrayList<>(); + switch (params.operation()) { + case "DeleteRow": + case "PutRow": + case "opWithSigv4SignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + case "opWithSigv4AndSigv4aUnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()) + .putSignerProperty(AwsV4aHttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opWithSigv4UnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opWithSigv4aSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()).build()); + break; + case "opWithSigv4aUnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()) + .putSignerProperty(AwsV4aHttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opsWithSigv4andSigv4aSignedPayload": + case "secondOpsWithSigv4andSigv4aSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()).build()); + break; + default: + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()).build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + break; + } + return Collections.unmodifiableList(options); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java new file mode 100644 index 000000000000..70b901338096 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java @@ -0,0 +1,155 @@ +package software.amazon.awssdk.services.database.auth.scheme.internal; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +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.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.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.SdkMetric; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DatabaseAuthSchemeInterceptor implements ExecutionInterceptor { + private static Logger LOG = Logger.loggerFor(DatabaseAuthSchemeInterceptor.class); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + List authOptions = resolveAuthOptions(context, executionAttributes); + SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); + putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + } + + private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + DatabaseAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(DatabaseAuthSchemeProvider.class, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), + "Expected an instance of DatabaseAuthSchemeProvider"); + DatabaseAuthSchemeParams params = authSchemeParams(context.request(), executionAttributes); + return authSchemeProvider.resolveAuthScheme(params); + } + + private SelectedAuthScheme selectAuthScheme(List authOptions, + ExecutionAttributes executionAttributes) { + MetricCollector metricCollector = executionAttributes.getAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); + Map> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + List> discardedReasons = new ArrayList<>(); + for (AuthSchemeOption authOption : authOptions) { + AuthScheme authScheme = authSchemes.get(authOption.schemeId()); + SelectedAuthScheme selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, + identityProviders, discardedReasons, metricCollector, executionAttributes); + if (selectedAuthScheme != null) { + if (!discardedReasons.isEmpty()) { + LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", ")))); + } + return selectedAuthScheme; + } + } + throw SdkException + .builder() + .message( + "Failed to determine how to authenticate the user: " + + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", "))).build(); + } + + private DatabaseAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + DatabaseAuthSchemeParams.Builder builder = DatabaseAuthSchemeParams.builder().operation(operation); + Region region = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); + builder.region(region); + executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet)) + .ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet))); + return builder.build(); + } + + private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, + IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, + ExecutionAttributes executionAttributes) { + if (authScheme == null) { + discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); + return null; + } + IdentityProvider identityProvider = authScheme.identityProvider(identityProviders); + if (identityProvider == null) { + discardedReasons + .add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId())); + return null; + } + HttpSigner signer; + try { + signer = authScheme.signer(); + } catch (RuntimeException e) { + discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(), + e.getMessage())); + return null; + } + ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder(); + authOption.forEachIdentityProperty(identityRequestBuilder::putProperty); + CompletableFuture identity; + SdkMetric metric = getIdentityMetric(identityProvider); + if (metric == null) { + identity = identityProvider.resolveIdentity(identityRequestBuilder.build()); + } else { + identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()), + metricCollector, metric); + } + return new SelectedAuthScheme<>(identity, signer, authOption); + } + + private SdkMetric getIdentityMetric(IdentityProvider identityProvider) { + Class identityType = identityProvider.identityType(); + if (identityType == AwsCredentialsIdentity.class) { + return CoreMetric.CREDENTIALS_FETCH_DURATION; + } + if (identityType == TokenIdentity.class) { + return CoreMetric.TOKEN_FETCH_DURATION; + } + return null; + } + + private void putSelectedAuthScheme(ExecutionAttributes attributes, + SelectedAuthScheme selectedAuthScheme) { + SelectedAuthScheme existingAuthScheme = attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (existingAuthScheme != null) { + AuthSchemeOption.Builder selectedOption = selectedAuthScheme.authSchemeOption().toBuilder(); + existingAuthScheme.authSchemeOption().forEachIdentityProperty(selectedOption::putIdentityPropertyIfAbsent); + existingAuthScheme.authSchemeOption().forEachSignerProperty(selectedOption::putSignerPropertyIfAbsent); + selectedAuthScheme = new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), + selectedOption.build()); + } + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java new file mode 100644 index 000000000000..37a202d5ee3a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java @@ -0,0 +1,69 @@ +package software.amazon.awssdk.services.database.auth.scheme; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.internal.DefaultDatabaseAuthSchemeParams; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * The parameters object used to resolve the auth schemes for the Database service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface DatabaseAuthSchemeParams extends ToCopyableBuilder { + /** + * Get a new builder for creating a {@link DatabaseAuthSchemeParams}. + */ + static Builder builder() { + return DefaultDatabaseAuthSchemeParams.builder(); + } + + /** + * Returns the operation for which to resolve the auth scheme. + */ + String operation(); + + /** + * Returns the region. The region parameter may be used with the "aws.auth#sigv4" auth scheme. + */ + Region region(); + + /** + * Returns the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + RegionSet regionSet(); + + /** + * Returns a {@link Builder} to customize the parameters. + */ + Builder toBuilder(); + + /** + * A builder for a {@link DatabaseAuthSchemeParams}. + */ + interface Builder extends CopyableBuilder { + /** + * Set the operation for which to resolve the auth scheme. + */ + Builder operation(String operation); + + /** + * Set the region. The region parameter may be used with the "aws.auth#sigv4" auth scheme. + */ + Builder region(Region region); + + /** + * Set the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + Builder regionSet(RegionSet regionSet); + + /** + * Returns a {@link DatabaseAuthSchemeParams} object that is created from the properties that have been set on + * the builder. + */ + DatabaseAuthSchemeParams build(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java index 48edb00b1855..0b30a534901e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java @@ -84,10 +84,11 @@ private SelectedAuthScheme selectAuthScheme(List SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java index 9bc1b2065fe1..87eaf93e4db4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-with-allowlist.java @@ -17,6 +17,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; @@ -29,6 +30,8 @@ public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams private final Region region; + private final RegionSet regionSet; + private final Boolean defaultTrueParam; private final String defaultStringParam; @@ -46,6 +49,7 @@ public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams private DefaultQueryAuthSchemeParams(Builder builder) { this.operation = Validate.paramNotNull(builder.operation, "operation"); this.region = builder.region; + this.regionSet = builder.regionSet; this.defaultTrueParam = Validate.paramNotNull(builder.defaultTrueParam, "defaultTrueParam"); this.defaultStringParam = Validate.paramNotNull(builder.defaultStringParam, "defaultStringParam"); this.deprecatedParam = builder.deprecatedParam; @@ -69,6 +73,11 @@ public Region region() { return region; } + @Override + public RegionSet regionSet() { + return regionSet; + } + @Override public Boolean defaultTrueParam() { return defaultTrueParam; @@ -115,6 +124,8 @@ private static final class Builder implements QueryAuthSchemeParams.Builder, Que private Region region; + private RegionSet regionSet; + private Boolean defaultTrueParam = true; private String defaultStringParam = "hello endpoints"; @@ -135,6 +146,7 @@ private static final class Builder implements QueryAuthSchemeParams.Builder, Que Builder(DefaultQueryAuthSchemeParams params) { this.operation = params.operation; this.region = params.region; + this.regionSet = params.regionSet; this.defaultTrueParam = params.defaultTrueParam; this.defaultStringParam = params.defaultStringParam; this.deprecatedParam = params.deprecatedParam; @@ -156,6 +168,12 @@ public Builder region(Region region) { return this; } + @Override + public Builder regionSet(RegionSet regionSet) { + this.regionSet = regionSet; + return this; + } + @Override public Builder defaultTrueParam(Boolean defaultTrueParam) { this.defaultTrueParam = defaultTrueParam; diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java index 8e01dcf75827..ee65f8d60da4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-default-params-without-allowlist.java @@ -4,6 +4,7 @@ import java.util.List; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; @@ -16,6 +17,8 @@ public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams private final Region region; + private final RegionSet regionSet; + private final Boolean useDualStackEndpoint; private final Boolean useFIPSEndpoint; @@ -47,6 +50,7 @@ public final class DefaultQueryAuthSchemeParams implements QueryAuthSchemeParams private DefaultQueryAuthSchemeParams(Builder builder) { this.operation = Validate.paramNotNull(builder.operation, "operation"); this.region = builder.region; + this.regionSet = builder.regionSet; this.useDualStackEndpoint = builder.useDualStackEndpoint; this.useFIPSEndpoint = builder.useFIPSEndpoint; this.accountId = builder.accountId; @@ -77,6 +81,11 @@ public Region region() { return region; } + @Override + public RegionSet regionSet() { + return regionSet; + } + @Override public Boolean useDualStackEndpoint() { return useDualStackEndpoint; @@ -158,6 +167,8 @@ private static final class Builder implements QueryAuthSchemeParams.Builder, Que private Region region; + private RegionSet regionSet; + private Boolean useDualStackEndpoint; private Boolean useFIPSEndpoint; @@ -192,6 +203,7 @@ private static final class Builder implements QueryAuthSchemeParams.Builder, Que Builder(DefaultQueryAuthSchemeParams params) { this.operation = params.operation; this.region = params.region; + this.regionSet = params.regionSet; this.useDualStackEndpoint = params.useDualStackEndpoint; this.useFIPSEndpoint = params.useFIPSEndpoint; this.accountId = params.accountId; @@ -220,6 +232,12 @@ public Builder region(Region region) { return this; } + @Override + public Builder regionSet(RegionSet regionSet) { + this.regionSet = regionSet; + return this; + } + @Override public Builder useDualStackEndpoint(Boolean useDualStackEndpoint) { this.useDualStackEndpoint = useDualStackEndpoint; diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java index ac5f527c9e7d..20b64152ad0d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider-without-allowlist.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; @@ -20,6 +21,7 @@ import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; @@ -72,7 +74,9 @@ public List resolveAuthScheme(QueryAuthSchemeParams params) { SigV4aAuthScheme sigv4aAuthScheme = Validate.isInstanceOf(SigV4aAuthScheme.class, authScheme, "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() .getName()); - RegionSet regionSet = RegionSet.create(sigv4aAuthScheme.signingRegionSet()); + RegionSet regionSet = Optional.ofNullable(params.regionSet()).orElseGet( + () -> Optional.ofNullable(sigv4aAuthScheme.signingRegionSet()) + .filter(set -> !CollectionUtils.isNullOrEmpty(set)).map(RegionSet::create).orElse(null)); AuthSchemeOption sigv4aAuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4aAuthScheme.SCHEME_ID) .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java index ab4ed5f48035..fd1ff149c237 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-endpoint-provider.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; @@ -20,6 +21,7 @@ import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; @@ -68,7 +70,9 @@ public List resolveAuthScheme(QueryAuthSchemeParams params) { SigV4aAuthScheme sigv4aAuthScheme = Validate.isInstanceOf(SigV4aAuthScheme.class, authScheme, "Expecting auth scheme of class SigV4AuthScheme, got instead object of class %s", authScheme.getClass() .getName()); - RegionSet regionSet = RegionSet.create(sigv4aAuthScheme.signingRegionSet()); + RegionSet regionSet = Optional.ofNullable(params.regionSet()).orElseGet( + () -> Optional.ofNullable(sigv4aAuthScheme.signingRegionSet()) + .filter(set -> !CollectionUtils.isNullOrEmpty(set)).map(RegionSet::create).orElse(null)); AuthSchemeOption sigv4aAuthSchemeOption = AuthSchemeOption.builder().schemeId(AwsV4aAuthScheme.SCHEME_ID) .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, sigv4aAuthScheme.signingName()) .putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java index 38d49f4851a5..7fb0ebe41072 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-with-allowlist.java @@ -17,6 +17,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -45,6 +46,11 @@ static Builder builder() { */ Region region(); + /** + * Returns the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + RegionSet regionSet(); + /** * A param that defauls to true */ @@ -80,6 +86,11 @@ interface Builder extends CopyableBuilder { */ Builder region(Region region); + /** + * Set the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + Builder regionSet(RegionSet regionSet); + /** * A param that defauls to true */ diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java index 1145f91cc7de..e5dd1d988daf 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-auth-scheme-params-without-allowlist.java @@ -3,6 +3,7 @@ import java.util.List; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.query.auth.scheme.internal.DefaultQueryAuthSchemeParams; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -31,6 +32,11 @@ static Builder builder() { */ Region region(); + /** + * Returns the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + RegionSet regionSet(); + Boolean useDualStackEndpoint(); Boolean useFipsEndpoint(); @@ -80,6 +86,11 @@ interface Builder extends CopyableBuilder { */ Builder region(Region region); + /** + * Set the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + Builder regionSet(RegionSet regionSet); + Builder useDualStackEndpoint(Boolean useDualStackEndpoint); Builder useFipsEndpoint(Boolean useFIPSEndpoint); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java index 942aa43d9aee..dcc6418d2e89 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkException; @@ -20,6 +21,7 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; 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; @@ -36,6 +38,7 @@ import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -96,6 +99,10 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr builder.operationContextParam(endpointParams.operationContextParam()); String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); builder.operation(operation); + executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet)) + .ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet))); + if (builder instanceof QueryEndpointResolverAware.Builder) { EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); if (endpointProvider instanceof QueryEndpointProvider) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java index 498681559a0a..e9405f2fbb67 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkException; @@ -20,6 +21,7 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; 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; @@ -36,6 +38,7 @@ import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -103,6 +106,9 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr builder.operationContextParam(endpointParams.operationContextParam()); String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); builder.operation(operation); + executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet)) + .ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet))); if (builder instanceof QueryEndpointResolverAware.Builder) { EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); if (endpointProvider instanceof QueryEndpointProvider) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java index e48d848c4276..80511b9556ce 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-client-builder-endpoints-auth-params.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; @@ -276,4 +277,10 @@ protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), "The 'tokenProvider' must be configured in the client builder."); } + + public B sigv4aSigningRegionSet(RegionSet sigv4aSigningRegionSet) { + clientConfiguration.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET, + sigv4aSigningRegionSet == null ? Collections.emptySet() : sigv4aSigningRegionSet.asSet()); + return thisBuilder(); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java new file mode 100644 index 000000000000..51c803fcad77 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java @@ -0,0 +1,197 @@ +package software.amazon.awssdk.services.database; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; +import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.retries.api.RetryStrategy; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.services.database.auth.scheme.internal.DatabaseAuthSchemeInterceptor; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; +import software.amazon.awssdk.services.database.endpoints.internal.DatabaseRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; +import software.amazon.awssdk.services.database.internal.DatabaseServiceClientConfigurationBuilder; +import software.amazon.awssdk.utils.CollectionUtils; + +/** + * Internal base class for {@link DefaultDatabaseClientBuilder} and {@link DefaultDatabaseAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultDatabaseBaseClientBuilder, C> extends + AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "database-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Database"; + } + + @Override + protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkClientOption.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider()) + .option(SdkClientOption.AUTH_SCHEMES, authSchemes()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new DatabaseAuthSchemeInterceptor()); + endpointInterceptors.add(new DatabaseResolveEndpointInterceptor()); + endpointInterceptors.add(new DatabaseRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/database/execution.interceptors"); + List additionalInterceptors = new ArrayList<>(); + interceptors = CollectionUtils.mergeLists(endpointInterceptors, interceptors); + interceptors = CollectionUtils.mergeLists(interceptors, additionalInterceptors); + interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); + SdkClientConfiguration.Builder builder = config.toBuilder(); + builder.lazyOption(SdkClientOption.IDENTITY_PROVIDERS, c -> { + IdentityProviders.Builder result = IdentityProviders.builder(); + IdentityProvider credentialsIdentityProvider = c.get(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); + if (credentialsIdentityProvider != null) { + result.putIdentityProvider(credentialsIdentityProvider); + } + return result.build(); + }); + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + builder.lazyOptionIfAbsent( + SdkClientOption.CLIENT_ENDPOINT_PROVIDER, + c -> AwsClientEndpointProvider + .builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") + .serviceProfileProperty("database_service") + .serviceEndpointPrefix(serviceEndpointPrefix()) + .defaultProtocol("https") + .region(c.get(AwsClientOption.AWS_REGION)) + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)) + .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, + c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) + .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) + .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + return builder.build(); + } + + @Override + protected final String signingName() { + return "database-service"; + } + + private DatabaseEndpointProvider defaultEndpointProvider() { + return DatabaseEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(DatabaseAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private DatabaseAuthSchemeProvider defaultAuthSchemeProvider() { + return DatabaseAuthSchemeProvider.defaultProvider(); + } + + @Override + public B putAuthScheme(AuthScheme authScheme) { + additionalAuthSchemes.put(authScheme.schemeId(), authScheme); + return thisBuilder(); + } + + private Map> authSchemes() { + Map> schemes = new HashMap<>(3 + this.additionalAuthSchemes.size()); + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + schemes.put(awsV4AuthScheme.schemeId(), awsV4AuthScheme); + AwsV4aAuthScheme awsV4aAuthScheme = AwsV4aAuthScheme.create(); + schemes.put(awsV4aAuthScheme.schemeId(), awsV4aAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return schemes; + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List internalPlugins = internalPlugins(config); + List externalPlugins = plugins(); + if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { + return config; + } + List plugins = CollectionUtils.mergeLists(internalPlugins, externalPlugins); + SdkClientConfiguration.Builder configuration = config.toBuilder(); + DatabaseServiceClientConfigurationBuilder serviceConfigBuilder = new DatabaseServiceClientConfigurationBuilder( + configuration); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + updateRetryStrategyClientConfiguration(configuration); + return configuration.build(); + } + + private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Builder configuration) { + ClientOverrideConfiguration.Builder builder = configuration.asOverrideConfigurationBuilder(); + RetryMode retryMode = builder.retryMode(); + if (retryMode != null) { + configuration.option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.forRetryMode(retryMode)); + } else { + Consumer> configurator = builder.retryStrategyConfigurator(); + if (configurator != null) { + RetryStrategy.Builder defaultBuilder = AwsRetryStrategy.defaultRetryStrategy().toBuilder(); + configurator.accept(defaultBuilder); + configuration.option(SdkClientOption.RETRY_STRATEGY, defaultBuilder.build()); + } else { + RetryStrategy retryStrategy = builder.retryStrategy(); + if (retryStrategy != null) { + configuration.option(SdkClientOption.RETRY_STRATEGY, retryStrategy); + } + } + } + configuration.option(SdkClientOption.CONFIGURED_RETRY_MODE, null); + configuration.option(SdkClientOption.CONFIGURED_RETRY_STRATEGY, null); + configuration.option(SdkClientOption.CONFIGURED_RETRY_CONFIGURATOR, null); + } + + private List internalPlugins(SdkClientConfiguration config) { + return Collections.emptyList(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } + + public B sigv4aSigningRegionSet(RegionSet sigv4aSigningRegionSet) { + clientConfiguration.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET, + sigv4aSigningRegionSet == null ? Collections.emptySet() : sigv4aSigningRegionSet.asSet()); + return thisBuilder(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java index df04abcb8bef..52c27dfcc8ac 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; @@ -251,4 +252,10 @@ protected static void validateClientOptions(SdkClientConfiguration c) { Validate.notNull(c.option(AwsClientOption.TOKEN_IDENTITY_PROVIDER), "The 'tokenProvider' must be configured in the client builder."); } + + public B sigv4aSigningRegionSet(RegionSet sigv4aSigningRegionSet) { + clientConfiguration.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET, + sigv4aSigningRegionSet == null ? Collections.emptySet() : sigv4aSigningRegionSet.asSet()); + return thisBuilder(); + } } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java new file mode 100644 index 000000000000..e7b2d2446708 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java @@ -0,0 +1,45 @@ +package software.amazon.awssdk.services.database; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; + +/** + * This includes configuration specific to Database Service that is supported by both {@link DatabaseClientBuilder} and + * {@link DatabaseAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface DatabaseBaseClientBuilder, C> extends AwsClientBuilder { + /** + * Set the {@link DatabaseEndpointProvider} implementation that will be used by the client to determine the endpoint + * for each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + default B endpointProvider(DatabaseEndpointProvider endpointProvider) { + throw new UnsupportedOperationException(); + } + + /** + * Set the {@link DatabaseAuthSchemeProvider} implementation that will be used by the client to resolve the auth + * scheme for each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + default B authSchemeProvider(DatabaseAuthSchemeProvider authSchemeProvider) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the {@link RegionSet} to be used for operations using Sigv4a signing requests. This is optional; if not + * provided, the following precedence is used: + *
    + *
  1. {@link software.amazon.awssdk.core.SdkSystemSetting#AWS_SIGV4A_SIGNING_REGION_SET}.
  2. + *
  3. as sigv4a_signing_region_set in the configuration file.
  4. + *
  5. The region configured for the client.
  6. + *
+ */ + default B sigv4aSigningRegionSet(RegionSet sigv4aSigningRegionSet) { + throw new UnsupportedOperationException(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config index e1d317b04292..2c63c0851048 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-different-value/customization.config @@ -1,3 +1,2 @@ { - "useMultiAuth": true } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config index e1d317b04292..2c63c0851048 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/all-ops-with-auth-same-value/customization.config @@ -1,3 +1,2 @@ { - "useMultiAuth": true } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config index e1d317b04292..bfd870e452fc 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/fine-grained-auth/customization.config @@ -1,3 +1,3 @@ { - "useMultiAuth": true } + diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/customization.config new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/customization.config @@ -0,0 +1,2 @@ +{ +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json new file mode 100644 index 000000000000..502f3ffeb19d --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json @@ -0,0 +1,90 @@ +{ + "version": "1.2", + "serviceId": "Database Service", + "parameters": { + "region": { + "type": "string", + "builtIn": "AWS::Region", + "required": true, + "documentation": "The region to send requests to" + }, + "useDualStackEndpoint": { + "type": "boolean", + "builtIn": "AWS::UseDualStack" + }, + "useFIPSEndpoint": { + "type": "boolean", + "builtIn": "AWS::UseFIPS" + }, + "AccountId": { + "type": "String", + "builtIn": "AWS::Auth::AccountId" + }, + "operationContextParam": { + "type": "string" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "region" + } + ], + "assign": "partitionResult" + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "endpointId" + } + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "useFIPSEndpoint" + } + ] + } + ], + "error": "FIPS endpoints not supported with multi-region endpoints", + "type": "error" + }, + { + "endpoint": { + "url": "https://{endpointId}.query.{partitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4a", + "signingName": "query", + "signingRegionSet": ["*"] + } + ] + } + }, + "type": "endpoint" + } + ], + "type": "tree" + } + ], + "type": "tree" + } + ] +} + diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json new file mode 100644 index 000000000000..1d273a0cf1b1 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json @@ -0,0 +1,294 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2023-06-08", + "endpointPrefix": "database-service-endpoint", + "globalEndpoint": "database-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Database Service", + "serviceFullName": "Some Service That Uses AWS Database Protocol With Sigv4a", + "serviceId": "Database Service", + "signingName": "database-service", + "signatureVersion": "v4", + "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"], + "uid": "database-service-2023-06-08", + "xmlNamespace": "https://database-service.amazonaws.com/doc/2023-06-08/" + }, + "operations": { + "GetRow": { + "name": "GetRow", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a GET request to get-row with default payload signing.

" }, + "PutRow": { + "name": "PutRow", + "auth": ["v4"], + "http": { + "method": "PUT", + "requestUri": "/put-row/" + }, + "input": { + "shape": "PutRowRequest" + }, + "output": { + "shape": "PutRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a PUT request to put-row with auth as sigv4.

" + }, + "DeleteRow": { + "name": "DeleteRow", + "auth": [ "v4"], + "http": { + "method": "DELETE", + "requestUri": "/delete-row/" + }, + "input": { + "shape": "DeleteRowRequest" + }, + "output": { + "shape": "DeleteRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "documentation": "

Performs a DELETE request to delete-row with auth as sigv4.

" + }, + "opWithSigv4aSignedPayload": { + "name": "OpWithSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4a.

" + }, + "opWithSigv4aUnSignedPayload": { + "name": "opWithSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4a.

" + }, + "opWithSigv4SignedPayload": { + "name": "OpWithSigv4SignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4.

" + }, + "opWithSigv4UnSignedPayload": { + "name": "opWithSigv4UnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4.

" + }, + "opsWithSigv4andSigv4aSignedPayload": { + "name": "opsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "secondOpsWithSigv4andSigv4aSignedPayload": { + "name": "secondOpsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "opWithSigv4AndSigv4aUnSignedPayload": { + "name": "opWithSigv4AndSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4 and sigv4a.

" + } + }, + "shapes": { + "GetRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "GetRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "PutRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "DeleteRowResponse": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String", + "documentation": "

A string Memer

" + } + } + }, + "InvalidInputException": { + "type": "structure", + "members": { + "message": { + "shape": "invalidInputMessage" + } + }, + "documentation": "

The request was rejected because an invalid or out-of-range value was supplied for an input parameter.

", + "error": { + "code": "InvalidInput", + "httpStatusCode": 400, + "senderFault": true + }, + "exception": true + }, + "String":{"type":"string"}, + "invalidInputMessage":{"type":"string"} + }, + "documentation": "A Database Service with Fine granularity authorization schemes" +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config index e1d317b04292..2c63c0851048 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-no-auth/customization.config @@ -1,3 +1,2 @@ { - "useMultiAuth": true } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config index e1d317b04292..2c63c0851048 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/service-with-no-auth/customization.config @@ -1,3 +1,2 @@ { - "useMultiAuth": true } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java index c8c4028f9295..5ff33962d6ae 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java @@ -46,6 +46,7 @@ import software.amazon.awssdk.services.query.model.OperationWithMapOperationContextParamRequest; import software.amazon.awssdk.services.query.model.OperationWithOperationContextParamRequest; import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; @Generated("software.amazon.awssdk:codegen") @@ -68,7 +69,8 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); try { long resolveEndpointStart = System.nanoTime(); - Endpoint endpoint = provider.resolveEndpoint(ruleParams(result, executionAttributes)).join(); + QueryEndpointParams endpointParams = ruleParams(result, executionAttributes); + Endpoint endpoint = provider.resolveEndpoint(endpointParams).join(); Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart); Optional metricCollector = executionAttributes .getOptionalAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); @@ -181,7 +183,7 @@ private SelectedAuthScheme authSchemeWithEndpointSignerP if (v4aAuthScheme.isDisableDoubleEncodingSet()) { option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); } - if (v4aAuthScheme.signingRegionSet() != null) { + if (!CollectionUtils.isNullOrEmpty(v4aAuthScheme.signingRegionSet())) { RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java new file mode 100644 index 000000000000..f658a707b0ef --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java @@ -0,0 +1,264 @@ +package software.amazon.awssdk.services.query.endpoints.internal; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AccountIdEndpointMode; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.awscore.internal.useragent.BusinessMetricsUtils; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; +import software.amazon.awssdk.services.query.jmespath.internal.JmesPathRuntime; +import software.amazon.awssdk.services.query.model.OperationWithContextParamRequest; +import software.amazon.awssdk.services.query.model.OperationWithMapOperationContextParamRequest; +import software.amazon.awssdk.services.query.model.OperationWithOperationContextParamRequest; +import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class QueryResolveEndpointInterceptor implements ExecutionInterceptor { + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + SdkRequest result = context.request(); + if (AwsEndpointProviderUtils.endpointIsDiscovered(executionAttributes)) { + return result; + } + QueryEndpointProvider provider = (QueryEndpointProvider) executionAttributes + .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); + try { + long resolveEndpointStart = System.nanoTime(); + QueryEndpointParams endpointParams = ruleParams(result, executionAttributes); + Endpoint endpoint = provider.resolveEndpoint(endpointParams).join(); + Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart); + Optional metricCollector = executionAttributes + .getOptionalAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); + metricCollector.ifPresent(mc -> mc.reportMetric(CoreMetric.ENDPOINT_RESOLVE_DURATION, resolveEndpointDuration)); + if (!AwsEndpointProviderUtils.disableHostPrefixInjection(executionAttributes)) { + Optional hostPrefix = hostPrefix(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME), + result); + if (hostPrefix.isPresent()) { + endpoint = AwsEndpointProviderUtils.addHostPrefix(endpoint, hostPrefix.get()); + } + } + List endpointAuthSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + SelectedAuthScheme selectedAuthScheme = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (endpointAuthSchemes != null && selectedAuthScheme != null) { + selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme); + // Precedence of SigV4a RegionSet is set according to multi-auth SigV4a specifications + if (selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) + && selectedAuthScheme.authSchemeOption().signerProperty(AwsV4aHttpSigner.REGION_SET) == null) { + AuthSchemeOption.Builder optionBuilder = selectedAuthScheme.authSchemeOption().toBuilder(); + RegionSet regionSet = RegionSet.create(endpointParams.region().id()); + optionBuilder.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + selectedAuthScheme = new SelectedAuthScheme(selectedAuthScheme.identity(), selectedAuthScheme.signer(), + optionBuilder.build()); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); + return result; + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof SdkClientException) { + throw (SdkClientException) cause; + } else { + throw SdkClientException.create("Endpoint resolution failed", cause); + } + } + } + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + if (resolvedEndpoint.headers().isEmpty()) { + return context.httpRequest(); + } + SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder(); + resolvedEndpoint.headers().forEach((name, values) -> { + values.forEach(v -> httpRequestBuilder.appendHeader(name, v)); + }); + return httpRequestBuilder.build(); + } + + public static QueryEndpointParams ruleParams(SdkRequest request, ExecutionAttributes executionAttributes) { + QueryEndpointParams.Builder builder = QueryEndpointParams.builder(); + builder.region(AwsEndpointProviderUtils.regionBuiltIn(executionAttributes)); + builder.useDualStackEndpoint(AwsEndpointProviderUtils.dualStackEnabledBuiltIn(executionAttributes)); + builder.useFipsEndpoint(AwsEndpointProviderUtils.fipsEnabledBuiltIn(executionAttributes)); + builder.accountId(resolveAndRecordAccountIdFromIdentity(executionAttributes)); + builder.accountIdEndpointMode(recordAccountIdEndpointMode(executionAttributes)); + setClientContextParams(builder, executionAttributes); + setContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); + setStaticContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME)); + setOperationContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); + return builder.build(); + } + + private static void setContextParams(QueryEndpointParams.Builder params, String operationName, SdkRequest request) { + switch (operationName) { + case "OperationWithContextParam": + setContextParams(params, (OperationWithContextParamRequest) request); + break; + default: + break; + } + } + + private static void setContextParams(QueryEndpointParams.Builder params, OperationWithContextParamRequest request) { + params.operationContextParam(request.stringMember()); + } + + private static void setStaticContextParams(QueryEndpointParams.Builder params, String operationName) { + switch (operationName) { + case "OperationWithStaticContextParams": + operationWithStaticContextParamsStaticContextParams(params); + break; + default: + break; + } + } + + private static void operationWithStaticContextParamsStaticContextParams(QueryEndpointParams.Builder params) { + params.staticStringParam("hello"); + } + + private SelectedAuthScheme authSchemeWithEndpointSignerProperties( + List endpointAuthSchemes, SelectedAuthScheme selectedAuthScheme) { + for (EndpointAuthScheme endpointAuthScheme : endpointAuthSchemes) { + if (!endpointAuthScheme.schemeId().equals(selectedAuthScheme.authSchemeOption().schemeId())) { + continue; + } + AuthSchemeOption.Builder option = selectedAuthScheme.authSchemeOption().toBuilder(); + if (endpointAuthScheme instanceof SigV4AuthScheme) { + SigV4AuthScheme v4AuthScheme = (SigV4AuthScheme) endpointAuthScheme; + if (v4AuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !v4AuthScheme.disableDoubleEncoding()); + } + if (v4AuthScheme.signingRegion() != null) { + option.putSignerProperty(AwsV4HttpSigner.REGION_NAME, v4AuthScheme.signingRegion()); + } + if (v4AuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, v4AuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + if (endpointAuthScheme instanceof SigV4aAuthScheme) { + SigV4aAuthScheme v4aAuthScheme = (SigV4aAuthScheme) endpointAuthScheme; + if (v4aAuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); + } + if (!(selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) && selectedAuthScheme + .authSchemeOption().signerProperty(AwsV4aHttpSigner.REGION_SET) != null) + && !CollectionUtils.isNullOrEmpty(v4aAuthScheme.signingRegionSet())) { + RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); + option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + } + if (v4aAuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, v4aAuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + throw new IllegalArgumentException("Endpoint auth scheme '" + endpointAuthScheme.name() + + "' cannot be mapped to the SDK auth scheme. Was it declared in the service's model?"); + } + return selectedAuthScheme; + } + + private static void setClientContextParams(QueryEndpointParams.Builder params, ExecutionAttributes executionAttributes) { + AttributeMap clientContextParams = executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS); + Optional.ofNullable(clientContextParams.get(QueryClientContextParams.BOOLEAN_CONTEXT_PARAM)).ifPresent( + params::booleanContextParam); + Optional.ofNullable(clientContextParams.get(QueryClientContextParams.STRING_CONTEXT_PARAM)).ifPresent( + params::stringContextParam); + } + + private static void setOperationContextParams(QueryEndpointParams.Builder params, String operationName, SdkRequest request) { + switch (operationName) { + case "OperationWithMapOperationContextParam": + setOperationContextParams(params, (OperationWithMapOperationContextParamRequest) request); + break; + case "OperationWithOperationContextParam": + setOperationContextParams(params, (OperationWithOperationContextParamRequest) request); + break; + default: + break; + } + } + + private static void setOperationContextParams(QueryEndpointParams.Builder params, + OperationWithMapOperationContextParamRequest request) { + JmesPathRuntime.Value input = new JmesPathRuntime.Value(request); + params.arnList(input.field("RequestMap").keys()); + } + + private static void setOperationContextParams(QueryEndpointParams.Builder params, + OperationWithOperationContextParamRequest request) { + JmesPathRuntime.Value input = new JmesPathRuntime.Value(request); + params.customEndpointArray(input.field("ListMember").field("StringList").wildcard().field("LeafString")); + } + + private static Optional hostPrefix(String operationName, SdkRequest request) { + switch (operationName) { + case "APostOperation": { + return Optional.of("foo-"); + } + default: + return Optional.empty(); + } + } + + private static String resolveAndRecordAccountIdFromIdentity(ExecutionAttributes executionAttributes) { + String accountId = accountIdFromIdentity(executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME)); + executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric( + BusinessMetricFeatureId.RESOLVED_ACCOUNT_ID.value()); + return accountId; + } + + private static String accountIdFromIdentity(SelectedAuthScheme selectedAuthScheme) { + T identity = CompletableFutureUtils.joinLikeSync(selectedAuthScheme.identity()); + String accountId = null; + if (identity instanceof AwsCredentialsIdentity) { + accountId = ((AwsCredentialsIdentity) identity).accountId().orElse(null); + } + return accountId; + } + + private static String recordAccountIdEndpointMode(ExecutionAttributes executionAttributes) { + AccountIdEndpointMode mode = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_AUTH_ACCOUNT_ID_ENDPOINT_MODE); + BusinessMetricsUtils.resolveAccountIdEndpointModeMetric(mode).ifPresent( + m -> executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric(m)); + return mode.name().toLowerCase(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java new file mode 100644 index 000000000000..1333f274df20 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java @@ -0,0 +1,168 @@ +package software.amazon.awssdk.services.database.endpoints.internal; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointParams; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; +import software.amazon.awssdk.utils.CollectionUtils; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DatabaseResolveEndpointInterceptor implements ExecutionInterceptor { + @Override + public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { + SdkRequest result = context.request(); + if (AwsEndpointProviderUtils.endpointIsDiscovered(executionAttributes)) { + return result; + } + DatabaseEndpointProvider provider = (DatabaseEndpointProvider) executionAttributes + .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); + try { + long resolveEndpointStart = System.nanoTime(); + DatabaseEndpointParams endpointParams = ruleParams(result, executionAttributes); + Endpoint endpoint = provider.resolveEndpoint(endpointParams).join(); + Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart); + Optional metricCollector = executionAttributes + .getOptionalAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); + metricCollector.ifPresent(mc -> mc.reportMetric(CoreMetric.ENDPOINT_RESOLVE_DURATION, resolveEndpointDuration)); + if (!AwsEndpointProviderUtils.disableHostPrefixInjection(executionAttributes)) { + Optional hostPrefix = hostPrefix(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME), + result); + if (hostPrefix.isPresent()) { + endpoint = AwsEndpointProviderUtils.addHostPrefix(endpoint, hostPrefix.get()); + } + } + List endpointAuthSchemes = endpoint.attribute(AwsEndpointAttribute.AUTH_SCHEMES); + SelectedAuthScheme selectedAuthScheme = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (endpointAuthSchemes != null && selectedAuthScheme != null) { + selectedAuthScheme = authSchemeWithEndpointSignerProperties(endpointAuthSchemes, selectedAuthScheme); + // Precedence of SigV4a RegionSet is set according to multi-auth SigV4a specifications + if (selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) + && selectedAuthScheme.authSchemeOption().signerProperty(AwsV4aHttpSigner.REGION_SET) == null) { + AuthSchemeOption.Builder optionBuilder = selectedAuthScheme.authSchemeOption().toBuilder(); + RegionSet regionSet = RegionSet.create(endpointParams.region().id()); + optionBuilder.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + selectedAuthScheme = new SelectedAuthScheme(selectedAuthScheme.identity(), selectedAuthScheme.signer(), + optionBuilder.build()); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } + executionAttributes.putAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT, endpoint); + return result; + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof SdkClientException) { + throw (SdkClientException) cause; + } else { + throw SdkClientException.create("Endpoint resolution failed", cause); + } + } + } + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT); + if (resolvedEndpoint.headers().isEmpty()) { + return context.httpRequest(); + } + SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder(); + resolvedEndpoint.headers().forEach((name, values) -> { + values.forEach(v -> httpRequestBuilder.appendHeader(name, v)); + }); + return httpRequestBuilder.build(); + } + + public static DatabaseEndpointParams ruleParams(SdkRequest request, ExecutionAttributes executionAttributes) { + DatabaseEndpointParams.Builder builder = DatabaseEndpointParams.builder(); + builder.region(AwsEndpointProviderUtils.regionBuiltIn(executionAttributes)); + builder.endpoint(AwsEndpointProviderUtils.endpointBuiltIn(executionAttributes)); + setContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); + setStaticContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME)); + setOperationContextParams(builder, executionAttributes.getAttribute(AwsExecutionAttribute.OPERATION_NAME), request); + return builder.build(); + } + + private static void setContextParams(DatabaseEndpointParams.Builder params, String operationName, SdkRequest request) { + } + + private static void setStaticContextParams(DatabaseEndpointParams.Builder params, String operationName) { + } + + private SelectedAuthScheme authSchemeWithEndpointSignerProperties( + List endpointAuthSchemes, SelectedAuthScheme selectedAuthScheme) { + for (EndpointAuthScheme endpointAuthScheme : endpointAuthSchemes) { + if (!endpointAuthScheme.schemeId().equals(selectedAuthScheme.authSchemeOption().schemeId())) { + continue; + } + AuthSchemeOption.Builder option = selectedAuthScheme.authSchemeOption().toBuilder(); + if (endpointAuthScheme instanceof SigV4AuthScheme) { + SigV4AuthScheme v4AuthScheme = (SigV4AuthScheme) endpointAuthScheme; + if (v4AuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, !v4AuthScheme.disableDoubleEncoding()); + } + if (v4AuthScheme.signingRegion() != null) { + option.putSignerProperty(AwsV4HttpSigner.REGION_NAME, v4AuthScheme.signingRegion()); + } + if (v4AuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, v4AuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + if (endpointAuthScheme instanceof SigV4aAuthScheme) { + SigV4aAuthScheme v4aAuthScheme = (SigV4aAuthScheme) endpointAuthScheme; + if (v4aAuthScheme.isDisableDoubleEncodingSet()) { + option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); + } + if (!(selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) && selectedAuthScheme + .authSchemeOption().signerProperty(AwsV4aHttpSigner.REGION_SET) != null) + && !CollectionUtils.isNullOrEmpty(v4aAuthScheme.signingRegionSet())) { + RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); + option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); + } + if (v4aAuthScheme.signingName() != null) { + option.putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, v4aAuthScheme.signingName()); + } + return new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), option.build()); + } + throw new IllegalArgumentException("Endpoint auth scheme '" + endpointAuthScheme.name() + + "' cannot be mapped to the SDK auth scheme. Was it declared in the service's model?"); + } + return selectedAuthScheme; + } + + private static void setOperationContextParams(DatabaseEndpointParams.Builder params, String operationName, SdkRequest request) { + } + + private static Optional hostPrefix(String operationName, SdkRequest request) { + return Optional.empty(); + } + +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java index 138b2cf5fe07..1255cc65c777 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java @@ -41,6 +41,7 @@ import software.amazon.awssdk.services.query.model.OperationWithMapOperationContextParamRequest; import software.amazon.awssdk.services.query.model.OperationWithOperationContextParamRequest; import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; @Generated("software.amazon.awssdk:codegen") @@ -56,7 +57,8 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut .getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); try { long resolveEndpointStart = System.nanoTime(); - Endpoint endpoint = provider.resolveEndpoint(ruleParams(result, executionAttributes)).join(); + QueryEndpointParams endpointParams = ruleParams(result, executionAttributes); + Endpoint endpoint = provider.resolveEndpoint(endpointParams).join(); Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart); Optional metricCollector = executionAttributes .getOptionalAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); @@ -167,7 +169,7 @@ private SelectedAuthScheme authSchemeWithEndpointSignerP if (v4aAuthScheme.isDisableDoubleEncodingSet()) { option.putSignerProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, !v4aAuthScheme.disableDoubleEncoding()); } - if (v4aAuthScheme.signingRegionSet() != null) { + if (!CollectionUtils.isNullOrEmpty(v4aAuthScheme.signingRegionSet())) { RegionSet regionSet = RegionSet.create(v4aAuthScheme.signingRegionSet()); option.putSignerProperty(AwsV4aHttpSigner.REGION_SET, regionSet); } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java index 12f6542043b6..3def22f782fd 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.awscore; +import java.util.Set; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.awscore.client.config.AwsClientOption; @@ -46,6 +47,12 @@ public final class AwsExecutionAttribute extends SdkExecutionAttribute { public static final ExecutionAttribute DUALSTACK_ENDPOINT_ENABLED = new ExecutionAttribute<>("DualstackEndpointsEnabled"); + /** + * AWS Sigv4a signing region set used for computing multi-region request signatures. + */ + public static final ExecutionAttribute> AWS_SIGV4A_SIGNING_REGION_SET = + new ExecutionAttribute<>("AwsSigv4aSigningRegionSet"); + /** * Whether fips endpoints were enabled for this request. */ diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java index 0b4f28050210..6f392f6f7324 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; @@ -39,6 +40,7 @@ import software.amazon.awssdk.awscore.eventstream.EventStreamInitialRequestInterceptor; import software.amazon.awssdk.awscore.interceptor.HelpfulUnknownHostExceptionInterceptor; import software.amazon.awssdk.awscore.interceptor.TraceIdExecutionInterceptor; +import software.amazon.awssdk.awscore.internal.auth.Sigv4aSigningRegionSetProvider; import software.amazon.awssdk.awscore.internal.defaultsmode.AutoDefaultsModeDiscovery; import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration; import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeResolver; @@ -194,6 +196,8 @@ private SdkClientConfiguration finalizeAwsConfiguration(SdkClientConfiguration c .applyMutation(this::configureRetryPolicy) .applyMutation(this::configureRetryStrategy) .lazyOptionIfAbsent(SdkClientOption.IDENTITY_PROVIDERS, this::resolveIdentityProviders) + .lazyOptionIfAbsent(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET, + this::resolveSigv4aSigningRegionSet) .build(); } @@ -384,6 +388,16 @@ private Boolean resolveDualstackEndpointEnabled(LazyValueSource config) { .orElse(null); } + private Set resolveSigv4aSigningRegionSet(LazyValueSource config) { + Supplier profileFile = config.get(SdkClientOption.PROFILE_FILE_SUPPLIER); + String profileName = config.get(SdkClientOption.PROFILE_NAME); + return Sigv4aSigningRegionSetProvider.builder() + .profileFile(profileFile) + .profileName(profileName) + .build() + .resolveRegionSet(); + } + /** * Resolve whether a fips endpoint should be used for this client. */ diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java index 3ab3798c4659..a2e860da314a 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.awscore.client.config; +import java.util.Set; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider; @@ -63,6 +64,13 @@ public final class AwsClientOption extends ClientOption { */ public static final AwsClientOption DUALSTACK_ENDPOINT_ENABLED = new AwsClientOption<>(Boolean.class); + + /** + * AWS Sigv4a signing region set used for computing multi-region request signatures. + */ + public static final AwsClientOption> AWS_SIGV4A_SIGNING_REGION_SET = + new AwsClientOption<>(new UnsafeValueType(Set.class)); + /** * Whether the SDK should resolve fips endpoints instead of default endpoints. See * {@link AwsClientBuilder#fipsEnabled(Boolean)}. diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index 2e24e1b64101..63bbcaa6cdb0 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -126,7 +126,9 @@ private AwsExecutionContextBuilder() { clientConfig.option(SdkClientOption.REQUEST_CHECKSUM_CALCULATION)) .putAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION, clientConfig.option(SdkClientOption.RESPONSE_CHECKSUM_VALIDATION)) - .putAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS, resolveUserAgentBusinessMetrics(clientConfig)); + .putAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS, resolveUserAgentBusinessMetrics(clientConfig)) + .putAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET, + clientConfig.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET)); // Auth Scheme resolution related attributes putAuthSchemeResolutionAttributes(executionAttributes, clientConfig, originalRequest); diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProvider.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProvider.java new file mode 100644 index 000000000000..ccb186a04bdf --- /dev/null +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProvider.java @@ -0,0 +1,112 @@ +/* + * 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.awscore.internal.auth; + + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.profiles.Profile; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSystemSetting; +import software.amazon.awssdk.profiles.ProfileProperty; +import software.amazon.awssdk.utils.Lazy; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.awssdk.utils.Validate; + +@SdkInternalApi +public final class Sigv4aSigningRegionSetProvider { + private final Supplier profileFile; + private final String profileName; + private final Lazy> regionSet; + + private Sigv4aSigningRegionSetProvider(Builder builder) { + this.profileFile = Validate.paramNotNull(builder.profileFile, "profileFile"); + this.profileName = builder.profileName; + this.regionSet = new Lazy<>(this::regionSet); + } + + public static Builder builder() { + return new Builder(); + } + + public Set resolveRegionSet() { + return this.regionSet.getValue(); + } + + /** + * Resolves the SIGV4A signing region set configuration. + * Returns a Set of non-empty strings if the configuration is set, or null if not configured + */ + private Set regionSet() { + Optional setting = SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.getStringValue(); + if (setting.isPresent()) { + return parseRegionSet(setting.get()); + } + ProfileFile file = this.profileFile.get(); + Optional profile = file.profile(profileName()); + if (profile.isPresent()) { + Optional property = profile.get().property(ProfileProperty.SIGV4A_SIGNING_REGION_SET); + if (property.isPresent()) { + return parseRegionSet(property.get()); + } + } + return Collections.emptySet(); + } + + private Set parseRegionSet(String value) { + if (StringUtils.isBlank(value)) { + return Collections.emptySet(); + } + + return Arrays.stream(value.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); + } + + + private String profileName() { + return profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); + } + + public static final class Builder { + private Supplier profileFile = ProfileFile::defaultProfileFile; + private String profileName; + + private Builder() { + } + + public Builder profileFile(Supplier profileFile) { + this.profileFile = profileFile; + return this; + } + + public Builder profileName(String profileName) { + this.profileName = profileName; + return this; + } + + public Sigv4aSigningRegionSetProvider build() { + return new Sigv4aSigningRegionSetProvider(this); + } + } +} \ No newline at end of file diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProviderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProviderTest.java new file mode 100644 index 000000000000..fd3c81e54536 --- /dev/null +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/auth/Sigv4aSigningRegionSetProviderTest.java @@ -0,0 +1,191 @@ +/* + * 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.awscore.internal.auth; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileProperty; + +import java.util.Set; +import java.util.stream.Stream; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.StringInputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class Sigv4aSigningRegionSetProviderTest { + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + private static final String PROFILE = "test"; + + @BeforeEach + public void setup() { + ENVIRONMENT_VARIABLE_HELPER.reset(); + System.clearProperty(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property()); + } + + @DisplayName("Region Set Resolution Tests") + @ParameterizedTest(name = "{4}") + @MethodSource("configValues") + void resolveRegionSet_whenValidValues_resolvesCorrectly( + String systemProperty, String envVar, ProfileFile profileFile, Set expected, String testDescription) { + setUpSystemSettings(systemProperty, envVar); + + Sigv4aSigningRegionSetProvider provider = Sigv4aSigningRegionSetProvider.builder() + .profileFile(() -> profileFile) + .profileName(PROFILE) + .build(); + + assertThat(provider.resolveRegionSet()).isEqualTo(expected); + } + + private static Stream configValues() { + return Stream.of( + Arguments.of(null, null, emptyProfile(), Collections.emptySet(), + "No values set anywhere"), + + Arguments.of(",", + null, + null, + Collections.emptySet(), + "Empty comma separated values"), + + Arguments.of("us-west-2", + null, + null, + Collections.singleton("us-west-2"), + "System Property value takes precedence"), + + Arguments.of(null, + "us-west-2", + null, + Collections.singleton("us-west-2"), + "Environment used when System Property null"), + + Arguments.of(null, + null, + configWithRegion("us-west-2"), + Collections.singleton("us-west-2"), + "Config file used when others null"), + + Arguments.of("us-west-2", + "us-east-1", + null, + Collections.singleton("us-west-2"), + "System Property overrides Environment"), + + Arguments.of("us-west-2", + null, + configWithRegion("us-east-1"), + Collections.singleton("us-west-2"), + "System Property overrides Config File"), + + Arguments.of(null, + "us-west-2", + configWithRegion("us-east-1"), + Collections.singleton("us-west-2"), + "Environment overrides Config File"), + + Arguments.of("us-west-2", + "us-east-1", + configWithRegion("us-north-1"), + Collections.singleton("us-west-2"), + "System Property highest precedence"), + + Arguments.of("*", + "us-west-2", + null, + Collections.singleton("*"), + "Wildcard in System Property overrides specific value"), + + Arguments.of("us-west-2", + "*", + null, + Collections.singleton("us-west-2"), + "Specific Environment overrides wildcard"), + + Arguments.of(null, + "*", + configWithRegion("us-west-2"), + Collections.singleton("*"), + "Wildcard in Environment overrides Config"), + + Arguments.of("us-west-1,us-east-1", + "us-west-2", + null, + createSet("us-west-1", "us-east-1"), + "Multi-region System Property overrides single"), + + Arguments.of("us-west-1,us-east-1", + null, + configWithRegion("us-west-2"), + createSet("us-west-1", "us-east-1"), + "Multi-region System Property overrides Config"), + + Arguments.of(null, + "us-west-1,us-east-1", + configWithRegion("us-west-2"), + createSet("us-west-1", "us-east-1"), + "Multi-region Environment overrides Config") + ); + } + + private static void setUpSystemSettings(String systemProperty, String envVar) { + if (systemProperty != null) { + System.setProperty( + SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property(), systemProperty); + } + if (envVar != null) { + ENVIRONMENT_VARIABLE_HELPER.set( + SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.environmentVariable(), envVar); + } + } + + private static ProfileFile emptyProfile() { + return configFile("profile test", Pair.of("foo", "bar")); + } + + private static ProfileFile configWithRegion(String region) { + return configFile("profile test", + Pair.of(ProfileProperty.SIGV4A_SIGNING_REGION_SET, region)); + } + + private static ProfileFile configFile(String profileName, Pair... pairs) { + String values = Arrays.stream(pairs) + .map(pair -> String.format("%s=%s", pair.left(), pair.right())) + .collect(Collectors.joining(System.lineSeparator())); + String contents = String.format("[%s]%n%s", profileName, values); + + return ProfileFile.builder() + .content(new StringInputStream(contents)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + } + + private static Set createSet(String... values) { + Set set = new HashSet<>(); + Collections.addAll(set, values); + return set; + } +} diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java index f27c9f8949c4..434e27b3b6f2 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java @@ -191,6 +191,14 @@ public final class ProfileProperty { public static final String SDK_UA_APP_ID = "sdk_ua_app_id"; + /** + * Property name for specifying the SIGV4A signing region set configuration. + * This optional property is a non-empty, comma-delimited list of non-empty strings, which can be configured + * via the environment variable {@code AWS_SIGV4A_SIGNING_REGION_SET}, + * or the configuration file property {@code sigv4a_signing_region_set}, following standard precedence rules. + */ + public static final String SIGV4A_SIGNING_REGION_SET = "sigv4a_signing_region_set"; + private ProfileProperty() { } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java index 58a5df2fd7aa..f55eb73cbc7f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java @@ -250,8 +250,13 @@ public enum SdkSystemSetting implements SystemSetting { * Configure an optional identification value to be appended to the user agent header. * The value should be less than 50 characters in length and is null by default. */ - AWS_SDK_UA_APP_ID("sdk.ua.appId", null) + AWS_SDK_UA_APP_ID("sdk.ua.appId", null), + /** + * Configure the SIGV4A signing region set. + * This is a non-empty, comma-delimited list of AWS region names used during signing. + */ + AWS_SIGV4A_SIGNING_REGION_SET("aws.sigv4a.signing.region.set", null) ; private final String systemProperty; diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/multiauthsigv4a/Sigv4aSigningRegionSetTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/multiauthsigv4a/Sigv4aSigningRegionSetTest.java new file mode 100644 index 000000000000..2c373e7bcca1 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/multiauthsigv4a/Sigv4aSigningRegionSetTest.java @@ -0,0 +1,251 @@ +/* + * 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.services.s3.multiauthsigv4a; + + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +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.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.endpoints.S3EndpointParams; +import software.amazon.awssdk.services.s3.endpoints.S3EndpointProvider; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +@WireMockTest +@DisplayName("S3 SigV4a Signing Region Set Tests") +class Sigv4aSigningRegionSetTest { + private static final String EXAMPLE_BUCKET = "Example-Bucket"; + private static final String EXAMPLE_RESPONSE_BODY = "Hello world"; + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + public static final Function stringFromStream = inputStream -> + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + + private final MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + private final MockAsyncHttpClient mockAsyncHttpClient = new MockAsyncHttpClient(); + + @AfterEach + void cleanUp() { + ENVIRONMENT_VARIABLE_HELPER.reset(); + } + + private static Stream regionSetConfigurations() { + return Stream.of( + Arguments.of("ClientBuilder Configuration", Region.EU_NORTH_1, "eu-north-1", "CLIENT_BUILDER"), + Arguments.of("Environment Variable", Region.EU_NORTH_1, "eu-north-1", "ENVIRONMENT"), + Arguments.of("Endpoint Override", Region.US_EAST_1, "us-*", "ENDPOINT_OVERRIDE"), + Arguments.of("Client Region", Region.EU_CENTRAL_1, "eu-central-1", "CLIENT_REGION") + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("regionSetConfigurations") + @DisplayName("Async GetObject with different region set configurations") + void asyncGetObjectWithDifferentRegionSetConfigurations(String testName, Region region, + String expectedRegionSet, String configType, + WireMockRuntimeInfo wm) { + + RegionSet regionSet = RegionSet.create(expectedRegionSet); + setupMockResponses(); + + S3AsyncClient s3Client = buildAsyncClientWithConfiguration(wm, region, regionSet, configType); + + s3Client.getObject(r -> r.bucket(EXAMPLE_BUCKET).key("key"), + AsyncResponseTransformer.toBytes()).join().asUtf8String(); + + verifyHeaders(mockAsyncHttpClient.getLastRequest().headers(), expectedRegionSet); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("regionSetConfigurations") + @DisplayName("Sync GetObject with different region set configurations") + void syncGetObjectWithDifferentRegionSetConfigurations(String testName, Region region, + String expectedRegionSet, String configType, + WireMockRuntimeInfo wm) { + RegionSet regionSet = RegionSet.create(expectedRegionSet); + setupMockResponses(); + + S3Client s3Client = buildSyncClientWithConfiguration(wm, region, regionSet, configType); + + s3Client.getObject(r -> r.bucket(EXAMPLE_BUCKET).key("key"), + ResponseTransformer.toBytes()).asUtf8String(); + + verifyHeaders(mockHttpClient.getLastRequest().headers(), expectedRegionSet); + } + + private void setupMockResponses() { + stubFor(any(anyUrl()).willReturn(aResponse() + .withStatus(200) + .withBody(EXAMPLE_RESPONSE_BODY))); + + HttpExecuteResponse mockResponse = HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .build(); + + mockHttpClient.stubNextResponse(mockResponse); + mockAsyncHttpClient.stubNextResponse(mockResponse); + } + + private S3AsyncClient buildAsyncClientWithConfiguration(WireMockRuntimeInfo wm, Region region, + RegionSet regionSet, String configType) { + S3AsyncClientBuilder builder = getAsyncClientBuilder(wm) + .httpClient(mockAsyncHttpClient); + + switch (configType) { + case "CLIENT_BUILDER": + builder.sigv4aSigningRegionSet(regionSet); + break; + case "ENVIRONMENT": + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, regionSet.asString()); + break; + case "CLIENT_REGION": + builder.region(region); + break; + } + + builder.endpointProvider(createEndpointProvider(configType)); + return builder.build(); + } + + private S3Client buildSyncClientWithConfiguration(WireMockRuntimeInfo wm, Region region, + RegionSet regionSet, String configType) { + S3ClientBuilder builder = getSyncClientBuilder(wm) + .httpClient(mockHttpClient); + + switch (configType) { + case "CLIENT_BUILDER": + builder.sigv4aSigningRegionSet(regionSet); + break; + case "ENVIRONMENT": + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, regionSet.asString()); + break; + case "CLIENT_REGION": + builder.region(region); + break; + } + + builder.endpointProvider(createEndpointProvider(configType)); + return builder.build(); + } + + private S3EndpointProvider createEndpointProvider(String configType) { + return new S3EndpointProvider() { + @Override + public CompletableFuture resolveEndpoint(S3EndpointParams endpointParams) { + return CompletableFuture.completedFuture( + configType.equals("CLIENT_REGION") ? + createClientRegionEndpoint() : + customEndPointForBucket(EXAMPLE_BUCKET) + ); + } + }; + } + + private void verifyHeaders(Map> headers, String expectedRegionSet) { + assertThat(headers.get("X-Amz-Region-Set").get(0)).isEqualTo(expectedRegionSet); + assertThat(headers.get("Authorization").get(0)).contains("AWS4-ECDSA-P256-SHA256"); + } + + private S3ClientBuilder getSyncClientBuilder(WireMockRuntimeInfo wm) { + return S3Client.builder() + .region(Region.US_EAST_1) + .endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("key", "secret"))); + } + + private S3AsyncClientBuilder getAsyncClientBuilder(WireMockRuntimeInfo wm) { + return S3AsyncClient.builder() + .region(Region.US_EAST_1) + .endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("key", "secret"))); + } + + private static Endpoint customEndPointForBucket(String bucketName) { + return Endpoint.builder() + .url(URI.create("https://" + bucketName + ".s3.us-east-1.amazonaws.com")) + .putAttribute( + AwsEndpointAttribute.AUTH_SCHEMES, + Arrays.asList( + SigV4aAuthScheme.builder() + .disableDoubleEncoding(true) + .signingName("s3") + .signingRegionSet(Collections.singletonList("us-*")) + .build(), + SigV4AuthScheme.builder() + .disableDoubleEncoding(true) + .signingName("s3") + .signingRegion("us-east-1") + .build())) + .build(); + } + + private static Endpoint createClientRegionEndpoint() { + return Endpoint.builder() + .url(URI.create("https://" + EXAMPLE_BUCKET + ".s3.us-east-1.amazonaws.com")) + .putAttribute( + AwsEndpointAttribute.AUTH_SCHEMES, + Collections.singletonList( + SigV4aAuthScheme.builder() + .disableDoubleEncoding(true) + .signingName("s3") + .build())) + .build(); + } +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/customization.config new file mode 100644 index 000000000000..4d8af1600396 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/customization.config @@ -0,0 +1,4 @@ +{ + "skipEndpointTestGeneration": true, + "enableEndpointAuthSchemeParams": true +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-rule-set.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-rule-set.json new file mode 100644 index 000000000000..a084d28daf07 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-rule-set.json @@ -0,0 +1,229 @@ + { + "version": "1.3", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": true, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + }, + "UseDualStack": { + "builtIn": "AWS::UseDualStack", + "required": true, + "default": false, + "documentation": "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", + "type": "Boolean" + }, + "UseFIPS": { + "builtIn": "AWS::UseFIPS", + "required": true, + "default": false, + "documentation": "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", + "type": "Boolean" + }, + "Endpoint": { + "builtIn": "SDK::Endpoint", + "required": false, + "documentation": "Override the endpoint used to send this request", + "type": "String" + }, + "ApiType": { + "required": true, + "documentation": "Parameter to determine whether current API is a control plane or data plane API", + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "AllSigv4aAuthPropertiesPresent" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "disableDoubleEncoding": true, + "name": "sigv4a", + "signingName": "sigv4afromruleset", + "signingRegionSet": [ + "us-seattle","us-west-2" + ] + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "RegionsetAbsentInSigv4aPropertiesInEndpointRules" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "name": "sigv4a", + "signingName": "sigv4afromruleset2" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "Region" + } + ], + "assign": "PartitionResult" + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "NoSigv4aPropertiesInEndpointRules" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + }, + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + }, + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson-fips.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "FIPS and DualStack are enabled, but this partition does not support one or both", + "type": "error" + } + ] + }, + { + "conditions": [], + "endpoint": { + "url": "https://restjson.{Region}.{PartitionResult#dnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "fromrulesetdefault" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-tests.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-tests.json new file mode 100644 index 000000000000..f94902ff9d99 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/endpoint-tests.json @@ -0,0 +1,5 @@ +{ + "testCases": [ + ], + "version": "1.0" +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/service-2.json new file mode 100644 index 000000000000..d0b44c0b303e --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/endpointbasedauth/service-2.json @@ -0,0 +1,68 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2016-03-11", + "endpointPrefix":"fromruleset", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AwsEndpointAuthService", + "serviceFullName":"AWS Endpoint Auth Service", + "serviceId":"EndpointAuth", + "signatureVersion":"v4", + "targetPrefix":"EndpointAuth", + "timestampFormat":"unixTimestamp", + "uid":"restjson-2016-03-11" + }, + "operations":{ + "allAuthPropertiesInEndpointRules":{ + "name":"allSigv4aAuthPropertiesPresent", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/allSigv4aAuthPropertiesPresent" + }, + "input":{"shape":"sampleRequest"}, + "staticContextParams":{ + "ApiType":{"value":"AllSigv4aAuthPropertiesPresent"} + } + }, + "regionsetAbsentInSigv4aPropertiesInEndpointRules":{ + "name":"regionsetAbsentInSigv4aPropertiesInEndpointRules", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/regionsetAbsentInSigv4aPropertiesInEndpointRules" + }, + "input":{"shape":"sampleRequest"}, + "staticContextParams":{ + "ApiType":{"value":"RegionsetAbsentInSigv4aPropertiesInEndpointRules"} + } + }, + "noSigv4aPropertiesInEndpointRules": { + "name": "noSigv4aPropertiesInEndpointRules", + "http": { + "method": "GET", + "requestUri": "/noSigv4aPropertiesInEndpointRules", + "responseCode": 200 + }, + "input": { + "shape": "sampleRequest" + }, + "documentation": "

Operation with EndpointParams w

", + "staticContextParams": { + "ApiType": { + "value": "NoSigv4aPropertiesInEndpointRules" + } + } + } + }, + "shapes": { + "sampleRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String":{"type":"string"} + } +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config new file mode 100644 index 000000000000..0239c220d9fc --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config @@ -0,0 +1,4 @@ +{ + "skipEndpointTestGeneration": true + +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json new file mode 100644 index 000000000000..80a2e9c7e0ba --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json @@ -0,0 +1,384 @@ +{ + "version": "1.3", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": true, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + }, + "UseDualStack": { + "builtIn": "AWS::UseDualStack", + "required": true, + "default": false, + "documentation": "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", + "type": "Boolean" + }, + "UseFIPS": { + "builtIn": "AWS::UseFIPS", + "required": true, + "default": false, + "documentation": "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", + "type": "Boolean" + }, + "Endpoint": { + "builtIn": "SDK::Endpoint", + "required": false, + "documentation": "Override the endpoint used to send this request", + "type": "String" + }, + "StaticStringParam": { + "type": "String", + "required": false + }, + "OperationContextParam": { + "type": "String", + "required": false + }, + "RegionWithDefault": { + "type": "String", + "required": true, + "default": "us-east-1", + "builtIn": "AWS::Region" + }, + "BooleanClientContextParam": { + "type": "Boolean" + }, + "StringClientContextParam": { + "type": "String" + }, + "ApiType": { + "required": true, + "documentation": "Parameter to determine whether current API is a control plane or dataplane API", + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "onlySigv4a" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "name": "sigv4a", + "signingName": "from-endpoint-params", + "signingRegionSet": [ + "*" + ] + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "RegionDefinedInRules" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingName": "greengrass", + "signingRegion": "us-gov-east-1" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "Region" + } + ], + "assign": "PartitionResult" + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "OnlyRegion" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingName": "onlyRegion", + "signingRegion": "us-east-2" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + }, + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + }, + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson-fips.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "FIPS and DualStack are enabled, but this partition does not support one or both", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson-fips.{Region}.{PartitionResult#dnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] + }, + { + "conditions": [], + "error": "FIPS is enabled but this partition does not support FIPS", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "DualStack is enabled but this partition does not support DualStack", + "type": "error" + } + ] + }, + { + "conditions": [], + "endpoint": { + "url": "https://restjson.{Region}.{PartitionResult#dnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json new file mode 100644 index 000000000000..f94902ff9d99 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json @@ -0,0 +1,5 @@ +{ + "testCases": [ + ], + "version": "1.0" +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json new file mode 100644 index 000000000000..1e467bbc8c40 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json @@ -0,0 +1,88 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2016-03-11", + "endpointPrefix":"internalconfig", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AwsMultiAuthService", + "serviceFullName":"AWS Multi Auth Service", + "serviceId":"Multiauth", + "signatureVersion":"v4", + "targetPrefix":"MultiAuth", + "timestampFormat":"unixTimestamp", + "uid":"restjson-2016-03-11" + }, + "operations":{ + "multiAuthWithOnlySigv4a":{ + "name":"multiAuthWithOnlySigv4a", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/multiAuthWithOnlySigv4a" + }, + "input":{"shape":"sampleRequest"}, + "auth": ["aws.auth#sigv4a"], + "staticContextParams":{ + "ApiType":{"value":"NoEndpointSigningProperties"} + } + }, + "multiAuthWithOnlySigv4aAndSigv4":{ + "name":"multiAuthWithOnlySigv4aAndSigv4", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/multiAuthWithOnlySigv4aAndSigv4" + }, + "input":{"shape":"sampleRequest"}, + "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"], + "staticContextParams":{ + "ApiType":{"value":"NoEndpointSigningProperties"} + } + }, + "legacySigv4WithEndpointsRules": { + "name": "legacySigv4WithEndpointsRules", + "http": { + "method": "GET", + "requestUri": "/legacySigv4WithEndpointsRules", + "responseCode": 200 + }, + "input": { + "shape": "sampleRequest" + }, + "documentation": "

Operation with EndpointParams w

", + "staticContextParams": { + "ApiType": { + "value": "RegionDefinedInRules" + } + } + }, + "multiAuthWithRegionSetInEndpointParams": { + "name": "multiAuthWithRegionSetInEndpointParams", + "http": { + "method": "GET", + "requestUri": "/multiAuthWithRegionSetInEndpointParams", + "responseCode": 200 + }, + "input": { + "shape": "sampleRequest" + }, + "documentation": "

Operation with EndpointParams w

", + "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"], + "staticContextParams": { + "ApiType": { + "value": "onlySigv4a" + } + } + } + }, + "shapes": { + "sampleRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String":{"type":"string"} + } +} diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/customization.config new file mode 100644 index 000000000000..b5c73436bb3f --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/customization.config @@ -0,0 +1,3 @@ +{ + "skipEndpointTestGeneration": true +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-rule-set.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-rule-set.json new file mode 100644 index 000000000000..d308f4496a61 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-rule-set.json @@ -0,0 +1,69 @@ +{ + "version": "1.3", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": true, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + }, + "ApiType": { + "required": true, + "documentation": "Parameter to determine whether current API is a control plane or dataplane API", + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "onlySigv4a" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + { + "name": "sigv4a", + "signingName": "from-endpoint-params", + "signingRegionSet": [ + "*" + ] + } + ] + }, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "ref": "ApiType" + }, + "NoEndpointSigningProperties" + ] + } + ], + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-tests.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-tests.json new file mode 100644 index 000000000000..f94902ff9d99 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/endpoint-tests.json @@ -0,0 +1,5 @@ +{ + "testCases": [ + ], + "version": "1.0" +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/service-2.json new file mode 100644 index 000000000000..937541733c07 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/sigv4aonly/service-2.json @@ -0,0 +1,51 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2016-03-11", + "endpointPrefix":"internalconfig", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AwsSigv4aMultiAuthService", + "serviceFullName":"AWS Multi Auth Service", + "serviceId":"Sigv4aauth", + "targetPrefix":"Sigv4aauth", + "auth":["aws.auth#sigv4a"], + "timestampFormat":"unixTimestamp", + "uid":"restjson-2016-03-11" + }, + "operations":{ + "simpleOperationWithNoEndpointParams":{ + "name":"simpleOperationWithNoEndpointParams", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/simpleOperationWithNoEndpointParams" + }, + "input":{"shape":"sampleRequest"}, + "staticContextParams":{ + "ApiType":{"value":"NoEndpointSigningProperties"} + } + }, + "simpleOperationWithEndpointParams":{ + "name":"simpleOperationWithEndpointParams", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/multiAuthWithOnlySigv4aAndSigv4" + }, + "input":{"shape":"sampleRequest"}, + "staticContextParams":{ + "ApiType":{"value":"onlySigv4a"} + } + } + }, + "shapes": { + "sampleRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String":{"type":"string"} + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java new file mode 100644 index 000000000000..cdf082c52bc2 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java @@ -0,0 +1,273 @@ +/* + * 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.services.auth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileProperty; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.multiauth.MultiauthClient; +import software.amazon.awssdk.services.multiauth.MultiauthClientBuilder; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.StringInputStream; + +class Sigv4aSigningRegionSetTest { + + private final EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); + + static Stream testCases() { + return Stream.of( + Arguments.of(new SuccessCase( + null, + null, + null, + null, + setOf(), + "No values set anywhere")), + + Arguments.of(new SuccessCase( + null, + null, + null, + "us-west-2", + setOf("us-west-2"), + "System Property value takes precedence")), + + Arguments.of(new SuccessCase( + null, + "us-west-2", + null, + null, + setOf("us-west-2"), + "Environment used when System Property null")), + + Arguments.of(new SuccessCase( + null, + null, + "us-west-2", + null, + setOf("us-west-2"), + "Config file used when others null")), + + Arguments.of(new SuccessCase( + null, + "us-east-1", + "us-west-2", + "us-west-1", + setOf("us-west-1"), + "System Property overrides Environment")), + + Arguments.of(new SuccessCase( + null, + null, + "us-east-1", + "us-west-2", + setOf("us-west-2"), + "System Property overrides Config File")), + + Arguments.of(new SuccessCase( + null, + "us-west-2", + "us-east-1", + null, + setOf("us-west-2"), + "Environment overrides Config File")), + + Arguments.of(new SuccessCase( + null, + "us-east-1", + "us-west-2", + "us-west-1", + setOf("us-west-1"), + "SystemProperty highest precedence")), + + Arguments.of(new SuccessCase( + null, + null, + null, + "*", + setOf("*"), + "Wildcard in System Property overrides specific value")), + + Arguments.of(new SuccessCase( + null, + "*", + null, + null, + setOf("*"), + "Specific Environment overrides wildcard")), + + Arguments.of(new SuccessCase( + null, + "*", + "us-west-2", + null, + setOf("*"), + "Wildcard in Environment overrides Config")), + + Arguments.of(new SuccessCase( + null, + null, + "us-west-2,us-east-1", + "us-west-1,us-east-2", + setOf("us-west-1", "us-east-2"), + "Multi-region System Property overrides Config")), + + Arguments.of(new SuccessCase( + RegionSet.GLOBAL, + "us-west-2,us-east-1", + "us-west-4", + "us-west-5", + setOf("*"), + "sigv4aSigningRegionSet set to GLOBAL value, takes highest precedence")), + + Arguments.of(new SuccessCase( + RegionSet.create("us-west-3"), + "us-west-2,us-east-1", + "us-west-4", + "us-west-5", + setOf("us-west-3"), + "sigv4aSigningRegionSet set to different value, takes highest precedence")) + ); + } + + + private static Set setOf(String... s) { + return new HashSet<>(Arrays.asList(s)); + } + + @AfterEach + void tearDown() { + System.clearProperty(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property()); + helper.reset(); + } + + @ParameterizedTest + @MethodSource("testCases") + void resolvesSigv4aSigningRegionSet(TestCase testCase) { + try { + MultiauthClientBuilder builder = + MultiauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()); + if (testCase.regionSet != null) { + builder.sigv4aSigningRegionSet(testCase.regionSet); + } + if (testCase.systemPropSetting != null) { + System.setProperty(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property(), testCase.systemPropSetting); + } + if (testCase.envVarSetting != null) { + helper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.environmentVariable(), testCase.envVarSetting); + } + ProfileFile.Builder profileFile = ProfileFile.builder().type(ProfileFile.Type.CONFIGURATION); + + if (testCase.profileSetting != null) { + profileFile.content(new StringInputStream("[default]\n" + + ProfileProperty.SIGV4A_SIGNING_REGION_SET + " = " + testCase.profileSetting)); + } else { + profileFile.content(new StringInputStream("")); + } + + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + + builder.overrideConfiguration(c -> c.defaultProfileFile(profileFile.build()) + .defaultProfileName("default") + .addExecutionInterceptor(interceptor)); + + MultiauthClient client = builder.build(); + + assertThatExceptionOfType(CaptureCompletedException.class) + .isThrownBy(() -> client.multiAuthWithOnlySigv4aAndSigv4(b -> b.stringMember("test").build())); + + assertThat(interceptor.sigv4aSigningRegionSet()) + .containsExactlyInAnyOrderElementsOf(testCase.expectedValues); + + } finally { + tearDown(); + } + } + + public static class TestCase { + private final RegionSet regionSet; + private final String envVarSetting; + private final String profileSetting; + private final String systemPropSetting; + private final Set expectedValues; + private final String caseName; + + public TestCase(RegionSet regionSet, String envVarSetting, String profileSetting, String systemPropSetting, Set expectedValues, + String caseName) { + this.regionSet = regionSet; + this.envVarSetting = envVarSetting; + this.profileSetting = profileSetting; + this.systemPropSetting = systemPropSetting; + this.expectedValues = expectedValues; + this.caseName = caseName; + } + + @Override + public String toString() { + return caseName; + } + } + + public static class SuccessCase extends TestCase { + public SuccessCase(RegionSet regionSet, String envVarSetting, String profileSetting, String systemPropSetting, Set expectedValues, + String caseName) { + super(regionSet, envVarSetting, profileSetting, systemPropSetting, expectedValues, caseName); + } + } + + public static class EndpointCapturingInterceptor implements ExecutionInterceptor { + private Set sigv4aSigningRegionSet = Collections.emptySet(); + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + sigv4aSigningRegionSet = + executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .orElse(Collections.emptySet()); + throw new CaptureCompletedException(); + } + + public Set sigv4aSigningRegionSet() { + return Collections.unmodifiableSet(sigv4aSigningRegionSet); + } + + public void reset() { + sigv4aSigningRegionSet = Collections.emptySet(); + } + } + public static class CaptureCompletedException extends RuntimeException { + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointauth/EndpointAuthSigningPropertiesTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointauth/EndpointAuthSigningPropertiesTest.java new file mode 100644 index 000000000000..4f17629b4c2a --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointauth/EndpointAuthSigningPropertiesTest.java @@ -0,0 +1,218 @@ +/* + * 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.services.endpointauth; + + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +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.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +@DisplayName("Endpoint-Auth Tests") +class EndpointAuthSigningPropertiesTest { + + private static final String MOCK_HTTP_CLIENT_NAME = "MockHttpClient"; + private static final String EXPECTED_EXCEPTION_MESSAGE = "expected exception"; + + private static final Region TEST_REGION = Region.US_WEST_2; + private static final String MULTI_REGION_SET = "us-west-2,us-west-1"; + + @Mock + private SdkHttpClient mockHttpClient; + private final EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + configureMockHttpClient(); + } + + @AfterEach + void tearDown() { + environmentVariableHelper.reset(); + } + + @Nested + @DisplayName("Region Set Configuration Tests") + class RegionSetConfigurationTests { + private CapturingSigner signer; + private EndpointAuthClient client; + + @BeforeEach + void setUp() { + signer = new CapturingSigner(); + } + + @Test + @DisplayName("Should not use region set for non-sigv4a operations") + void shouldNotUseRegionSetForNonSigv4aOperations() { + + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, MULTI_REGION_SET); + client = createEndpointAuthClient() + .putAuthScheme(authScheme("aws.auth#sigv4", signer)) + .build(); + assertAll( + () -> assertThatThrownBy(() -> + client.noSigv4aPropertiesInEndpointRules(r -> r.stringMember(""))) + .hasMessageContaining("stop"), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isNull(), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)) + .isEqualTo("fromruleset") + ); + } + + @Test + @DisplayName("Should fall back to client region when no environment variable exists") + void shouldFallBackToClientRegion() { + + client = createEndpointAuthClient() + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .build(); + assertAll( + () -> assertThatThrownBy(() -> + client.regionsetAbsentInSigv4aPropertiesInEndpointRules(r -> r.stringMember(""))) + .hasMessageContaining("stop"), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create(TEST_REGION.toString())), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)) + .isEqualTo("sigv4afromruleset2") + ); + } + + @Test + @DisplayName("Should use client region set despite endpoint signing properties") + void clientConfiguredRegionSetTakesPrecedenceOverEndpointRegionSet() { + + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, MULTI_REGION_SET); + client = createEndpointAuthClient() + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .build(); + + assertAll( + () -> assertThatThrownBy(() -> + client.allAuthPropertiesInEndpointRules(r -> r.stringMember(""))) + .hasMessageContaining("stop"), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create(MULTI_REGION_SET)), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)) + .isEqualTo("sigv4afromruleset") + ); + } + + @Test + @DisplayName("Environment variable config should take precedence over endpoint rules") + void environmentVariableRegionSetTakesPrecedenceOverEndpointRegionSet() { + + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, MULTI_REGION_SET); + client = createEndpointAuthClient() + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .build(); + + + assertAll( + () -> assertThatThrownBy(() -> + client.regionsetAbsentInSigv4aPropertiesInEndpointRules(r -> r.stringMember(""))) + .hasMessageContaining("stop"), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create("us-west-1, us-west-2")), + () -> assertThat(signer.request.property(AwsV4aHttpSigner.SERVICE_SIGNING_NAME)) + .isEqualTo("sigv4afromruleset2") + ); + } + } + + + private void configureMockHttpClient() { + when(mockHttpClient.clientName()).thenReturn(MOCK_HTTP_CLIENT_NAME); + when(mockHttpClient.prepareRequest(any())) + .thenThrow(new RuntimeException(EXPECTED_EXCEPTION_MESSAGE)); + } + + private EndpointAuthClientBuilder createEndpointAuthClient() { + return EndpointAuthClient.builder() + .httpClient(mockHttpClient) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .region(TEST_REGION); + } + + // Helper classes and methods + private static AuthScheme authScheme(String schemeId, HttpSigner signer) { + return new AuthScheme() { + @Override + public String schemeId() { + return schemeId; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + @Override + public HttpSigner signer() { + return signer; + } + }; + } + + public static class CapturingSigner implements HttpSigner { + private BaseSignRequest request; + + @Override + public SignedRequest sign(SignRequest request) { + this.request = request; + throw new RuntimeException("stop"); + } + + @Override + public CompletableFuture signAsync( + AsyncSignRequest request) { + this.request = request; + return CompletableFutureUtils.failedFuture(new RuntimeException("stop")); + } + } +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java index 6ca37d6d3f73..d24456368fbd 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/EndpointInterceptorTests.java @@ -15,20 +15,33 @@ package software.amazon.awssdk.services.endpointproviders; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import org.junit.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; +import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute; +import software.amazon.awssdk.awscore.endpoints.authscheme.EndpointAuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme; +import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.endpoints.Endpoint; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; 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.AsyncSignRequest; import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; @@ -39,6 +52,7 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.RegionScope; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClient; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersAsyncClientBuilder; import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClient; @@ -187,8 +201,7 @@ public void async_endpointProviderReturnsHeaders_appendedToExistingRequest() { assertThat(interceptor.context.httpRequest().matchingHeaders("TestHeader")).containsExactly("TestValue", "TestValue0"); } - // TODO(sra-identity-auth): Enable for useSraAuth=true - /* + @Test public void sync_endpointProviderReturnsSignerProperties_overridesV4AuthSchemeResolverProperties() { RestJsonEndpointProvidersEndpointProvider defaultEndpointProvider = @@ -452,7 +465,7 @@ public void sync_endpointProviderReturnsV4aSignerProperties_executionAttributesF assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME)).isEqualTo("name-from-ep"); assertThat(interceptor.executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE)).isEqualTo(true); } - */ + private static AuthScheme authScheme(String schemeId, HttpSigner signer) { return new AuthScheme() { diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/MultiAuthSigningPropertiesTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/MultiAuthSigningPropertiesTest.java new file mode 100644 index 000000000000..639f5097146b --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/MultiAuthSigningPropertiesTest.java @@ -0,0 +1,253 @@ +/* + * 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.services.multiauth; + + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +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.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +@DisplayName("Multi-Auth Tests") +class MultiAuthSigningPropertiesTest { + + private static final String MOCK_HTTP_CLIENT_NAME = "MockHttpClient"; + private static final String EXPECTED_EXCEPTION_MESSAGE = "expected exception"; + private static final String CRT_DEPENDENCY_ERROR_MESSAGE = + "You must add a dependency on the 'software.amazon.awssdk:http-auth-aws-crt' module to enable the CRT-V4a signing feature"; + + private final EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + + @Mock + private SdkHttpClient mockHttpClient; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(mockHttpClient.clientName()).thenReturn(MOCK_HTTP_CLIENT_NAME); + when(mockHttpClient.prepareRequest(any())).thenThrow(new RuntimeException(EXPECTED_EXCEPTION_MESSAGE)); + } + + @AfterEach + void tearDown() { + environmentVariableHelper.reset(); + } + + + @Nested + @DisplayName("Region Set Configuration Tests") + class RegionSetConfigurationTests { + + @Test + @DisplayName("Should use environment variable region set when provided") + void shouldUseEnvironmentVariableRegionSet() { + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, "us-west-2,us-west-1"); + CapturingSigner signer = new CapturingSigner(); + + MultiauthClient client = createMultiauthClient(signer); + + assertThatThrownBy(() -> client.multiAuthWithOnlySigv4a(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create(Arrays.asList("us-west-2", "us-west-1"))); + } + + @Test + @DisplayName("Should fall back to client region when no environment variable is set") + void shouldFallBackToClientRegion() { + CapturingSigner signer = new CapturingSigner(); + MultiauthClient client = createMultiauthClient(signer); + + assertThatThrownBy(() -> client.multiAuthWithOnlySigv4a(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create(Region.US_WEST_2.toString())); + } + + @Test + @DisplayName("Should use GLOBAL Regionset as defined in the endpoint rule set when no region set defined on client") + void endpointParamsDefinedAsGlobalUsedWhenNoRegionSetConfigured() { + CapturingSigner signer = new CapturingSigner(); + + MultiauthClient client = createMultiauthClient(signer); + + assertThatThrownBy(() -> client.multiAuthWithRegionSetInEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.GLOBAL); + } + + @Test + @DisplayName("Should use the Region set from Endpoint RuleSet when no RegionSet configured") + void clientApiConfiguredRegionSetTakePrecedenceOverEndpointRulesRegionSet() { + CapturingSigner signer = new CapturingSigner(); + MultiauthClient client = MultiauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .sigv4aSigningRegionSet(RegionSet.create(new StringJoiner(",") + .add(Region.US_WEST_2.id()) + .add(Region.US_GOV_EAST_1.id()) + .toString())) + .build(); + + assertThatThrownBy(() -> client.multiAuthWithRegionSetInEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create("us-west-2,us-gov-east-1")); + } + } + + @Nested + @DisplayName("Fallback Behavior Tests") + class FallbackBehaviorTests { + + @Test + @DisplayName("Should throw error when Sigv4a has no fallback to Sigv4") + void shouldThrowErrorWhenNoFallback() { + MultiauthClient client = MultiauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> client.multiAuthWithOnlySigv4a(r -> r.stringMember(""))) + .hasMessageContaining(CRT_DEPENDENCY_ERROR_MESSAGE); + } + + @Test + @DisplayName("Should fall back to Sigv4 when Sigv4a is not available") + void shouldFallBackToSigv4() { + MultiauthClient client = createMultiauthClient(null); + + assertThatThrownBy(() -> client.multiAuthWithOnlySigv4aAndSigv4(r -> r.stringMember(""))) + .hasMessageContaining(EXPECTED_EXCEPTION_MESSAGE); + + verify(mockHttpClient).prepareRequest( + argThat(request -> request.httpRequest().firstMatchingHeader("Authorization").isPresent())); + } + } + + @Nested + @DisplayName("Region Configuration Tests") + class RegionConfigurationTests { + @Test + @DisplayName("Endpoint Rules Auth Scheme Region take highest precedence ") + void authSchemesParamsUpdatedWithStaticContextAndDefaultEndpointParams() { + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, "us-west-2,us-west-1"); + + CapturingSigner signer = new CapturingSigner(); + + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .putAuthScheme(authScheme("aws.auth#sigv4", signer)) + .region(Region.EU_CENTRAL_1) + .build(); + + Assertions.assertThatThrownBy(() -> multiauthClient.legacySigv4WithEndpointsRules(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + Assertions.assertThat(signer.request.property(AwsV4HttpSigner.REGION_NAME)).isEqualTo(Region.US_GOV_EAST_1.id()); + } + } + + + private MultiauthClient createMultiauthClient(CapturingSigner signer) { + MultiauthClientBuilder builder = MultiauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2); + + if (signer != null) { + builder.putAuthScheme(authScheme("aws.auth#sigv4a", signer)); + } + + return builder.build(); + } + + + + public static class CapturingSigner implements HttpSigner { + private BaseSignRequest request; + + @Override + public SignedRequest sign(SignRequest request) { + this.request = request; + throw new RuntimeException("stop"); + } + + @Override + public CompletableFuture signAsync( + AsyncSignRequest request) { + this.request = request; + return CompletableFutureUtils.failedFuture(new RuntimeException("stop")); + } + } + + private static AuthScheme authScheme(String schemeId, HttpSigner signer) { + return new AuthScheme() { + @Override + public String schemeId() { + return schemeId; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + @Override + public HttpSigner signer() { + return signer; + } + }; + } + +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aOnlyMultiAuthTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aOnlyMultiAuthTest.java new file mode 100644 index 000000000000..736d909edad2 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aOnlyMultiAuthTest.java @@ -0,0 +1,193 @@ +/* + * 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.services.multiauth; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; +import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; +import software.amazon.awssdk.http.auth.spi.signer.BaseSignRequest; +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.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthClient; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +public class Sigv4aOnlyMultiAuthTest { + + + private static final String MOCK_HTTP_CLIENT_NAME = "MockHttpClient"; + private static final String EXPECTED_EXCEPTION_MESSAGE = "expected exception"; + private static final String CRT_DEPENDENCY_ERROR_MESSAGE = + "You must add a dependency on the 'software.amazon.awssdk:http-auth-aws-crt' module to enable the CRT-V4a signing " + + "feature"; + + private final EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + + @Mock + private SdkHttpClient mockHttpClient; + + private static AuthScheme authScheme(String schemeId, HttpSigner signer) { + return new AuthScheme() { + @Override + public String schemeId() { + return schemeId; + } + + @Override + public IdentityProvider identityProvider(IdentityProviders providers) { + return providers.identityProvider(AwsCredentialsIdentity.class); + } + + @Override + public HttpSigner signer() { + return signer; + } + }; + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(mockHttpClient.clientName()).thenReturn(MOCK_HTTP_CLIENT_NAME); + when(mockHttpClient.prepareRequest(any())).thenThrow(new RuntimeException(EXPECTED_EXCEPTION_MESSAGE)); + } + + @AfterEach + void tearDown() { + environmentVariableHelper.reset(); + } + + public static class CapturingSigner implements HttpSigner { + private BaseSignRequest request; + + @Override + public SignedRequest sign(SignRequest request) { + this.request = request; + throw new RuntimeException("stop"); + } + + @Override + public CompletableFuture signAsync( + AsyncSignRequest request) { + this.request = request; + return CompletableFutureUtils.failedFuture(new RuntimeException("stop")); + } + } + + @Nested + @DisplayName("Fall Back behaviour with No CRT library") + class FallbackBehaviorTests { + + @Test + @DisplayName("Should throw error when Sigv4a is ") + void shouldThrowErrorWhenNoFallback() { + Sigv4AauthClient client = Sigv4AauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> client.simpleOperationWithNoEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining(CRT_DEPENDENCY_ERROR_MESSAGE); + } + + } + + @Nested + @DisplayName("Region Configuration Tests") + class RegionConfigurationTests { + @Test + @DisplayName("Client Configured Auth Scheme RegionSet take highest precedence ") + void clientConfiguredRegionSetTakesPrecedenceOverEndpointRuleSet() { + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, "us-west-2,us-west-1"); + + CapturingSigner signer = new CapturingSigner(); + + Sigv4AauthClient sigv4AauthClient = Sigv4AauthClient.builder() + .httpClient(mockHttpClient) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .region(Region.EU_CENTRAL_1) + .build(); + + Assertions.assertThatThrownBy(() -> sigv4AauthClient.simpleOperationWithEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + Assertions.assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)).isEqualTo(RegionSet.create("us-west-2," + + "us-west-1" + )); + } + + @Test + @DisplayName("Endpoint Rules Auth Scheme RegionSet takes precedence over Endpoint region ") + void endpointRuleSetRegionSetTakesPrecedenceOverEndpointRegion() { + + CapturingSigner signer = new CapturingSigner(); + + Sigv4AauthClient sigv4AauthClient = Sigv4AauthClient.builder() + .httpClient(mockHttpClient) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .region(Region.EU_CENTRAL_1) + .build(); + + Assertions.assertThatThrownBy(() -> sigv4AauthClient.simpleOperationWithEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + Assertions.assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)).isEqualTo(RegionSet.GLOBAL); + } + + @Test + @DisplayName("Endpoint region takes precedence when No RegionSet defined ") + void endpointRegionTakesPrecedenceWhenNoRegionSetFound() { + + CapturingSigner signer = new CapturingSigner(); + + Sigv4AauthClient sigv4AauthClient = Sigv4AauthClient.builder() + .httpClient(mockHttpClient) + .putAuthScheme(authScheme("aws.auth#sigv4a", signer)) + .region(Region.EU_CENTRAL_1) + .build(); + + Assertions.assertThatThrownBy(() -> sigv4AauthClient.simpleOperationWithNoEndpointParams(r -> r.stringMember(""))) + .hasMessageContaining("stop"); + + Assertions.assertThat(signer.request.property(AwsV4aHttpSigner.REGION_SET)) + .isEqualTo(RegionSet.create(Region.EU_CENTRAL_1.id())); + } + } + + +}