Skip to content
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

chore(credential-providers): add credential attribution #6546

Merged
merged 8 commits into from
Oct 8, 2024
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
12 changes: 10 additions & 2 deletions clients/client-sts/src/defaultStsRoleAssumers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// smithy-typescript generated code
// Please do not touch this file. It's generated from template in:
// https://github.com/aws/aws-sdk-js-v3/blob/main/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/sts-client-defaultStsRoleAssumers.ts
import { setCredentialFeature } from "@aws-sdk/core/client";
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types";

Expand Down Expand Up @@ -118,7 +119,7 @@ export const getDefaultRoleAssumer = (

const accountId = getAccountIdFromAssumedRoleUser(AssumedRoleUser);

return {
const credentials = {
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
Expand All @@ -127,6 +128,8 @@ export const getDefaultRoleAssumer = (
...((Credentials as any).CredentialScope && { credentialScope: (Credentials as any).CredentialScope }),
...(accountId && { accountId }),
};
setCredentialFeature(credentials, "CREDENTIALS_STS_ASSUME_ROLE", "i");
return credentials;
};
};

Expand Down Expand Up @@ -174,7 +177,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (

const accountId = getAccountIdFromAssumedRoleUser(AssumedRoleUser);

return {
const credentials = {
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
Expand All @@ -183,6 +186,11 @@ export const getDefaultRoleAssumerWithWebIdentity = (
...((Credentials as any).CredentialScope && { credentialScope: (Credentials as any).CredentialScope }),
...(accountId && { accountId }),
};
if (accountId) {
setCredentialFeature(credentials, "RESOLVED_ACCOUNT_ID", "T");
}
setCredentialFeature(credentials, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
return credentials;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { AwsCredentialIdentity, Logger, Provider } from "@smithy/types";

Expand Down Expand Up @@ -115,7 +116,7 @@ export const getDefaultRoleAssumer = (

const accountId = getAccountIdFromAssumedRoleUser(AssumedRoleUser);

return {
const credentials = {
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
Expand All @@ -124,6 +125,8 @@ export const getDefaultRoleAssumer = (
...((Credentials as any).CredentialScope && { credentialScope: (Credentials as any).CredentialScope }),
...(accountId && { accountId }),
};
setCredentialFeature(credentials, "CREDENTIALS_STS_ASSUME_ROLE", "i");
return credentials;
};
};

Expand Down Expand Up @@ -171,7 +174,7 @@ export const getDefaultRoleAssumerWithWebIdentity = (

const accountId = getAccountIdFromAssumedRoleUser(AssumedRoleUser);

return {
const credentials = {
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
Expand All @@ -180,6 +183,11 @@ export const getDefaultRoleAssumerWithWebIdentity = (
...((Credentials as any).CredentialScope && { credentialScope: (Credentials as any).CredentialScope }),
...(accountId && { accountId }),
};
if (accountId) {
setCredentialFeature(credentials, "RESOLVED_ACCOUNT_ID", "T");
}
setCredentialFeature(credentials, "CREDENTIALS_STS_ASSUME_ROLE_WEB_ID", "k");
return credentials;
};
};

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/submodules/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./emitWarningIfUnsupportedVersion";
export * from "./setCredentialFeature";
export * from "./setFeature";
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { AttributedAwsCredentialIdentity } from "@aws-sdk/types";

import { setCredentialFeature } from "./setCredentialFeature";

describe(setCredentialFeature.name, () => {
it("should create the data path if it does't exist", () => {
const credentials = {
accessKeyId: "",
secretAccessKey: "",
} as AttributedAwsCredentialIdentity;
expect(setCredentialFeature(credentials, "CREDENTIALS_CODE", "e")).toEqual({
accessKeyId: "",
secretAccessKey: "",
$source: {
CREDENTIALS_CODE: "e",
},
});
});

it("should track a set of features", () => {
const credentials = {
accessKeyId: "",
secretAccessKey: "",
} as AttributedAwsCredentialIdentity;

setCredentialFeature(credentials, "CREDENTIALS_CODE", "e");
setCredentialFeature(credentials, "CREDENTIALS_ENV_VARS", "g");
// it ignores duplicates.
setCredentialFeature(credentials, "CREDENTIALS_ENV_VARS", "g");

expect(setCredentialFeature(credentials, "CREDENTIALS_CODE", "e")).toEqual({
accessKeyId: "",
secretAccessKey: "",
$source: {
CREDENTIALS_CODE: "e",
CREDENTIALS_ENV_VARS: "g",
},
});
});
});
18 changes: 18 additions & 0 deletions packages/core/src/submodules/client/setCredentialFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { AttributedAwsCredentialIdentity, AwsSdkCredentialsFeatures } from "@aws-sdk/types";

/**
* @internal
*
* @returns the credentials with source feature attribution.
*/
export function setCredentialFeature<F extends keyof AwsSdkCredentialsFeatures>(
credentials: AttributedAwsCredentialIdentity,
feature: F,
value: AwsSdkCredentialsFeatures[F]
): AttributedAwsCredentialIdentity {
if (!credentials.$source) {
credentials.$source = {};
}
credentials.$source![feature] = value;
return credentials;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { AttributedAwsCredentialIdentity } from "@aws-sdk/types";
import {
doesIdentityRequireRefresh,
isIdentityExpired,
Expand Down Expand Up @@ -102,9 +104,11 @@ export interface AwsSdkSigV4AuthResolvedConfig {
export const resolveAwsSdkSigV4Config = <T>(
config: T & AwsSdkSigV4AuthInputConfig & AwsSdkSigV4PreviouslyResolved
): T & AwsSdkSigV4AuthResolvedConfig => {
let isUserSupplied = false;
// Normalize credentials
let normalizedCreds: AwsCredentialIdentityProvider | undefined;
if (config.credentials) {
isUserSupplied = true;
normalizedCreds = memoizeIdentityProvider(config.credentials, isIdentityExpired, doesIdentityRequireRefresh);
}
if (!normalizedCreds) {
Expand Down Expand Up @@ -218,7 +222,12 @@ export const resolveAwsSdkSigV4Config = <T>(
...config,
systemClockOffset,
signingEscapePath,
credentials: normalizedCreds!,
credentials: isUserSupplied
? async () =>
normalizedCreds!().then((creds: AttributedAwsCredentialIdentity) =>
setCredentialFeature(creds, "CREDENTIALS_CODE", "e")
)
: normalizedCreds!,
signer,
};
};
Expand Down
1 change: 1 addition & 0 deletions packages/credential-provider-env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "*",
"@aws-sdk/types": "*",
"@smithy/property-provider": "^3.1.7",
"@smithy/types": "^3.5.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/credential-provider-env/src/fromEnv.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ describe(fromEnv.name, () => {
sessionToken: mockSessionToken,
expiration: new Date(mockExpiration),
accountId: mockAccountId,
$source: {
CREDENTIALS_ENV_VARS: "g",
},
});
});

Expand All @@ -44,6 +47,9 @@ describe(fromEnv.name, () => {
expect(receivedCreds).toStrictEqual({
accessKeyId: mockAccessKeyId,
secretAccessKey: mockSecretAccessKey,
$source: {
CREDENTIALS_ENV_VARS: "g",
},
});
});

Expand Down
9 changes: 6 additions & 3 deletions packages/credential-provider-env/src/fromEnv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { setCredentialFeature } from "@aws-sdk/core/client";
import type { AttributedAwsCredentialIdentity, CredentialProviderOptions } from "@aws-sdk/types";
import { CredentialsProviderError } from "@smithy/property-provider";
import { AwsCredentialIdentityProvider } from "@smithy/types";

Expand Down Expand Up @@ -48,14 +49,16 @@ export const fromEnv =
const accountId: string | undefined = process.env[ENV_ACCOUNT_ID];

if (accessKeyId && secretAccessKey) {
return {
const credentials = {
accessKeyId,
secretAccessKey,
...(sessionToken && { sessionToken }),
...(expiry && { expiration: new Date(expiry) }),
...(credentialScope && { credentialScope }),
...(accountId && { accountId }),
};
} as AttributedAwsCredentialIdentity;
setCredentialFeature(credentials, "CREDENTIALS_ENV_VARS", "g");
return credentials;
}

throw new CredentialsProviderError("Unable to find environment variable credentials.", { logger: init?.logger });
Expand Down
1 change: 1 addition & 0 deletions packages/credential-provider-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "*",
"@aws-sdk/types": "*",
"@smithy/fetch-http-handler": "^3.2.9",
"@smithy/node-http-handler": "^3.2.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import { CredentialsProviderError } from "@smithy/property-provider";
import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@smithy/types";
Expand Down Expand Up @@ -81,7 +82,7 @@ Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
}
try {
const result = await requestHandler.handle(request);
return getCredentials(result.response);
return getCredentials(result.response).then((creds) => setCredentialFeature(creds, "CREDENTIALS_HTTP", "z"));
} catch (e: unknown) {
throw new CredentialsProviderError(String(e), { logger: options.logger });
}
Expand Down
1 change: 1 addition & 0 deletions packages/credential-provider-ini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "*",
"@aws-sdk/credential-provider-env": "*",
"@aws-sdk/credential-provider-http": "*",
"@aws-sdk/credential-provider-process": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { resolveProfileData } from "./resolveProfileData";

jest.mock("@aws-sdk/client-sts", () => {
return {
getDefaultRoleAssumer: jest.fn().mockReturnValue(() => {}),
getDefaultRoleAssumer: jest.fn().mockReturnValue(async () => ({})),
};
});
jest.mock("@smithy/shared-ini-file-loader");
Expand Down Expand Up @@ -98,7 +98,7 @@ describe(resolveAssumeRoleCredentials.name, () => {
const mockProfiles = { [mockProfileName]: {} };
const mockOptions = {
mfaCodeProvider: jest.fn(),
roleAssumer: jest.fn().mockReturnValue(mockCreds),
roleAssumer: jest.fn().mockReturnValue(Promise.resolve(mockCreds)),
roleAssumerWithWebIdentity: jest.fn(),
};
const mockCredentialSource = "mockCredentialSource";
Expand All @@ -120,7 +120,7 @@ describe(resolveAssumeRoleCredentials.name, () => {
beforeEach(() => {
(getProfileName as jest.Mock).mockReturnValue(mockProfileName);
(resolveProfileData as jest.Mock).mockResolvedValue(mockSourceCredsFromProfile);
(resolveCredentialSource as jest.Mock).mockReturnValue(() => () => Promise.resolve(mockSourceCredsFromCredential));
(resolveCredentialSource as jest.Mock).mockReturnValue(async () => async () => mockSourceCredsFromCredential);
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { CredentialsProviderError } from "@smithy/property-provider";
import { getProfileName } from "@smithy/shared-ini-file-loader";
import { AwsCredentialIdentity, IniSection, Logger, ParsedIniData, Profile } from "@smithy/types";
Expand Down Expand Up @@ -159,7 +160,7 @@ export const resolveAssumeRoleCredentials = async (
* can use its role_arn instead of redundantly needing another role_arn at
* this final layer.
*/
return sourceCredsProvider;
return sourceCredsProvider.then((creds) => setCredentialFeature(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o"));
} else {
const params: AssumeRoleParams = {
RoleArn: data.role_arn!,
Expand All @@ -181,7 +182,9 @@ export const resolveAssumeRoleCredentials = async (
}

const sourceCreds = await sourceCredsProvider;
return options.roleAssumer!(sourceCreds, params);
return options.roleAssumer!(sourceCreds, params).then((creds) =>
setCredentialFeature(creds, "CREDENTIALS_PROFILE_SOURCE_PROFILE", "o")
);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CredentialProviderOptions } from "@aws-sdk/types";
import { setCredentialFeature } from "@aws-sdk/core/client";
import type { AwsCredentialIdentity, CredentialProviderOptions } from "@aws-sdk/types";
import { chain, CredentialsProviderError } from "@smithy/property-provider";
import { AwsCredentialIdentityProvider, Logger } from "@smithy/types";

Expand All @@ -21,17 +22,17 @@ export const resolveCredentialSource = (
const { fromHttp } = await import("@aws-sdk/credential-provider-http");
const { fromContainerMetadata } = await import("@smithy/credential-provider-imds");
logger?.debug("@aws-sdk/credential-provider-ini - credential_source is EcsContainer");
return chain(fromHttp(options ?? {}), fromContainerMetadata(options));
return async () => chain(fromHttp(options ?? {}), fromContainerMetadata(options))().then(setNamedProvider);
},
Ec2InstanceMetadata: async (options?: CredentialProviderOptions) => {
logger?.debug("@aws-sdk/credential-provider-ini - credential_source is Ec2InstanceMetadata");
const { fromInstanceMetadata } = await import("@smithy/credential-provider-imds");
return fromInstanceMetadata(options);
return async () => fromInstanceMetadata(options)().then(setNamedProvider);
},
Environment: async (options?: CredentialProviderOptions) => {
logger?.debug("@aws-sdk/credential-provider-ini - credential_source is Environment");
const { fromEnv } = await import("@aws-sdk/credential-provider-env");
return fromEnv(options);
return async () => fromEnv(options)().then(setNamedProvider);
},
};
if (credentialSource in sourceProvidersMap) {
Expand All @@ -44,3 +45,6 @@ export const resolveCredentialSource = (
);
}
};

const setNamedProvider = (creds: AwsCredentialIdentity) =>
setCredentialFeature(creds, "CREDENTIALS_PROFILE_NAMED_PROVIDER", "p");
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { Credentials, Profile } from "@aws-sdk/types";

import { FromIniInit } from "./fromIni";
Expand All @@ -23,5 +24,5 @@ export const resolveProcessCredentials = async (options: FromIniInit, profile: s
fromProcess({
...options,
profile,
})()
})().then((creds) => setCredentialFeature(creds, "CREDENTIALS_PROFILE_PROCESS", "v"))
);
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ describe(resolveProfileData.name, () => {
(resolveSsoCredentials as jest.Mock).mockImplementation(() => Promise.resolve(mockCreds));
const receivedCreds = await resolveProfileData(mockProfileName, mockProfiles, mockOptions);
expect(receivedCreds).toStrictEqual(mockCreds);
expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName, mockOptions);
expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName, {}, mockOptions);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const resolveProfileData = async (
}

if (isSsoProfile(data)) {
return await resolveSsoCredentials(profileName, options);
return await resolveSsoCredentials(profileName, data, options);
}

// If the profile cannot be parsed or contains neither static credentials
Expand Down
Loading
Loading