Skip to content

Commit d858bcf

Browse files
authored
Add business metric support for Sigv4A auth scheme (#6489)
* Add business metric support for Sigv4A auth scheme * Fixing failing tests * Address PR feedback * Additional changes * Fixing test failures * Fixing test failures * Fix test failure * Additional changes in test: Removing print statements Renaming few files * Address review comments * Removing unused import * Move sigv4a business metric logic to auth interceptor to record at auth scheme selection * Remove changes from ApplyUserAgentStage * Adding missed file * Address review comments
1 parent 2307338 commit d858bcf

File tree

11 files changed

+372
-6
lines changed

11 files changed

+372
-6
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Add business metric support for Sigv4A auth scheme to track when an operation is called using Sigv4A signing"
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import software.amazon.awssdk.core.metrics.CoreMetric;
5252
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
5353
import software.amazon.awssdk.endpoints.EndpointProvider;
54+
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme;
5455
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
5556
import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme;
5657
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
@@ -158,6 +159,22 @@ private MethodSpec generateBeforeExecution() {
158159
builder.addStatement("recordEnvironmentTokenBusinessMetric(selectedAuthScheme, "
159160
+ "executionAttributes)");
160161
}
162+
163+
if (authSchemeSpecUtils.hasSigV4aSupport()) {
164+
builder.beginControlFlow("if (selectedAuthScheme != null && "
165+
+ "selectedAuthScheme.authSchemeOption().schemeId().equals($T.SCHEME_ID) && "
166+
+ "!$T.isSignerOverridden(context.request(), executionAttributes))",
167+
AwsV4aAuthScheme.class,
168+
ClassName.get("software.amazon.awssdk.awscore.util", "SignerOverrideUtils"))
169+
.addStatement("$T businessMetrics = executionAttributes.getAttribute($T.BUSINESS_METRICS)",
170+
ClassName.get("software.amazon.awssdk.core.useragent", "BusinessMetricCollection"),
171+
SdkInternalExecutionAttribute.class)
172+
.beginControlFlow("if (businessMetrics != null)")
173+
.addStatement("businessMetrics.addMetric($T.SIGV4A_SIGNING.value())",
174+
BusinessMetricFeatureId.class)
175+
.endControlFlow()
176+
.endControlFlow();
177+
}
161178
return builder.build();
162179
}
163180

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import software.amazon.awssdk.annotations.Generated;
1111
import software.amazon.awssdk.annotations.SdkInternalApi;
1212
import software.amazon.awssdk.awscore.AwsExecutionAttribute;
13+
import software.amazon.awssdk.awscore.util.SignerOverrideUtils;
1314
import software.amazon.awssdk.core.SdkRequest;
1415
import software.amazon.awssdk.core.SelectedAuthScheme;
1516
import software.amazon.awssdk.core.exception.SdkException;
@@ -20,6 +21,9 @@
2021
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2122
import software.amazon.awssdk.core.internal.util.MetricUtils;
2223
import software.amazon.awssdk.core.metrics.CoreMetric;
24+
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
25+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
26+
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme;
2327
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2428
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2529
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
@@ -49,6 +53,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
4953
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5054
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5155
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
56+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID)
57+
&& !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) {
58+
BusinessMetricCollection businessMetrics = executionAttributes
59+
.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
60+
if (businessMetrics != null) {
61+
businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value());
62+
}
63+
}
5264
}
5365

5466
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import software.amazon.awssdk.annotations.Generated;
1111
import software.amazon.awssdk.annotations.SdkInternalApi;
1212
import software.amazon.awssdk.awscore.AwsExecutionAttribute;
13+
import software.amazon.awssdk.awscore.util.SignerOverrideUtils;
1314
import software.amazon.awssdk.core.SdkRequest;
1415
import software.amazon.awssdk.core.SelectedAuthScheme;
1516
import software.amazon.awssdk.core.exception.SdkException;
@@ -20,7 +21,10 @@
2021
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2122
import software.amazon.awssdk.core.internal.util.MetricUtils;
2223
import software.amazon.awssdk.core.metrics.CoreMetric;
24+
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
25+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
2326
import software.amazon.awssdk.endpoints.EndpointProvider;
27+
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme;
2428
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2529
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2630
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
@@ -52,6 +56,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
5256
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5357
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5458
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
59+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID)
60+
&& !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) {
61+
BusinessMetricCollection businessMetrics = executionAttributes
62+
.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
63+
if (businessMetrics != null) {
64+
businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value());
65+
}
66+
}
5567
}
5668

