Skip to content

Add Environment Token support #6130

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 26 commits into
base: rvaknin/auth-schem-preference-config
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e2e2fbf
Prototype implementation of Auth Scheme Preference
alextwoods Apr 15, 2025
6790d42
Add tracking of explictly set token provider
alextwoods Apr 16, 2025
b894b88
Use generated PreferredAuthSchemeProvider to wrap/delegate
alextwoods Apr 16, 2025
2bdaac7
Add generic-service environment token provider + customization config…
alextwoods Apr 16, 2025
f72dac1
Use generated PreferredAuthSchemeProvider to wrap/delegate
alextwoods Apr 16, 2025
7cc52ee
Merge branch 'alexwoo/configure-auth-preference' into alexwoo/bedrock…
alextwoods Apr 21, 2025
26b8fda
Include tokenProvider in service config when bearer is on the model
alextwoods May 1, 2025
14682dc
Support sourcing token from jvm settings + env variable.
alextwoods May 1, 2025
a6a1346
Refactor + use namingStrategy + add tests
alextwoods May 2, 2025
60165c1
Set business metric using an interceptor
alextwoods May 2, 2025
8feab9e
Add ability to override token provider on request
alextwoods May 2, 2025
345bb93
Fix checkstyle on prefered auth scheme provider
alextwoods May 5, 2025
e55fe52
Add validation of service+customization
alextwoods May 5, 2025
1849a83
Refactor env token customizaiton logic + add more tests
alextwoods May 5, 2025
e834b54
Testing and cleanup
alextwoods May 9, 2025
2090d04
Merge branch 'master' into alexwoo/env-token-provider
alextwoods May 21, 2025
badc138
Merge branch 'master' into alexwoo/env-token-provider
alextwoods May 23, 2025
a85dace
Merge branch 'rvaknin/auth-schem-preference-config' into alexwoo/env-…
alextwoods May 23, 2025
c4b6fbd
Add changelog
alextwoods May 23, 2025
48784f5
Minor cleanups
alextwoods May 23, 2025
430e335
Merge branch 'rvaknin/auth-schem-preference-config' into alexwoo/env-…
alextwoods May 23, 2025
ca2faac
Refactor and cleanup - move anon classes to full codegen classes.
alextwoods May 27, 2025
f3992dc
Update docs
alextwoods May 27, 2025
7a8bfa0
Additional codegen tests
alextwoods May 28, 2025
10c5e4f
Update core/aws-core/src/main/java/software/amazon/awssdk/awscore/int…
alextwoods May 28, 2025
e9afd67
Add codegen tset for base client builder w/ env bearer token
alextwoods May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-1932d6a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Adds support for configuring bearer auth using a token sourced from the environment for services with the `enableEnvironmentBearerToken` customization flag."
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

package software.amazon.awssdk.codegen.emitters.tasks;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
import software.amazon.awssdk.codegen.emitters.PoetGeneratorTask;
import software.amazon.awssdk.codegen.poet.client.EnvironmentTokenMetricsInterceptorClass;
import software.amazon.awssdk.codegen.poet.client.EnvironmentTokenSystemSettingsClass;
import software.amazon.awssdk.codegen.poet.client.SdkClientOptions;
import software.amazon.awssdk.codegen.poet.common.UserAgentUtilsSpec;

