Skip to content

Commit ddbfde3

Browse files
committed
chore(core): add disableClockSkewCorrection config support
1 parent 644e8e4 commit ddbfde3

8 files changed

Lines changed: 319 additions & 21 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.aws.typescript.codegen;
6+
7+
import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService;
8+
import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isSigV4Service;
9+
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.function.Consumer;
14+
import java.util.logging.Logger;
15+
import software.amazon.smithy.codegen.core.Symbol;
16+
import software.amazon.smithy.codegen.core.SymbolProvider;
17+
import software.amazon.smithy.model.Model;
18+
import software.amazon.smithy.model.shapes.ServiceShape;
19+
import software.amazon.smithy.typescript.codegen.LanguageTarget;
20+
import software.amazon.smithy.typescript.codegen.SmithyCoreSubmodules;
21+
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
22+
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
23+
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
24+
import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin;
25+
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
26+
import software.amazon.smithy.utils.SmithyInternalApi;
27+
28+
/**
29+
* Generates disableClockSkewCorrection configuration field for all AWS service clients.
30+
* When enabled, the SDK will not adjust signing timestamps, will not update the client
31+
* clock offset from response headers, and will not retry clock skew errors.
32+
*/
33+
@SmithyInternalApi
34+
public final class AddDisableClockSkewCorrectionRuntimeConfig implements TypeScriptIntegration {
35+
36+
private static final Logger LOGGER =
37+
Logger.getLogger(AddDisableClockSkewCorrectionRuntimeConfig.class.getName());
38+
39+
@Override
40+
public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(
41+
TypeScriptSettings settings,
42+
Model model,
43+
SymbolProvider symbolProvider,
44+
LanguageTarget target
45+
) {
46+
Map<String, Consumer<TypeScriptWriter>> runtimeConfigs = new HashMap<>();
47+
if (isAwsService(settings, model) || isSigV4Service(settings, model)) {
48+
switch (target) {
49+
case BROWSER:
50+
runtimeConfigs.put("disableClockSkewCorrection", writer -> {
51+
writer.addImportSubmodule(
52+
"DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION",
53+
null,
54+
AwsDependency.AWS_SDK_CORE,
55+
"/httpAuthSchemes"
56+
);
57+
writer.write("(() => Promise.resolve(DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION))");
58+
});
59+
break;
60+
case NODE:
61+
runtimeConfigs.put("disableClockSkewCorrection", writer -> {
62+
writer.addImportSubmodule(
63+
"loadConfig",
64+
"loadNodeConfig",
65+
TypeScriptDependency.SMITHY_CORE,
66+
SmithyCoreSubmodules.CONFIG
67+
);
68+
writer.addImportSubmodule(
69+
"NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS",
70+
null,
71+
AwsDependency.AWS_SDK_CORE,
72+
"/httpAuthSchemes"
73+
);
74+
writer.write(
75+
"loadNodeConfig(NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS, loaderConfig)"
76+
);
77+
});
78+
break;
79+
default:
80+
LOGGER.warning("DisableClockSkewCorrection config not supported for target: " + target);
81+
break;
82+
}
83+
}
84+
return runtimeConfigs;
85+
}
86+
87+
@Override
88+
public List<RuntimeClientPlugin> getClientPlugins() {
89+
return List.of(
90+
RuntimeClientPlugin.builder()
91+
.inputConfig(
92+
Symbol.builder()
93+
.namespace(
94+
AwsDependency.AWS_SDK_CORE.getPackageName() + "/httpAuthSchemes",
95+
"/"
96+
)
97+
.name("DisableClockSkewCorrectionInputConfig")
98+
.build()
99+
)
100+
.resolvedConfig(
101+
Symbol.builder()
102+
.namespace(
103+
AwsDependency.AWS_SDK_CORE.getPackageName() + "/httpAuthSchemes",
104+
"/"
105+
)
106+
.name("DisableClockSkewCorrectionResolvedConfig")
107+
.build()
108+
)
109+
.resolveFunction(
110+
Symbol.builder()
111+
.namespace(
112+
AwsDependency.AWS_SDK_CORE.getPackageName() + "/httpAuthSchemes",
113+
"/"
114+
)
115+
.name("resolveDisableClockSkewCorrectionConfig")
116+
.build()
117+
)
118+
.servicePredicate((m, s) -> isAwsService(s) || isSigV4Service(s))
119+
.build()
120+
);
121+
}
122+
}

codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
software.amazon.smithy.aws.typescript.codegen.AddEndpointsV2ParameterNameMap
22
software.amazon.smithy.aws.typescript.codegen.AddAwsRuntimeConfig
33
software.amazon.smithy.aws.typescript.codegen.AddAccountIdEndpointModeRuntimeConfig
4+
software.amazon.smithy.aws.typescript.codegen.AddDisableClockSkewCorrectionRuntimeConfig
45
software.amazon.smithy.aws.typescript.codegen.AddBuiltinPlugins
56
software.amazon.smithy.aws.typescript.codegen.AddServiceCustomizationPlugins
67
software.amazon.smithy.aws.typescript.codegen.AddAwsAuthPlugin

packages-internal/core/src/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,76 @@ describe(AwsSdkSigV4Signer.name, () => {
111111

112112
expect((error as any).$metadata.clockSkewCorrected).toBeUndefined();
113113
});
114+
115+
describe("when disableClockSkewCorrection is true", () => {
116+
it("sign() does not capture _requestSentAt or _preRequestSystemClockOffset", async () => {
117+
const signer = new AwsSdkSigV4Signer();
118+
const signingProperties: Record<string, unknown> = {
119+
context: { endpointV2: { properties: {} } },
120+
config: {
121+
systemClockOffset: 60_000,
122+
signer: async () => ({
123+
sign: async (request: any) => request,
124+
}),
125+
disableClockSkewCorrection: async () => true,
126+
},
127+
};
128+
129+
const { HttpRequest } = await import("@smithy/core/protocols");
130+
const httpRequest = new HttpRequest({ hostname: "example.com", path: "/" });
131+
await signer.sign(httpRequest, { accessKeyId: "akid", secretAccessKey: "secret" }, signingProperties);
132+
133+
expect(signingProperties._requestSentAt).toBeUndefined();
134+
expect(signingProperties._preRequestSystemClockOffset).toBeUndefined();
135+
expect(signingProperties._disableClockSkewCorrection).toBe(true);
136+
});
137+
138+
it("errorHandler() does not update systemClockOffset or set clockSkewCorrected", async () => {
139+
const signer = new AwsSdkSigV4Signer();
140+
const oneHourMs = 60 * 60 * 1000;
141+
const serverTime = new Date(Date.now() + oneHourMs).toISOString();
142+
const config = { systemClockOffset: 0 };
143+
144+
let error: Error | any;
145+
try {
146+
signer.errorHandler({
147+
_disableClockSkewCorrection: true,
148+
_preRequestSystemClockOffset: 0,
149+
config,
150+
})(
151+
Object.assign(new Error("RequestTimeTooSkewed"), {
152+
name: "RequestTimeTooSkewed",
153+
ServerTime: serverTime,
154+
$metadata: {},
155+
$response: {
156+
headers: { date: serverTime },
157+
statusCode: 403,
158+
},
159+
})
160+
);
161+
} catch (e) {
162+
error = e as Error;
163+
}
164+
165+
expect(config.systemClockOffset).toBe(0);
166+
expect((error as any).$metadata.clockSkewCorrected).toBeUndefined();
167+
});
168+
169+
it("successHandler() does not update systemClockOffset", () => {
170+
const signer = new AwsSdkSigV4Signer();
171+
const oneHourMs = 60 * 60 * 1000;
172+
const serverTime = new Date(Date.now() + oneHourMs).toISOString();
173+
const config = { systemClockOffset: 0 };
174+
175+
signer.successHandler(
176+
{ headers: { date: serverTime }, statusCode: 200 },
177+
{
178+
_disableClockSkewCorrection: true,
179+
config,
180+
}
181+
);
182+
183+
expect(config.systemClockOffset).toBe(0);
184+
});
185+
});
114186
});

packages-internal/core/src/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
HttpRequest as IHttpRequest,
88
HttpResponse,
99
HttpSigner,
10+
Provider,
1011
RequestSigner,
1112
} from "@smithy/types";
1213

@@ -29,6 +30,7 @@ const throwSigningPropertyError = <T>(name: string, property: T | undefined): T
2930
interface AwsSdkSigV4Config extends AwsSdkSigV4AAuthResolvedConfig {
3031
systemClockOffset: number;
3132
signer: (authScheme?: AuthScheme) => Promise<RequestSigner>;
33+
disableClockSkewCorrection?: Provider<boolean>;
3234
}
3335