5769
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
@@ -102,7 +114,6 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr
102114
executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET)
103115
.filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet))
104116
.ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet)));
105-
106117
if (builder instanceof QueryEndpointResolverAware.Builder) {
107118
EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER);
108119
if (endpointProvider instanceof QueryEndpointProvider) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import software.amazon.awssdk.annotations.Generated;
1111
import software.amazon.awssdk.annotations.SdkInternalApi;
1212
import software.amazon.awssdk.awscore.AwsExecutionAttribute;
13+
import software.amazon.awssdk.awscore.util.SignerOverrideUtils;
1314
import software.amazon.awssdk.core.SdkRequest;
1415
import software.amazon.awssdk.core.SelectedAuthScheme;
1516
import software.amazon.awssdk.core.exception.SdkException;
@@ -20,7 +21,10 @@
2021
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2122
import software.amazon.awssdk.core.internal.util.MetricUtils;
2223
import software.amazon.awssdk.core.metrics.CoreMetric;
24+
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
25+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
2326
import software.amazon.awssdk.endpoints.EndpointProvider;
27+
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme;
2428
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2529
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2630
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
@@ -52,6 +56,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
5256
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5357
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5458
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
59+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID)
60+
&& !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) {
61+
BusinessMetricCollection businessMetrics = executionAttributes
62+
.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
63+
if (businessMetrics != null) {
64+
businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value());
65+
}
66+
}
5567
}
5668

