Skip to content

Commit

Permalink
chore(credential-provider-web-identity): update unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP committed Mar 31, 2021
1 parent 52de007 commit b61a6cb
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 132 deletions.
6 changes: 0 additions & 6 deletions packages/credential-provider-web-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ After you have both an IAM role with configured privileges and an application re
providers, you can set up the SDK to get credentials for the IAM role using helper code, as follows:

```javascript
= new AWS.WebIdentityCredentials({
RoleArn: 'arn:aws:iam::<AWS_ACCOUNT_ID>/:role/<WEB_IDENTITY_ROLE_NAME>',
ProviderId: 'graph.facebook.com|www.amazon.com', // this is null for Google
WebIdentityToken: ACCESS_TOKEN
});

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { STSClient, AssumeRoleWithWebIdentityCommand } from "@aws-sdk/client-sts";
import { fromWebToken } from "@aws-sdk/credential-provider-web-identity";
Expand Down
183 changes: 60 additions & 123 deletions packages/credential-provider-web-identity/src/fromTokenFile.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ProviderError } from "@aws-sdk/property-provider";
import { readFileSync } from "fs";

import { fromTokenFile, FromTokenFileInit } from "./fromTokenFile";
import { AssumeRoleWithWebIdentityParams } from "./index";
jest.mock("./fromWebToken", () => ({
fromWebToken: jest.fn().mockReturnValue(() => Promise.resolve(MOCK_CREDS)),
}));
import { fromTokenFile } from "./fromTokenFile";
import { fromWebToken } from "./fromWebToken";

const ENV_TOKEN_FILE = "AWS_WEB_IDENTITY_TOKEN_FILE";
const ENV_ROLE_ARN = "AWS_ROLE_ARN";
Expand Down Expand Up @@ -31,57 +32,6 @@ describe(fromTokenFile.name, () => {
jest.restoreAllMocks();
});

const testRoleAssumerWithWebIdentityNotDefined = async (init: FromTokenFileInit, roleArn: string) => {
try {
// @ts-ignore An argument for 'init' was not provided.
await fromTokenFile(init)();
fail(`Expected error to be thrown`);
} catch (error) {
expect(error).toEqual(
new ProviderError(
`Role Arn '${roleArn}' needs to be assumed with web identity, but no role assumption callback was provided.`,
false
)
);
}
};

const testReadFileSyncError = async (init: FromTokenFileInit) => {
const readFileSyncError = new Error("readFileSyncError");
(readFileSync as jest.Mock).mockImplementation(() => {
throw readFileSyncError;
});
try {
await fromTokenFile(init)();
fail(`Expected error to be thrown`);
} catch (error) {
expect(error).toEqual(readFileSyncError);
}
expect(readFileSync).toHaveBeenCalledTimes(1);
};

const testRoleAssumerWithWebIdentitySuccess = async (init: FromTokenFileInit) => {
const creds = await fromTokenFile(init)();
expect(creds).toEqual(MOCK_CREDS);
expect(readFileSync).toHaveBeenCalledTimes(1);
expect(readFileSync).toHaveBeenCalledWith(mockTokenFile, { encoding: "ascii" });
};

const testRandomValueForRoleSessionName = async (init: FromTokenFileInit) => {
const mockDateNow = Date.now();
const spyDateNow = jest.spyOn(Date, "now").mockReturnValueOnce(mockDateNow);

const creds = await fromTokenFile({
...init,
roleAssumerWithWebIdentity: async (params: AssumeRoleWithWebIdentityParams) => {
expect(params.RoleSessionName).toEqual(`aws-sdk-js-session-${mockDateNow}`);
return MOCK_CREDS;
},
})();
expect(creds).toEqual(MOCK_CREDS);
expect(spyDateNow).toHaveBeenCalledTimes(1);
};

describe("reads config from env", () => {
const original_ENV_TOKEN_FILE = process.env[ENV_TOKEN_FILE];
const original_ENV_ROLE_ARN = process.env[ENV_ROLE_ARN];
Expand All @@ -99,83 +49,70 @@ describe(fromTokenFile.name, () => {
process.env[ENV_ROLE_SESSION_NAME] = original_ENV_ROLE_SESSION_NAME;
});

it("throws if roleAssumerWithWebIdentity is not defined", async () => {
return testRoleAssumerWithWebIdentityNotDefined({}, process.env[ENV_ROLE_ARN]);
it(`passes values to ${fromWebToken.name}`, async () => {
const roleAssumerWithWebIdentity = jest.fn();
const creds = await fromTokenFile({
roleAssumerWithWebIdentity,
})();
expect(creds).toEqual(MOCK_CREDS);
expect(fromWebToken as jest.Mock).toBeCalledTimes(1);
const webTokenInit = (fromWebToken as jest.Mock).mock.calls[0][0];
expect(webTokenInit.webIdentityToken).toBe(mockTokenValue);
expect(webTokenInit.roleSessionName).toBe(mockRoleSessionName);
expect(webTokenInit.roleArn).toBe(mockRoleArn);
expect(webTokenInit.roleAssumerWithWebIdentity).toBe(roleAssumerWithWebIdentity);
});

it("throws if ENV_TOKEN_FILE read from disk failed", async () => {
return testReadFileSyncError({
roleAssumerWithWebIdentity: async (params: AssumeRoleWithWebIdentityParams) => {
return MOCK_CREDS;
},
});
it("prefers init parameters over environmental variables", async () => {
const roleAssumerWithWebIdentity = jest.fn();
const init = {
webIdentityTokenFile: "anotherTokenFile",
roleArn: "anotherRoleArn",
roleSessionName: "anotherRoleSessionName",
roleAssumerWithWebIdentity,
};
const creds = await fromTokenFile(init)();
expect(creds).toEqual(MOCK_CREDS);
expect(fromWebToken as jest.Mock).toBeCalledTimes(1);
const webTokenInit = (fromWebToken as jest.Mock).mock.calls[0][0];
expect(webTokenInit.roleSessionName).toBe(init.roleSessionName);
expect(webTokenInit.roleArn).toBe(init.roleArn);
expect(webTokenInit.roleAssumerWithWebIdentity).toBe(roleAssumerWithWebIdentity);
expect(readFileSync as jest.Mock).toBeCalledTimes(1);
expect((readFileSync as jest.Mock).mock.calls[0][0]).toBe(init.webIdentityTokenFile);
});

it("passes values to roleAssumerWithWebIdentity", async () => {
return testRoleAssumerWithWebIdentitySuccess({
roleAssumerWithWebIdentity: async (params: AssumeRoleWithWebIdentityParams) => {
expect(params.WebIdentityToken).toEqual(mockTokenValue);
expect(params.RoleArn).toEqual(mockRoleArn);
expect(params.RoleSessionName).toEqual(mockRoleSessionName);
return MOCK_CREDS;
},
it("throws if ENV_TOKEN_FILE read from disk failed", async () => {
const readFileSyncError = new Error("readFileSyncError");
(readFileSync as jest.Mock).mockImplementation(() => {
throw readFileSyncError;
});
});

it("generates a random value for RoleSessionName if not available", async () => {
delete process.env[ENV_ROLE_SESSION_NAME];
return testRandomValueForRoleSessionName({});
});
});

describe("reads config from configuration keys", () => {
const original_ENV_TOKEN_FILE = process.env[ENV_TOKEN_FILE];
const original_ENV_ROLE_ARN = process.env[ENV_ROLE_ARN];
const original_ENV_ROLE_SESSION_NAME = process.env[ENV_ROLE_SESSION_NAME];

beforeAll(() => {
delete process.env[ENV_TOKEN_FILE];
delete process.env[ENV_ROLE_ARN];
delete process.env[ENV_ROLE_SESSION_NAME];
});

afterAll(() => {
process.env[ENV_TOKEN_FILE] = original_ENV_TOKEN_FILE;
process.env[ENV_ROLE_ARN] = original_ENV_ROLE_ARN;
process.env[ENV_ROLE_SESSION_NAME] = original_ENV_ROLE_SESSION_NAME;
});

it("throws if roleAssumerWithWebIdentity is not defined", async () => {
return testRoleAssumerWithWebIdentityNotDefined({ roleArn: mockRoleArn }, mockRoleArn);
try {
await fromTokenFile({ roleAssumerWithWebIdentity: jest.fn() })();
fail(`Expected error to be thrown`);
} catch (error) {
expect(error).toEqual(readFileSyncError);
}
expect(readFileSync).toHaveBeenCalledTimes(1);
});

it("throws if web_identity_token_file read from disk failed", async () => {
return testReadFileSyncError({
webIdentityTokenFile: mockTokenFile,
roleArn: mockRoleArn,
roleSessionName: mockRoleSessionName,
roleAssumerWithWebIdentity: async (params: AssumeRoleWithWebIdentityParams) => {
return MOCK_CREDS;
},
});
});

it("passes values to roleAssumerWithWebIdentity", async () => {
return testRoleAssumerWithWebIdentitySuccess({
webIdentityTokenFile: mockTokenFile,
roleArn: mockRoleArn,
roleSessionName: mockRoleSessionName,
roleAssumerWithWebIdentity: async (params: AssumeRoleWithWebIdentityParams) => {
expect(params.WebIdentityToken).toEqual(mockTokenValue);
expect(params.RoleArn).toEqual(mockRoleArn);
expect(params.RoleSessionName).toEqual(mockRoleSessionName);
return MOCK_CREDS;
},
const readFileSyncError = new Error("readFileSyncError");
(readFileSync as jest.Mock).mockImplementation(() => {
throw readFileSyncError;
});
});

it("generates a random value for RoleSessionName if not available", async () => {
return testRandomValueForRoleSessionName({ webIdentityTokenFile: mockTokenFile, roleArn: mockRoleArn });
try {
await fromTokenFile({
webIdentityTokenFile: mockTokenFile,
roleArn: mockRoleArn,
roleSessionName: mockRoleSessionName,
roleAssumerWithWebIdentity: jest.fn(),
})();
fail(`Expected error to be thrown`);
} catch (error) {
expect(error).toEqual(readFileSyncError);
}
expect(readFileSync).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ENV_TOKEN_FILE = "AWS_WEB_IDENTITY_TOKEN_FILE";
const ENV_ROLE_ARN = "AWS_ROLE_ARN";
const ENV_ROLE_SESSION_NAME = "AWS_ROLE_SESSION_NAME";

export interface FromTokenFileInit extends Partial<FromWebTokenInit> {
export interface FromTokenFileInit extends Partial<Omit<FromWebTokenInit, "webIdentityToken">> {
/**
* File location of where the `OIDC` token is stored.
*/
Expand All @@ -24,6 +24,6 @@ export const fromTokenFile = (init: FromTokenFileInit): CredentialProvider => {
...init,
webIdentityToken: readFileSync(webIdentityTokenFile ?? process.env[ENV_TOKEN_FILE]!, { encoding: "ascii" }),
roleArn: roleArn ?? process.env[ENV_ROLE_ARN]!,
roleSessionName: roleSessionName ?? process.env[ENV_ROLE_SESSION_NAME] ?? `aws-sdk-js-session-${Date.now()}`,
roleSessionName: roleSessionName ?? process.env[ENV_ROLE_SESSION_NAME],
});
};
84 changes: 84 additions & 0 deletions packages/credential-provider-web-identity/src/fromWebToken.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ProviderError } from "@aws-sdk/property-provider";

import { fromWebToken } from "./fromWebToken";

const mockToken = "exampletoken";
const mockRoleArn = "mockRoleArn";
const mockRoleSessionName = "mockRoleSessionName";
const mockPolicy = "mockPolicy";
const mockPolicyArns = [{ arn: "policyArn" }];
const mockProviderId = "mockProviderId";
const mockDurationSeconds = 7200;
const MOCK_CREDS = {
accessKeyId: "accessKeyId",
secretAccessKey: "secretAccessKey",
sessionToken: "sessionToken",
};

describe("fromWebToken", () => {
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
it("throws if roleAssumerWithWebIdentity is not defined", async () => {
try {
await fromWebToken({
webIdentityToken: mockToken,
roleArn: mockRoleArn,
})();
fail(`Expected error to be thrown`);
} catch (error) {
expect(error).toEqual(
new ProviderError(
`Role Arn '${mockRoleArn}' needs to be assumed with web identity, but no role assumption callback was provided.`,
false
)
);
}
});

it("passes values to roleAssumerWithWebIdentity", async () => {
expect.assertions(2);
const init = {
webIdentityToken: mockToken,
roleArn: mockRoleArn,
roleSessionName: mockRoleSessionName,
providerId: mockProviderId,
policyArns: mockPolicyArns,
policy: mockPolicy,
durationSeconds: mockDurationSeconds,
};
const creds = await fromWebToken({
...init,
roleAssumerWithWebIdentity: async (params) => {
expect(params).toMatchObject({
WebIdentityToken: mockToken,
RoleArn: mockRoleArn,
RoleSessionName: mockRoleSessionName,
ProviderId: mockProviderId,
PolicyArns: mockPolicyArns,
Policy: mockPolicy,
DurationSeconds: mockDurationSeconds,
});
return MOCK_CREDS;
},
})();
expect(creds).toEqual(MOCK_CREDS);
});

it("generates a random value for RoleSessionName if not available", async () => {
const mockDateNow = Date.now();
const spyDateNow = jest.spyOn(Date, "now").mockReturnValueOnce(mockDateNow);

const creds = await fromWebToken({
webIdentityToken: mockToken,
roleArn: mockRoleArn,
roleAssumerWithWebIdentity: async (params) => {
expect(params.RoleSessionName).toEqual(`aws-sdk-js-session-${mockDateNow}`);
return MOCK_CREDS;
},
})();
expect(creds).toEqual(MOCK_CREDS);
expect(spyDateNow).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const fromWebToken = (init: FromWebTokenInit): CredentialProvider => () =

return roleAssumerWithWebIdentity({
RoleArn: roleArn,
RoleSessionName: roleSessionName ?? "web-identity",
RoleSessionName: roleSessionName ?? `aws-sdk-js-session-${Date.now()}`,
WebIdentityToken: webIdentityToken,
ProviderId: providerId,
PolicyArns: policyArns,
Expand Down

0 comments on commit b61a6cb

Please sign in to comment.