3436
/**
@@ -116,12 +118,18 @@ export class AwsSdkSigV4Signer implements HttpSigner {
116118
}
117119
}
118120

119-
// Capture the clock offset before signing so errorHandler can detect concurrent corrections.
120-
signingProperties._preRequestSystemClockOffset = config.systemClockOffset;
121-
// Capture the raw send time (no skew applied) for the midpoint skew formula.
122-
signingProperties._requestSentAt = Date.now();
121+
const disabled = (await config.disableClockSkewCorrection?.()) === true;
122+
signingProperties._disableClockSkewCorrection = disabled;
123+
124+
if (!disabled) {
125+
// Capture the clock offset before signing so errorHandler can detect concurrent corrections.
126+
signingProperties._preRequestSystemClockOffset = config.systemClockOffset;
127+
// Capture the raw send time (no skew applied) for the midpoint skew formula.
128+
signingProperties._requestSentAt = Date.now();
129+
}
130+
123131
const signedRequest = await signer.sign(httpRequest, {
124-
signingDate: getSkewCorrectedDate(config.systemClockOffset),
132+
signingDate: disabled ? new Date() : getSkewCorrectedDate(config.systemClockOffset),
125133
signingRegion: signingRegion,
126134
signingService: signingName,
127135
});
@@ -131,34 +139,44 @@ export class AwsSdkSigV4Signer implements HttpSigner {
131139
errorHandler(signingProperties: Record<string, unknown>): (error: Error) => never {
132140
return (error: Error) => {
133141
const errorException = error as AwsSdkSigV4Exception;
134-
const serverTime: string | undefined = errorException.ServerTime ?? getDateHeader(errorException.$response);
135-
if (serverTime) {
136-
const config = throwSigningPropertyError("config", signingProperties.config as AwsSdkSigV4Config | undefined);
137-
const preRequestOffset = signingProperties._preRequestSystemClockOffset as number | undefined;
138-
const timeRequestSent = signingProperties._requestSentAt as number | undefined;
139-
const ageHeader = getAgeHeader(errorException.$response);
140-
const newOffset = getUpdatedSystemClockOffset(serverTime, config.systemClockOffset, timeRequestSent, ageHeader);
141-
142-
const isLocalCorrection = newOffset !== config.systemClockOffset;
143-
const isConcurrentCorrection = preRequestOffset !== undefined && preRequestOffset !== newOffset;
144-
145-
const clockSkewCorrected = isLocalCorrection || isConcurrentCorrection;
146-
if (clockSkewCorrected && errorException.$metadata) {
147-
config.systemClockOffset = newOffset;
148-
errorException.$metadata.clockSkewCorrected = true;
142+
if (!signingProperties._disableClockSkewCorrection) {
143+
const serverTime: string | undefined = errorException.ServerTime ?? getDateHeader(errorException.$response);
144+
if (serverTime) {
145+
const config = throwSigningPropertyError("config", signingProperties.config as AwsSdkSigV4Config | undefined);
146+
const preRequestOffset = signingProperties._preRequestSystemClockOffset as number | undefined;
147+
const timeRequestSent = signingProperties._requestSentAt as number | undefined;
148+
const ageHeader = getAgeHeader(errorException.$response);
149+
const newOffset = getUpdatedSystemClockOffset(serverTime, config.systemClockOffset, timeRequestSent, ageHeader);
150+
151+
const isLocalCorrection = newOffset !== config.systemClockOffset;
152+
const isConcurrentCorrection = preRequestOffset !== undefined && preRequestOffset !== newOffset;
153+
154+
const clockSkewCorrected = isLocalCorrection || isConcurrentCorrection;
155+
if (clockSkewCorrected && errorException.$metadata) {
156+
config.systemClockOffset = newOffset;
157+
errorException.$metadata.clockSkewCorrected = true;
158+
}
149159
}
150160
}
151161
throw error;
152162
};
153163
}
154164

155165
successHandler(httpResponse: HttpResponse | unknown, signingProperties: Record<string, unknown>): void {
166+
if (signingProperties._disableClockSkewCorrection) {
167+
return;
168+
}
156169
const dateHeader = getDateHeader(httpResponse);
157170
if (dateHeader) {
158171
const config = throwSigningPropertyError("config", signingProperties.config as AwsSdkSigV4Config | undefined);
159172
const timeRequestSent = signingProperties._requestSentAt as number | undefined;
160173
const ageHeader = getAgeHeader(httpResponse);
161-
config.systemClockOffset = getUpdatedSystemClockOffset(dateHeader, config.systemClockOffset, timeRequestSent, ageHeader);
174+
config.systemClockOffset = getUpdatedSystemClockOffset(
175+
dateHeader,
176+
config.systemClockOffset,
177+
timeRequestSent,
178+
ageHeader
179+
);
162180
}
163181
}
164182
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { type LoadedConfigSelectors, booleanSelector, SelectorType } from "@smithy/core/config";
2+
3+
/**
4+
* @internal
5+
*/
6+
export const ENV_DISABLE_CLOCK_SKEW_CORRECTION = "AWS_DISABLE_CLOCK_SKEW_CORRECTION";
7+
8+
/**
9+
* @internal
10+
*/
11+
export const CONFIG_DISABLE_CLOCK_SKEW_CORRECTION = "disable_clock_skew_correction";
12+
13+
/**
14+
* @internal
15+
*/
16+
export const DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION = false;
17+
18+
/**
19+
* @internal
20+
*/
21+
export const NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS: LoadedConfigSelectors<boolean> = {
22+
environmentVariableSelector: (env: NodeJS.ProcessEnv) =>
23+
booleanSelector(env, ENV_DISABLE_CLOCK_SKEW_CORRECTION, SelectorType.ENV),
24+
configFileSelector: (profile) => booleanSelector(profile, CONFIG_DISABLE_CLOCK_SKEW_CORRECTION, SelectorType.CONFIG),
25+
default: DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION,
26+
};