5769
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
/**
4949
* A stage for adding the user agent header to the request, after retrieving the current string
50-
* from execution attributes and adding any additional information.
50+
* from execution attributes and adding any additional information.
5151
*/
5252
@SdkInternalApi
5353
public class ApplyUserAgentStage implements MutableRequestToRequestPipeline {
@@ -152,11 +152,11 @@ private static Optional<String> getBusinessMetricsString(ExecutionAttributes exe
152152
businessMetrics.merge(metricsFromApiNames);
153153

154154
credentialProviderBusinessMetrics(executionAttributes).ifPresent(businessMetrics::merge);
155-
155+
156156
if (businessMetrics.recordedMetrics().isEmpty()) {
157157
return Optional.empty();
158158
}
159-
159+
160160
return Optional.of(businessMetrics.asBoundedString());
161161
}
162162

@@ -195,4 +195,4 @@ private Optional<String> requestApiNames(List<ApiName> requestApiNames) {
195195
.append(apiName.version()));
196196
return Optional.of(concatenatedNames.toString());
197197
}
198-
}
198+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/**
2323
* An enum class representing a short form of identity providers to record in the UA string.
2424
*
25-
* Unimplemented metrics: I,J,K,O,S,U-c
25+
* Unimplemented metrics: I,J,K,O,U-c
2626
* Unsupported metrics (these will never be added): A,H
2727
*/
2828
@SdkProtectedApi
@@ -40,6 +40,7 @@ public enum BusinessMetricFeatureId {
4040
ACCOUNT_ID_MODE_PREFERRED("P"),
4141
ACCOUNT_ID_MODE_DISABLED("Q"),
4242
ACCOUNT_ID_MODE_REQUIRED("R"),
43+
SIGV4A_SIGNING("S"),
4344
RESOLVED_ACCOUNT_ID("T"),
4445
DDB_MAPPER("d"),
4546
BEARER_SERVICE_ENV_VARS("3"),
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.core.useragent.BusinessMetricCollection.METRIC_SEARCH_PATTERN;
20+
21+
import java.util.List;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
25+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
26+
import software.amazon.awssdk.auth.signer.Aws4Signer;
27+
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
28+
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
29+
import software.amazon.awssdk.http.AbortableInputStream;
30+
import software.amazon.awssdk.http.HttpExecuteResponse;
31+
import software.amazon.awssdk.http.SdkHttpRequest;
32+
import software.amazon.awssdk.http.SdkHttpResponse;
33+
import software.amazon.awssdk.regions.Region;
34+
import software.amazon.awssdk.services.multiauth.MultiauthClient;
35+
import software.amazon.awssdk.services.multiauth.auth.scheme.MultiauthAuthSchemeProvider;
36+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient;
37+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
38+
import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthAsyncClient;
39+
import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthClient;
40+
import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient;
41+
import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
42+
import software.amazon.awssdk.utils.StringInputStream;
43+
import java.util.Arrays;
44+
45+
/**
46+
* Test class to verify that SIGV4A_SIGNING business metric is correctly included
47+
* in the User-Agent header when operation called using Sigv4A signing.
48+
*/
49+
class Sigv4aBusinessMetricUserAgentTest {
50+
private static final String USER_AGENT_HEADER_NAME = "User-Agent";
51+
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER =
52+
StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"));
53+
54+
private MockSyncHttpClient mockHttpClient;
55+
private MockAsyncHttpClient mockAsyncHttpClient;
56+
57+
@BeforeEach
58+
public void setup() {
59+
mockHttpClient = new MockSyncHttpClient();
60+
mockHttpClient.stubNextResponse(mockResponse());
61+
62+
mockAsyncHttpClient = new MockAsyncHttpClient();
63+
mockAsyncHttpClient.stubNextResponse(mockResponse());
64+
}
65+
66+
@Test
67+
void when_sigv4aServiceIsUsed_correctMetricIsAdded() {
68+
Sigv4AauthClient client = Sigv4AauthClient.builder()
69+
.region(Region.US_WEST_2)
70+
.credentialsProvider(CREDENTIALS_PROVIDER)
71+
.httpClient(mockHttpClient)
72+
.build();
73+
74+
client.simpleOperationWithNoEndpointParams(r -> r.stringMember("test"));
75+
76+
String userAgent = getUserAgentFromLastRequest();
77+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("S"));
78+
}
79+
80+
@Test
81+
void when_sigv4aServiceIsUsedAsync_correctMetricIsAdded() {
82+
Sigv4AauthAsyncClient asyncClient = Sigv4AauthAsyncClient.builder()
83+
.region(Region.US_WEST_2)
84+
.credentialsProvider(CREDENTIALS_PROVIDER)
85+
.httpClient(mockAsyncHttpClient)
86+
.build();
87+
88+
asyncClient.simpleOperationWithNoEndpointParams(r -> r.stringMember("test")).join();
89+
90+
String userAgent = getUserAgentFromLastAsyncRequest();
91+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("S"));
92+
}
93+
94+
@Test
95+
void when_regularServiceIsUsed_sigv4aMetricIsNotAdded() {
96+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
97+
.region(Region.US_WEST_2)
98+
.credentialsProvider(CREDENTIALS_PROVIDER)
99+
.httpClient(mockHttpClient)
100+
.build();
101+
102+
client.allTypes(r -> {});
103+
104+
String userAgent = getUserAgentFromLastRequest();
105+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S"));
106+
}
107+
108+
@Test
109+
void when_regularServiceIsUsedAsync_sigv4aMetricIsNotAdded() {
110+
ProtocolRestJsonAsyncClient asyncClient = ProtocolRestJsonAsyncClient.builder()
111+
.region(Region.US_WEST_2)
112+
.credentialsProvider(CREDENTIALS_PROVIDER)
113+
.httpClient(mockAsyncHttpClient)
114+
.build();
115+
116+
asyncClient.allTypes(r -> {}).join();
117+
118+
String userAgent = getUserAgentFromLastAsyncRequest();
119+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S"));
120+
}
121+
122+
@Test
123+
void when_signerIsOverridden_sigv4aMetricIsNotAdded() {
124+
MultiauthClient client = MultiauthClient.builder()
125+
.region(Region.US_WEST_2)
126+
.credentialsProvider(CREDENTIALS_PROVIDER)
127+
.overrideConfiguration(ClientOverrideConfiguration.builder()
128+
.putAdvancedOption(SdkAdvancedClientOption.SIGNER,
129+
Aws4Signer.create())
130+
.build())
131+
.httpClient(mockHttpClient)
132+
.build();
133+
134+
client.multiAuthWithOnlySigv4aAndSigv4(r -> r.stringMember("test"));
135+
String userAgent = getUserAgentFromLastRequest();
136+
137+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S"));
138+
}
139+
140+
@Test
141+
void when_authSchemeProviderOverridesSigv4aOrder_sigv4IsSelected() {
142+
MultiauthClient client = MultiauthClient.builder()
143+
.region(Region.US_WEST_2)
144+
.credentialsProvider(CREDENTIALS_PROVIDER)
145+
.authSchemeProvider(MultiauthAuthSchemeProvider.
146+
defaultProvider(
147+
Arrays.asList("sigv4","sigv4a")))
148+
.httpClient(mockHttpClient)
149+
.build();
150+
151+
client.multiAuthWithOnlySigv4aAndSigv4(r -> r.stringMember("test"));
152+
String userAgent = getUserAgentFromLastRequest();
153+
154+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S"));
155+
}
156+
157+
private String getUserAgentFromLastRequest() {
158+
SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
159+
assertThat(lastRequest).isNotNull();
160+
161+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
162+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
163+
return userAgentHeaders.get(0);
164+
}
165+
166+
private String getUserAgentFromLastAsyncRequest() {
167+
SdkHttpRequest lastRequest = mockAsyncHttpClient.getLastRequest();
168+
assertThat(lastRequest).isNotNull();
169+
170+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
171+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
172+
return userAgentHeaders.get(0);
173+
}
174+
175+
private static HttpExecuteResponse mockResponse() {
176+
return HttpExecuteResponse.builder()
177+
.response(SdkHttpResponse.builder().statusCode(200).build())
178+
.responseBody(AbortableInputStream.create(new StringInputStream("{}")))
179+
.build();
180+
}
181+
}

0 commit comments

Comments
 (0)