From bd447d23360efd53bee40e5563281cd98eb842f8 Mon Sep 17 00:00:00 2001 From: Steven Yuan Date: Wed, 10 Jan 2024 23:43:06 -0800 Subject: [PATCH] temp(temp): temp --- .../aws_sdk/AwsSdkSigV4Signer.ts | 132 ++++++++++ .../core/src/httpAuthSchemes/aws_sdk/index.ts | 2 + .../aws_sdk/resolveAwsSdkSigV4Config.ts | 236 ++++++++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 packages/core/src/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.ts create mode 100644 packages/core/src/httpAuthSchemes/aws_sdk/index.ts create mode 100644 packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts diff --git a/packages/core/src/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.ts b/packages/core/src/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.ts new file mode 100644 index 0000000000000..7f1aed5594a20 --- /dev/null +++ b/packages/core/src/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.ts @@ -0,0 +1,132 @@ +import { HttpRequest } from "@smithy/protocol-http"; +import { ServiceException } from "@smithy/smithy-client"; +import { + AuthScheme, + AwsCredentialIdentity, + HandlerExecutionContext, + HttpRequest as IHttpRequest, + HttpResponse, + HttpSigner, + RequestSigner, +} from "@smithy/types"; + +import { getDateHeader, getSkewCorrectedDate, getUpdatedSystemClockOffset } from "../utils"; + +/** + * @internal + */ +const throwSigningPropertyError = (name: string, property: T | undefined): T | never => { + if (!property) { + throw new Error(`Property \`${name}\` is not resolved for AWS SDK SigV4Auth`); + } + return property; +}; + +/** + * @internal + */ +interface AwsSdkSigV4Config { + systemClockOffset: number; + signer: (authScheme?: AuthScheme) => Promise; +} + +/** + * @internal + */ +interface AwsSdkSigV4AuthSigningProperties { + config: AwsSdkSigV4Config; + signer: RequestSigner; + signingRegion?: string; + signingName?: string; +} + +/** + * @internal + */ +interface AwsSdkSigV4Exception extends ServiceException { + ServerTime?: string; +} + +/** + * @internal + */ +const validateSigningProperties = async ( + signingProperties: Record +): Promise => { + const context = throwSigningPropertyError( + "context", + signingProperties.context as HandlerExecutionContext | undefined + ); + const config = throwSigningPropertyError("config", signingProperties.config as AwsSdkSigV4Config | undefined); + const authScheme = context.endpointV2?.properties?.authSchemes?.[0]; + const signerFunction = throwSigningPropertyError( + "signer", + config.signer as ((authScheme?: AuthScheme) => Promise) | undefined + ); + const signer = await signerFunction(authScheme); + const signingRegion: string | undefined = signingProperties?.signingRegion as string | undefined; + const signingName = signingProperties?.signingName as string | undefined; + return { + config, + signer, + signingRegion, + signingName, + }; +}; + +/** + * @internal + */ +export class AwsSdkSigV4Signer implements HttpSigner { + async sign( + httpRequest: IHttpRequest, + /** + * `identity` is bound in {@link resolveAWSSDKSigV4Config} + */ + identity: AwsCredentialIdentity, + signingProperties: Record + ): Promise { + if (!HttpRequest.isInstance(httpRequest)) { + throw new Error("The request is not an instance of `HttpRequest` and cannot be signed"); + } + const { config, signer, signingRegion, signingName } = await validateSigningProperties(signingProperties); + + const signedRequest = await signer.sign(httpRequest, { + signingDate: getSkewCorrectedDate(config.systemClockOffset), + signingRegion: signingRegion, + signingService: signingName, + }); + return signedRequest; + } + + errorHandler(signingProperties: Record): (error: Error) => never { + return (error: Error) => { + const serverTime: string | undefined = + (error as AwsSdkSigV4Exception).ServerTime ?? getDateHeader((error as AwsSdkSigV4Exception).$response); + if (serverTime) { + const config = throwSigningPropertyError( + "config", + signingProperties.config as AwsSdkSigV4Config | undefined + ); + config.systemClockOffset = getUpdatedSystemClockOffset(serverTime, config.systemClockOffset); + } + throw error; + }; + } + + successHandler(httpResponse: HttpResponse | unknown, signingProperties: Record): void { + const dateHeader = getDateHeader(httpResponse); + if (dateHeader) { + const config = throwSigningPropertyError( + "config", + signingProperties.config as AwsSdkSigV4Config | undefined + ); + config.systemClockOffset = getUpdatedSystemClockOffset(dateHeader, config.systemClockOffset); + } + } +} + +/** + * @deprecated renamed to {@link AwsSdkSigV4Signer} + */ +export const AWSSDKSigV4Signer = AwsSdkSigV4Signer; diff --git a/packages/core/src/httpAuthSchemes/aws_sdk/index.ts b/packages/core/src/httpAuthSchemes/aws_sdk/index.ts new file mode 100644 index 0000000000000..6d17c7096d045 --- /dev/null +++ b/packages/core/src/httpAuthSchemes/aws_sdk/index.ts @@ -0,0 +1,2 @@ +export * from "./AwsSdkSigV4Signer"; +export * from "./resolveAwsSdkSigV4Config"; diff --git a/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts b/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts new file mode 100644 index 0000000000000..7b01c9980e9cb --- /dev/null +++ b/packages/core/src/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.ts @@ -0,0 +1,236 @@ +import { + doesIdentityRequireRefresh, + isIdentityExpired, + memoizeIdentityProvider, + normalizeProvider, +} from "@smithy/core"; +import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@smithy/signature-v4"; +import { + AuthScheme, + AwsCredentialIdentity, + AwsCredentialIdentityProvider, + ChecksumConstructor, + HashConstructor, + MemoizedProvider, + Provider, + RegionInfo, + RegionInfoProvider, + RequestSigner, +} from "@smithy/types"; + +/** + * @internal + */ +export interface AwsSdkSigV4AuthInputConfig { + /** + * The credentials used to sign requests. + */ + credentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider; + + /** + * The signer to use when signing requests. + */ + signer?: RequestSigner | ((authScheme?: AuthScheme) => Promise); + + /** + * Whether to escape request path when signing the request. + */ + signingEscapePath?: boolean; + + /** + * An offset value in milliseconds to apply to all signing times. + */ + systemClockOffset?: number; + + /** + * The region where you want to sign your request against. This + * can be different to the region in the endpoint. + */ + signingRegion?: string; + + /** + * The injectable SigV4-compatible signer class constructor. If not supplied, + * regular SignatureV4 constructor will be used. + * + * @internal + */ + signerConstructor?: new (options: SignatureV4Init & SignatureV4CryptoInit) => RequestSigner; +} + +/** + * @internal + */ +export interface AwsSdkSigV4PreviouslyResolved { + credentialDefaultProvider?: (input: any) => MemoizedProvider; + region: string | Provider; + sha256: ChecksumConstructor | HashConstructor; + signingName?: string; + regionInfoProvider?: RegionInfoProvider; + defaultSigningName?: string; + serviceId: string; + useFipsEndpoint: Provider; + useDualstackEndpoint: Provider; +} + +/** + * @internal + */ +export interface AwsSdkSigV4AuthResolvedConfig { + /** + * Resolved value for input config {@link AwsSdkSigV4AuthInputConfig.credentials} + * This provider MAY memoize the loaded credentials for certain period. + * See {@link MemoizedProvider} for more information. + */ + credentials: AwsCredentialIdentityProvider; + /** + * Resolved value for input config {@link AwsSdkSigV4AuthInputConfig.signer} + */ + signer: (authScheme?: AuthScheme) => Promise; + /** + * Resolved value for input config {@link AwsSdkSigV4AuthInputConfig.signingEscapePath} + */ + signingEscapePath: boolean; + /** + * Resolved value for input config {@link AwsSdkSigV4AuthInputConfig.systemClockOffset} + */ + systemClockOffset: number; +} + +/** + * @internal + */ +export const resolveAwsSdkSigV4Config = ( + config: T & AwsSdkSigV4AuthInputConfig & AwsSdkSigV4PreviouslyResolved +): T & AwsSdkSigV4AuthResolvedConfig => { + // Normalize credentials + let normalizedCreds: AwsCredentialIdentityProvider | undefined; + if (config.credentials) { + normalizedCreds = memoizeIdentityProvider(config.credentials, isIdentityExpired, doesIdentityRequireRefresh); + } + if (!normalizedCreds) { + // credentialDefaultProvider should always be populated, but in case + // it isn't, set a default identity provider that throws an error + if (config.credentialDefaultProvider) { + normalizedCreds = normalizeProvider(config.credentialDefaultProvider(config as any)); + } else { + normalizedCreds = async () => { throw new Error("`credentials` is missing") }; + } + } + + // Populate sigv4 arguments + const { + // Default for signingEscapePath + signingEscapePath = true, + // Default for systemClockOffset + systemClockOffset = config.systemClockOffset || 0, + // No default for sha256 since it is platform dependent + sha256, + } = config; + + // Resolve signer + let signer: (authScheme?: AuthScheme) => Promise; + if (config.signer) { + // if signer is supplied by user, normalize it to a function returning a promise for signer. + signer = normalizeProvider(config.signer); + } else if (config.regionInfoProvider) { + // This branch is for endpoints V1. + // construct a provider inferring signing from region. + signer = () => + normalizeProvider(config.region)() + .then( + async (region) => + [ + (await config.regionInfoProvider!(region, { + useFipsEndpoint: await config.useFipsEndpoint(), + useDualstackEndpoint: await config.useDualstackEndpoint(), + })) || {}, + region, + ] as [RegionInfo, string] + ) + .then(([regionInfo, region]) => { + const { signingRegion, signingService } = regionInfo; + // update client's singing region and signing service config if they are resolved. + // signing region resolving order: user supplied signingRegion -> endpoints.json inferred region -> client region + config.signingRegion = config.signingRegion || signingRegion || region; + // signing name resolving order: + // user supplied signingName -> endpoints.json inferred (credential scope -> model arnNamespace) -> model service id + config.signingName = config.signingName || signingService || config.serviceId; + + const params: SignatureV4Init & SignatureV4CryptoInit = { + ...config, + credentials: normalizedCreds!, + region: config.signingRegion, + service: config.signingName, + sha256, + uriEscapePath: signingEscapePath, + }; + const SignerCtor = config.signerConstructor || SignatureV4; + return new SignerCtor(params); + }); + } else { + // This branch is for endpoints V2. + // Handle endpoints v2 that resolved per-command + // TODO: need total refactor for reference auth architecture. + signer = async (authScheme?: AuthScheme) => { + authScheme = Object.assign( + {}, + { + name: "sigv4", + signingName: config.signingName || config.defaultSigningName!, + signingRegion: await normalizeProvider(config.region)(), + properties: {}, + }, + authScheme + ); + + const signingRegion = authScheme.signingRegion; + const signingService = authScheme.signingName; + // update client's singing region and signing service config if they are resolved. + // signing region resolving order: user supplied signingRegion -> endpoints.json inferred region -> client region + config.signingRegion = config.signingRegion || signingRegion; + // signing name resolving order: + // user supplied signingName -> endpoints.json inferred (credential scope -> model arnNamespace) -> model service id + config.signingName = config.signingName || signingService || config.serviceId; + + const params: SignatureV4Init & SignatureV4CryptoInit = { + ...config, + credentials: normalizedCreds!, + region: config.signingRegion, + service: config.signingName, + sha256, + uriEscapePath: signingEscapePath, + }; + + const SignerCtor = config.signerConstructor || SignatureV4; + return new SignerCtor(params); + }; + } + + return { + ...config, + systemClockOffset, + signingEscapePath, + credentials: normalizedCreds!, + signer, + }; +}; + +/** + * @deprecated renamed to {@link AwsSdkSigV4AuthInputConfig} + */ +export interface AWSSDKSigV4AuthInputConfig extends AwsSdkSigV4AuthInputConfig {} + +/** + * @deprecated renamed to {@link AwsSdkSigV4PreviouslyResolved} + */ +export interface AWSSDKSigV4PreviouslyResolved extends AwsSdkSigV4PreviouslyResolved {} + +/** + * @deprecated renamed to {@link AwsSdkSigV4AuthResolvedConfig} + */ +export interface AWSSDKSigV4AuthResolvedConfig extends AwsSdkSigV4AuthResolvedConfig {} + +/** + * @deprecated renamed to {@link resolveAwsSdkSigV4Config} + */ +export const resolveAWSSDKSigV4Config = resolveAwsSdkSigV4Config;