Expand All @@ -33,7 +35,14 @@ public CommonInternalGeneratorTasks(GeneratorTaskParams params) {

@Override
protected List<GeneratorTask> createTasks() throws Exception {
return Arrays.asList(createClientOptionTask(), createUserAgentTask());
List<GeneratorTask> tasks = new ArrayList<>();
tasks.add(createClientOptionTask());
tasks.add(createUserAgentTask());
if (params.getModel().getCustomizationConfig().isEnableEnvironmentBearerToken()) {
tasks.add(createEnvironmentTokenSystemSettingTask());
tasks.add(createEnvironmentTokenMetricInterceptorTask());
}
return tasks;
}

private PoetGeneratorTask createClientOptionTask() {
Expand All @@ -46,6 +55,16 @@ private PoetGeneratorTask createUserAgentTask() {
new UserAgentUtilsSpec(params.getModel()));
}

private GeneratorTask createEnvironmentTokenSystemSettingTask() {
return new PoetGeneratorTask(clientOptionsDir(), params.getModel().getFileHeader(),
new EnvironmentTokenSystemSettingsClass(params.getModel()));
}

private GeneratorTask createEnvironmentTokenMetricInterceptorTask() {
return new PoetGeneratorTask(clientOptionsDir(), params.getModel().getFileHeader(),
new EnvironmentTokenMetricsInterceptorClass(params.getModel()));
}

private String clientOptionsDir() {
return params.getPathProvider().getClientInternalDirectory();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,13 @@ public class CustomizationConfig {
*/
private boolean enableFastUnmarshaller;

/**
* A boolean flag to indicate if support for configuring a bearer token sourced from the environment should be added to the
* generated service. When enabled, the generated client will use bearer auth with the token sourced from the
* `AWS_BEARER_TOKEN_[SigningName]` environment variable.
*/
private boolean enableEnvironmentBearerToken = false;

private CustomizationConfig() {
}

Expand Down Expand Up @@ -924,4 +931,12 @@ public boolean getEnableFastUnmarshaller() {
public void setEnableFastUnmarshaller(boolean enableFastUnmarshaller) {
this.enableFastUnmarshaller = enableFastUnmarshaller;
}

public boolean isEnableEnvironmentBearerToken() {
return enableEnvironmentBearerToken;
}

public void setEnableEnvironmentBearerToken(boolean enableEnvironmentBearerToken) {
this.enableEnvironmentBearerToken = enableEnvironmentBearerToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,22 @@ private boolean isDisallowedNameForShape(String name, Shape parentShape) {
}
}

@Override
public String getSigningName() {
return Optional.ofNullable(serviceModel.getMetadata().getSigningName())
.orElseGet(() -> serviceModel.getMetadata().getEndpointPrefix());
}

@Override
public String getSigningNameForEnvironmentVariables() {
return screamCase(getSigningName());
}

@Override
public String getSigningNameForSystemProperties() {
return pascalCase(getSigningName());
}

@Override
public void validateCustomerVisibleNaming(IntermediateModel trimmedModel) {
Metadata metadata = trimmedModel.getMetadata();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@ public interface NamingStrategy {
*/
String getExistenceCheckMethodName(String memberName, Shape parentShape);

/**
* Retrieve the service's signing name that should be used based on the model.
*/
String getSigningName();

/**
* Retrieve the service's signing name that should be used for environment variables.
*/
String getSigningNameForEnvironmentVariables();

/**
* Retrieve the service's signing name that should be used for system properties.
*/
String getSigningNameForSystemProperties();

/**
* Verify the customer-visible naming in the provided intermediate model will compile and is idiomatic to Java.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ public ClassName getUserAgentClass() {
return ClassName.get(model.getMetadata().getFullClientInternalPackageName(), "UserAgentUtils");
}

public ClassName getEnvironmentTokenMetricsInterceptorClass() {
return ClassName.get(model.getMetadata().getFullClientInternalPackageName(), "EnvironmentTokenMetricsInterceptor");
}

public ClassName getEnvironmentTokenSystemSettingsClass() {
return ClassName.get(model.getMetadata().getFullClientInternalPackageName(), "EnvironmentTokenSystemSettings");
}

/**
* @param operationName Name of the operation
* @return A Poet {@link ClassName} for the response type of a paginated operation in the base service package.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.TokenUtils;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.token.credentials.StaticTokenProvider;
import software.amazon.awssdk.auth.token.credentials.aws.DefaultAwsTokenProvider;
import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner;
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
Expand All @@ -53,6 +54,7 @@
import software.amazon.awssdk.codegen.model.service.AuthType;
import software.amazon.awssdk.codegen.model.service.ClientContextParam;
import software.amazon.awssdk.codegen.poet.ClassSpec;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils;
import software.amazon.awssdk.codegen.poet.auth.scheme.ModelAuthSchemeClassesKnowledgeIndex;
Expand Down Expand Up @@ -102,6 +104,8 @@ public class BaseClientBuilderClass implements ClassSpec {
private final AuthSchemeSpecUtils authSchemeSpecUtils;
private final ServiceClientConfigurationUtils configurationUtils;
private final EndpointParamsKnowledgeIndex endpointParamsKnowledgeIndex;
private final PoetExtension poetExtensions;


public BaseClientBuilderClass(IntermediateModel model) {
this.model = model;
Expand All @@ -112,6 +116,7 @@ public BaseClientBuilderClass(IntermediateModel model) {
this.authSchemeSpecUtils = new AuthSchemeSpecUtils(model);
this.configurationUtils = new ServiceClientConfigurationUtils(model);
this.endpointParamsKnowledgeIndex = EndpointParamsKnowledgeIndex.of(model);
this.poetExtensions = new PoetExtension(model);
}

@Override
Expand Down Expand Up @@ -266,24 +271,24 @@ private MethodSpec serviceNameMethod() {
}

private MethodSpec mergeServiceDefaultsMethod() {
boolean crc32FromCompressedDataEnabled = model.getCustomizationConfig().isCalculateCrc32FromCompressedData();

MethodSpec.Builder builder = MethodSpec.methodBuilder("mergeServiceDefaults")
.addAnnotation(Override.class)
.addModifiers(PROTECTED, FINAL)
.returns(SdkClientConfiguration.class)
.addParameter(SdkClientConfiguration.class, "config")
.addCode("return config.merge(c -> c");
.addParameter(SdkClientConfiguration.class, "config");

builder.addCode(".option($T.ENDPOINT_PROVIDER, defaultEndpointProvider())", SdkClientOption.class);
boolean crc32FromCompressedDataEnabled = model.getCustomizationConfig().isCalculateCrc32FromCompressedData();

builder.beginControlFlow("return config.merge(c -> ");
builder.addCode("c.option($T.ENDPOINT_PROVIDER, defaultEndpointProvider())", SdkClientOption.class);

if (authSchemeSpecUtils.useSraAuth()) {
builder.addCode(".option($T.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider(config))", SdkClientOption.class);
builder.addCode(".option($T.AUTH_SCHEMES, authSchemes())", SdkClientOption.class);
} else {
if (defaultAwsAuthSignerMethod().isPresent()) {
builder.addCode(".option($T.SIGNER, defaultSigner())\n", SdkAdvancedClientOption.class);
if (!model.getCustomizationConfig().isEnableEnvironmentBearerToken()) {
builder.addCode(".option($T.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider(config))", SdkClientOption.class);
}
builder.addCode(".option($T.AUTH_SCHEMES, authSchemes())", SdkClientOption.class);
} else if (defaultAwsAuthSignerMethod().isPresent()) {
builder.addCode(".option($T.SIGNER, defaultSigner())\n", SdkAdvancedClientOption.class);
}
builder.addCode(".option($T.CRC32_FROM_COMPRESSED_DATA_ENABLED, $L)\n",
SdkClientOption.class, crc32FromCompressedDataEnabled);
Expand All @@ -302,11 +307,52 @@ private MethodSpec mergeServiceDefaultsMethod() {
builder.addCode(".option($T.TOKEN_SIGNER, defaultTokenSigner())", SdkAdvancedClientOption.class);
}
}
builder.addStatement("");

builder.addCode(");");
if (model.getCustomizationConfig().isEnableEnvironmentBearerToken()) {
configureEnvironmentBearerToken(builder);
}
builder.endControlFlow(")");
return builder.build();
}

private void configureEnvironmentBearerToken(MethodSpec.Builder builder) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get a codegenerated test class to see what this looks like?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added, see: test-env-bearer-token-client-builder-class.java (in particular here)

if (!authSchemeSpecUtils.useSraAuth()) {
throw new IllegalStateException("The enableEnvironmentBearerToken customization requires SRA Auth.");
}
if (!AuthUtils.usesBearerAuth(model)) {
throw new IllegalStateException("The enableEnvironmentBearerToken customization requires the service to model and "
+ "support smithy.api#httpBearerAuth.");
}

builder.addStatement("$T tokenFromEnv = new $T().getStringValue()",
ParameterizedTypeName.get(Optional.class, String.class),
poetExtensions.getEnvironmentTokenSystemSettingsClass());

builder
.beginControlFlow("if (tokenFromEnv.isPresent() && config.option($T.AUTH_SCHEME_PROVIDER) == null && config.option($T"
+ ".TOKEN_IDENTITY_PROVIDER) == null)",
SdkClientOption.class, AwsClientOption.class)
.addStatement("c.option($T.AUTH_SCHEME_PROVIDER, $T.builder()"
+ ".withPreferredAuthSchemes($T.singletonList($S)).build())",
SdkClientOption.class, authSchemeSpecUtils.providerInterfaceName(), Collections.class,
"httpBearerAuth")
.addStatement("c.option($T.TOKEN_IDENTITY_PROVIDER, $T.create(tokenFromEnv::get))",
AwsClientOption.class, StaticTokenProvider.class)
.addStatement("$T interceptors = c.option($T.EXECUTION_INTERCEPTORS)",
ParameterizedTypeName.get(List.class, ExecutionInterceptor.class), SdkClientOption.class)
.addStatement("$T envTokenMetricInterceptors = $T.singletonList(new $T(tokenFromEnv.get()))",
ParameterizedTypeName.get(List.class, ExecutionInterceptor.class), Collections.class,
poetExtensions.getEnvironmentTokenMetricsInterceptorClass())
.addStatement("c.option($T.EXECUTION_INTERCEPTORS, $T.mergeLists(interceptors, envTokenMetricInterceptors))",
SdkClientOption.class, CollectionUtils.class)
.endControlFlow()
.beginControlFlow("else")
.addStatement("c.option($T.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider(config))", SdkClientOption.class)
.endControlFlow();

}

private Optional<MethodSpec> mergeInternalDefaultsMethod() {
String userAgent = model.getCustomizationConfig().getUserAgent();
RetryMode defaultRetryMode = model.getCustomizationConfig().getDefaultRetryMode();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.client;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.poet.ClassSpec;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.core.SelectedAuthScheme;
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.core.useragent.BusinessMetricFeatureId;
import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme;
import software.amazon.awssdk.identity.spi.TokenIdentity;

public class EnvironmentTokenMetricsInterceptorClass implements ClassSpec {
protected final PoetExtension poetExtensions;

public EnvironmentTokenMetricsInterceptorClass(IntermediateModel model) {
this.poetExtensions = new PoetExtension(model);
}


@Override
public TypeSpec poetSpec() {
return TypeSpec.classBuilder(className())
.addModifiers(Modifier.PUBLIC)
.addAnnotation(PoetUtils.generatedAnnotation())
.addAnnotation(SdkInternalApi.class)
.addSuperinterface(ExecutionInterceptor.class)
.addField(String.class, "tokenFromEnv", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(constructor())
.addMethod(beforeExecutionMethod())
.build();
}

private MethodSpec constructor() {
return MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "tokenFromEnv")
.addStatement("this.tokenFromEnv = tokenFromEnv")
.build();
}

@Override
public ClassName className() {
return poetExtensions.getEnvironmentTokenMetricsInterceptorClass();
}

private MethodSpec beforeExecutionMethod() {
return MethodSpec
.methodBuilder("beforeExecution")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(Context.BeforeExecution.class, "context")
.addParameter(ExecutionAttributes.class, "executionAttributes")
.addStatement("$T<?> selectedAuthScheme = executionAttributes.getAttribute($T.SELECTED_AUTH_SCHEME)",
SelectedAuthScheme.class, SdkInternalExecutionAttribute.class)
.beginControlFlow("if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals($T"
+ ".SCHEME_ID) && selectedAuthScheme.identity().isDone())", BearerAuthScheme.class)
.beginControlFlow("if (selectedAuthScheme.identity().getNow(null) instanceof $T)", TokenIdentity.class)

.addStatement("$T configuredToken = ($T) selectedAuthScheme.identity().getNow(null)",
TokenIdentity.class, TokenIdentity.class)
.beginControlFlow("if (configuredToken.token().equals(tokenFromEnv))")
.addStatement("executionAttributes.getAttribute($T.BUSINESS_METRICS)"
+ ".addMetric($T.BEARER_SERVICE_ENV_VARS.value())",
SdkInternalExecutionAttribute.class, BusinessMetricFeatureId.class)
.endControlFlow()
.endControlFlow()
.endControlFlow()
.build();
}
}
Loading