packages-internal/core/src/submodules/httpAuthSchemes/aws_sdk/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
export { AwsSdkSigV4Signer, AWSSDKSigV4Signer, validateSigningProperties } from "./AwsSdkSigV4Signer";
22
export { AwsSdkSigV4ASigner } from "./AwsSdkSigV4ASigner";
33
export { NODE_AUTH_SCHEME_PREFERENCE_OPTIONS } from "./NODE_AUTH_SCHEME_PREFERENCE_OPTIONS";
4+
export {
5+
ENV_DISABLE_CLOCK_SKEW_CORRECTION,
6+
CONFIG_DISABLE_CLOCK_SKEW_CORRECTION,
7+
DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION,
8+
NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS,
9+
} from "./NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS";
10+
export { resolveDisableClockSkewCorrectionConfig } from "./resolveDisableClockSkewCorrectionConfig";
11+
export type {
12+
DisableClockSkewCorrectionInputConfig,
13+
DisableClockSkewCorrectionResolvedConfig,
14+
} from "./resolveDisableClockSkewCorrectionConfig";
415
export { resolveAwsSdkSigV4AConfig, NODE_SIGV4A_CONFIG_OPTIONS } from "./resolveAwsSdkSigV4AConfig";
516
export type {
617
AwsSdkSigV4AAuthInputConfig,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { normalizeProvider } from "@smithy/core/client";
2+
import type { Provider } from "@smithy/types";
3+
4+
import { DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION } from "./NODE_DISABLE_CLOCK_SKEW_CORRECTION_CONFIG_OPTIONS";
5+
6+
/**
7+
* @public
8+
*/
9+
export interface DisableClockSkewCorrectionInputConfig {
10+
/**
11+
* Whether to disable clock skew correction. When true, the SDK will not adjust
12+
* the signing timestamp, will not update the client clock offset from response
13+
* headers, and will not retry clock skew errors.
14+
*
15+
* Defaults to false (correction enabled).
16+
*/
17+
disableClockSkewCorrection?: boolean | Provider<boolean>;
18+
}
19+
20+
/**
21+
* @internal
22+
*/
23+
export interface DisableClockSkewCorrectionResolvedConfig {
24+
/**
25+
* Resolved value for input config {@link DisableClockSkewCorrectionInputConfig.disableClockSkewCorrection}
26+
*/
27+
disableClockSkewCorrection: Provider<boolean>;
28+
}
29+
30+
/**
31+
* @internal
32+
*/
33+
export const resolveDisableClockSkewCorrectionConfig = <T>(
34+
input: T & DisableClockSkewCorrectionInputConfig
35+
): T & DisableClockSkewCorrectionResolvedConfig => {
36+
return Object.assign(input, {
37+
disableClockSkewCorrection: normalizeProvider(
38+
input.disableClockSkewCorrection ?? DEFAULT_DISABLE_CLOCK_SKEW_CORRECTION
39+
),
40+
});
41+
};

0 commit comments

Comments
 (0)