Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion clients/client-sts/src/defaultStsRoleAssumers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC
/**
* @public
*/
export type STSRoleAssumerOptions = Pick<STSClientConfig, "logger" | "region" | "requestHandler" | "profile"> & {
export type STSRoleAssumerOptions = Pick<
STSClientConfig,
"logger" | "region" | "requestHandler" | "profile" | "userAgentAppId"
> & {
credentialProviderLogger?: Logger;
parentClientConfig?: CredentialProviderOptions["parentClientConfig"];
};
Expand Down Expand Up @@ -98,6 +101,7 @@ export const getDefaultRoleAssumer = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -112,6 +116,7 @@ export const getDefaultRoleAssumer = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
// A hack to make sts client uses the credential in current closure.
credentialDefaultProvider: () => async () => closureSourceCreds,
Expand Down Expand Up @@ -165,6 +170,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -179,6 +185,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
region: resolvedRegion,
requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC
/**
* @public
*/
export type STSRoleAssumerOptions = Pick<STSClientConfig, "logger" | "region" | "requestHandler" | "profile"> & {
export type STSRoleAssumerOptions = Pick<
STSClientConfig,
"logger" | "region" | "requestHandler" | "profile" | "userAgentAppId"
> & {
credentialProviderLogger?: Logger;
parentClientConfig?: CredentialProviderOptions["parentClientConfig"];
};
Expand Down Expand Up @@ -95,6 +98,7 @@ export const getDefaultRoleAssumer = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -109,6 +113,7 @@ export const getDefaultRoleAssumer = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
// A hack to make sts client uses the credential in current closure.
credentialDefaultProvider: () => async () => closureSourceCreds,
Expand Down Expand Up @@ -162,6 +167,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -176,6 +182,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
region: resolvedRegion,
requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters):
parameters.logger?.debug("@aws-sdk/credential-provider-cognito-identity - fromCognitoIdentity");
const { GetCredentialsForIdentityCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity");

const fromConfigs = (property: "region" | "profile"): any =>
const fromConfigs = (property: "region" | "profile" | "userAgentAppId"): any =>
parameters.clientConfig?.[property] ??
parameters.parentClientConfig?.[property] ??
awsIdentityProperties?.callerClientConfig?.[property];
Expand All @@ -51,6 +51,7 @@ export function fromCognitoIdentity(parameters: FromCognitoIdentityParameters):
Object.assign({}, parameters.clientConfig ?? {}, {
region: fromConfigs("region"),
profile: fromConfigs("profile"),
userAgentAppId: fromConfigs("userAgentAppId"),
})
)
).send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function fromCognitoIdentityPool({
let provider: CognitoIdentityCredentialProvider = async (awsIdentityProperties?: AwsIdentityProperties) => {
const { GetIdCommand, CognitoIdentityClient } = await import("./loadCognitoIdentity");

const fromConfigs = (property: "region" | "profile"): any =>
const fromConfigs = (property: "region" | "profile" | "userAgentAppId"): any =>
clientConfig?.[property] ??
parentClientConfig?.[property] ??
awsIdentityProperties?.callerClientConfig?.[property];
Expand All @@ -49,6 +49,7 @@ export function fromCognitoIdentityPool({
Object.assign({}, clientConfig ?? {}, {
region: fromConfigs("region"),
profile: fromConfigs("profile"),
userAgentAppId: fromConfigs("userAgentAppId"),
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const resolveSSOCredentials = async ({
Object.assign({}, clientConfig ?? {}, {
logger: clientConfig?.logger ?? parentClientConfig?.logger,
region: clientConfig?.region ?? ssoRegion,
userAgentAppId: clientConfig?.userAgentAppId ?? parentClientConfig?.userAgentAppId,
})
);
let ssoResp: GetRoleCredentialsCommandOutput;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export const fromTemporaryCredentials = (
);

stsClient = new STSClient({
userAgentAppId: callerClientConfig?.userAgentAppId,
...options.clientConfig,
credentials: coalesce(credentialSources),
logger,
Expand Down
4 changes: 2 additions & 2 deletions packages/middleware-user-agent/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const SPACE = " ";

export const UA_NAME_SEPARATOR = "/";

export const UA_NAME_ESCAPE_REGEX = /[^\!\$\%\&\'\*\+\-\.\^\_\`\|\~\d\w]/g;
export const UA_NAME_ESCAPE_REGEX = /[^!$%&'*+\-.^_`|~\w]/g;

export const UA_VALUE_ESCAPE_REGEX = /[^\!\$\%\&\'\*\+\-\.\^\_\`\|\~\d\w\#]/g;
export const UA_VALUE_ESCAPE_REGEX = /[^!$%&'*+\-.^_`|~\w#]/g;

export const UA_ESCAPE_CHAR = "-";
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { requireRequestsFrom } from "@aws-sdk/aws-util-test/src";
import { CodeCatalyst } from "@aws-sdk/client-codecatalyst";
import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { S3 } from "@aws-sdk/client-s3";
import { fromTemporaryCredentials } from "@aws-sdk/credential-providers";
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
import { STSClient } from "@aws-sdk/nested-clients/sts";
import { AwsSdkFeatures } from "@aws-sdk/types";
import { describe, expect, test as it } from "vitest";
import { describe, expect, test as it, vi } from "vitest";

describe("middleware-user-agent", () => {
describe(CodeCatalyst.name, () => {
Expand All @@ -17,7 +20,7 @@ describe("middleware-user-agent", () => {
requireRequestsFrom(client).toMatch({
headers: {
"x-amz-user-agent": /aws-sdk-js\/[\d\.]+/,
"user-agent": /aws-sdk-js\/[\d\.]+ (.*?)lang\/js md\/nodejs\#[\d\.]+ (.*?)api\/(.+)\#[\d\.]+ (.*?)m\//,
"user-agent": /aws-sdk-js\/[\d.]+ (.*?)lang\/js md\/nodejs#[\d.]+ (.*?)api\/(.+)#[\d.]+ (.*?)m\//,
},
});
await client.getUserDetails({
Expand All @@ -27,6 +30,65 @@ describe("middleware-user-agent", () => {
});
});

describe("user agent customization", () => {
it("should propagate the application id configuration to inner clients", async () => {
const s3 = new S3({
region: "us-west-2",
credentials: fromTemporaryCredentials({
masterCredentials: {
accessKeyId: "my-access-key",
secretAccessKey: "my-secretKey",
},
params: {
RoleArn: "arn:aws:iam::1234567890:role/Rigmarole",
},
}),
userAgentAppId: "widget-factory",
});

requireRequestsFrom(s3).toMatch({
headers: {
"user-agent": /app\/widget-factory$/,
},
});

const actual = STSClient.prototype.send;
vi.spyOn(STSClient.prototype, "send").mockImplementation(async function (this: STSClient, ...args) {
if (this instanceof STSClient) {
expect(await this.config.userAgentAppId()).toEqual("widget-factory");
return {
Credentials: {
AccessKeyId: "A",
SecretAccessKey: "S",
},
};
}
return actual.bind(this)(...args);
});

await s3.listBuckets();

expect.assertions(2);
});

it("should allow characters from the set !#$%&'*+-.^_`|~[0-9][A-Za-z]", async () => {
const s3 = new S3({
region: "us-west-2",
userAgentAppId: "!#$%&'*+-.^_`|~abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
});

requireRequestsFrom(s3).toMatch({
headers: {
"user-agent": /app\/!#\$%&'\*\+-\.\^_`\|~abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$/,
},
});

await s3.listBuckets();

expect.hasAssertions();
});
});

describe("features", () => {
it("should detect DDB mapper, account id, and account id mode", async () => {
const client = new DynamoDB({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ describe("userAgentMiddleware", () => {
{ ua: ["api/Service", "1.0.0"], expected: "api/service#1.0.0" },
{ ua: ["#name#", "1.0.0#blah"], expected: "-name-/1.0.0#blah" },
{ ua: ["#prefix#/#name#", "1.0.0#blah"], expected: "-prefix-/-name-#1.0.0#blah" },
{
ua: ["app", "!#$%&'*+-.^_`|~abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"],
expected: "app/!#$%&'*+-.^_`|~abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
},
];
[
{ runtime: "node", sdkUserAgentKey: USER_AGENT },
Expand All @@ -148,9 +152,7 @@ describe("userAgentMiddleware", () => {
});
const handler = middleware(mockNextHandler, {});
await handler({ input: {}, request: new HttpRequest({ headers: {} }) });
expect(mockNextHandler.mock.calls[0][0].request.headers[sdkUserAgentKey]).toEqual(
expect.stringContaining(expected)
);
expect(mockNextHandler.mock.calls[0][0].request.headers[sdkUserAgentKey]).toContain(expected);
});

it(`should include internal metadata, user agent ${ua} customization: ${expected}`, async () => {
Expand All @@ -164,8 +166,8 @@ describe("userAgentMiddleware", () => {
setPartitionInfo({} as any, "a-test-prefix");
const handler = middleware(mockInternalNextHandler, {});
await handler({ input: {}, request: new HttpRequest({ headers: {} }) });
expect(mockInternalNextHandler.mock.calls[0][0].request.headers[sdkUserAgentKey]).toEqual(
expect.stringContaining("a-test-prefix " + expected)
expect(mockInternalNextHandler.mock.calls[0][0].request.headers[sdkUserAgentKey]).toContain(
"a-test-prefix " + expected
);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const userAgentMiddleware =
const customUserAgent = options?.customUserAgent?.map(escapeUserAgent) || [];
const appId = await options.userAgentAppId();
if (appId) {
defaultUserAgent.push(escapeUserAgent([`app/${appId}`]));
defaultUserAgent.push(escapeUserAgent([`app`, `${appId}`]));
}
const prefix = getUserAgentPrefix();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import type { STSClient, STSClientConfig, STSClientResolvedConfig } from "./STSC
/**
* @public
*/
export type STSRoleAssumerOptions = Pick<STSClientConfig, "logger" | "region" | "requestHandler" | "profile"> & {
export type STSRoleAssumerOptions = Pick<
STSClientConfig,
"logger" | "region" | "requestHandler" | "profile" | "userAgentAppId"
> & {
credentialProviderLogger?: Logger;
parentClientConfig?: CredentialProviderOptions["parentClientConfig"];
};
Expand Down Expand Up @@ -98,6 +101,7 @@ export const getDefaultRoleAssumer = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -112,6 +116,7 @@ export const getDefaultRoleAssumer = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
// A hack to make sts client uses the credential in current closure.
credentialDefaultProvider: () => async () => closureSourceCreds,
Expand Down Expand Up @@ -165,6 +170,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (
region,
requestHandler = stsOptions?.parentClientConfig?.requestHandler,
credentialProviderLogger,
userAgentAppId = stsOptions?.parentClientConfig?.userAgentAppId,
} = stsOptions;
const resolvedRegion = await resolveRegion(
region,
Expand All @@ -179,6 +185,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (

stsClient = new STSClient({
...stsOptions,
userAgentAppId,
profile,
region: resolvedRegion,
requestHandler: isCompatibleRequestHandler ? (requestHandler as any) : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ i="https://sts.{Region}.{PartitionResult#dnsSuffix}",
j="tree",
k="error",
l="getAttr",
m={[F]:false,[G]:"String"},
n={[F]:true,"default":false,[G]:"Boolean"},
m={[F]:false,[G]:"string"},
n={[F]:true,"default":false,[G]:"boolean"},
o={[J]:"Endpoint"},
p={[H]:"isSet",[I]:[{[J]:"Region"}]},
q={[J]:"Region"},
Expand Down
5 changes: 4 additions & 1 deletion packages/token-providers/src/getSsoOidcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { FromSsoInit } from "./fromSso";
export const getSsoOidcClient = async (ssoRegion: string, init: FromSsoInit = {}) => {
const { SSOOIDCClient } = await import("@aws-sdk/nested-clients/sso-oidc");

const coalesce = (prop: string) => init.clientConfig?.[prop] ?? init.parentClientConfig?.[prop];

const ssoOidcClient = new SSOOIDCClient(
Object.assign({}, init.clientConfig ?? {}, {
region: ssoRegion ?? init.clientConfig?.region,
logger: init.clientConfig?.logger ?? init.parentClientConfig?.logger,
logger: coalesce("logger"),
userAgentAppId: coalesce("userAgentAppId"),
})
);
return ssoOidcClient;
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type CredentialProviderOptions = {
region?: string | Provider<string>;
profile?: string;
logger?: Logger;
userAgentAppId?(): Promise<string | undefined>;
[key: string]: unknown;
};
};
1 change: 1 addition & 0 deletions packages/types/src/identity/AwsCredentialIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface AwsIdentityProperties {
profile?: string;
region(): Promise<string>;
requestHandler?: RequestHandler<any, any>;
userAgentAppId?(): Promise<string | undefined>;
};
}

Expand Down
Loading