1
0
Fork 0
mirror of synced 2026-06-05 18:35:14 +00:00

chore: add remaining tests

This commit is contained in:
Tom Keller 2022-10-18 17:58:04 -07:00
commit 49bbbeb420
No known key found for this signature in database
GPG key ID: E1806C1EE1663B8D
12 changed files with 798 additions and 209 deletions

2
.gitignore generated vendored
View file

@ -30,6 +30,8 @@ jspm_packages/
*.tgz
.yarn-integrity
.cache
.vscode
.env
!/.projenrc.js
/test-reports/
junit.xml

4
.projen/deps.json generated
View file

@ -4,6 +4,10 @@
"name": "@aws-sdk/credential-provider-env",
"type": "build"
},
{
"name": "@aws-sdk/property-provider",
"type": "build"
},
{
"name": "@jest/globals",
"type": "build"

View file

@ -10,6 +10,7 @@ const project = new GitHubActionTypeScriptProject({
'@aws-sdk/credential-provider-env',
'aws-sdk-client-mock',
'@jest/globals',
'@aws-sdk/property-provider',
],
deps: ['@aws-sdk/client-sts@^3'],
name: 'configure-aws-credentials',
@ -21,6 +22,7 @@ const project = new GitHubActionTypeScriptProject({
authorUrl: 'https://aws.amazon.com',
packageManager: NodePackageManager.NPM,
sampleCode: false,
gitignore: ['.vscode', '.env'],
actionMetadata: {
name: '"Configure AWS Credentials" Action for GitHub Actions',
description: 'Configures AWS credentials for use in subsequent steps in a GitHub Action workflow',
@ -135,6 +137,7 @@ const project = new GitHubActionTypeScriptProject({
target: 'es2022',
module: 'commonjs',
outDir: 'build',
noUnusedLocals: false,
},
},
prettier: true,

165
package-lock.json generated
View file

@ -15,7 +15,7 @@
},
"devDependencies": {
"@aws-sdk/credential-provider-env": "^3.186.0",
"@aws-sdk/credential-providers": "^3.188.0",
"@aws-sdk/property-provider": "^3.188.0",
"@jest/globals": "^29.1.2",
"@types/jest": "^29.1.2",
"@types/node": "^14",
@ -179,51 +179,6 @@
"node": ">= 12.0.0"
}
},
"node_modules/@aws-sdk/client-cognito-identity": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.188.0.tgz",
"integrity": "sha512-lpl8yxAjER3xUDYQxJR8oyQzfcw4TQbsgTY+kZzVTIsHVLIuZfJoWBNA/ONWkmuCOylH8jEu5jcZ3a45fyx5fg==",
"dev": true,
"dependencies": {
"@aws-crypto/sha256-browser": "2.0.0",
"@aws-crypto/sha256-js": "2.0.0",
"@aws-sdk/client-sts": "3.188.0",
"@aws-sdk/config-resolver": "3.188.0",
"@aws-sdk/credential-provider-node": "3.188.0",
"@aws-sdk/fetch-http-handler": "3.188.0",
"@aws-sdk/hash-node": "3.188.0",
"@aws-sdk/invalid-dependency": "3.188.0",
"@aws-sdk/middleware-content-length": "3.188.0",
"@aws-sdk/middleware-host-header": "3.188.0",
"@aws-sdk/middleware-logger": "3.188.0",
"@aws-sdk/middleware-recursion-detection": "3.188.0",
"@aws-sdk/middleware-retry": "3.188.0",
"@aws-sdk/middleware-serde": "3.188.0",
"@aws-sdk/middleware-signing": "3.188.0",
"@aws-sdk/middleware-stack": "3.188.0",
"@aws-sdk/middleware-user-agent": "3.188.0",
"@aws-sdk/node-config-provider": "3.188.0",
"@aws-sdk/node-http-handler": "3.188.0",
"@aws-sdk/protocol-http": "3.188.0",
"@aws-sdk/smithy-client": "3.188.0",
"@aws-sdk/types": "3.188.0",
"@aws-sdk/url-parser": "3.188.0",
"@aws-sdk/util-base64-browser": "3.188.0",
"@aws-sdk/util-base64-node": "3.188.0",
"@aws-sdk/util-body-length-browser": "3.188.0",
"@aws-sdk/util-body-length-node": "3.188.0",
"@aws-sdk/util-defaults-mode-browser": "3.188.0",
"@aws-sdk/util-defaults-mode-node": "3.188.0",
"@aws-sdk/util-user-agent-browser": "3.188.0",
"@aws-sdk/util-user-agent-node": "3.188.0",
"@aws-sdk/util-utf8-browser": "3.188.0",
"@aws-sdk/util-utf8-node": "3.188.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@aws-sdk/client-sso": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.188.0.tgz",
@ -325,21 +280,6 @@
"node": ">= 12.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-cognito-identity": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.188.0.tgz",
"integrity": "sha512-PK+a5wiQT/xz3CVVXulkYBdvejrHSmQ/JI38jW0GPQaa6zWobk1kkNOLUTqcpAaiYsyZUm+3guDUSobdHWnJ2A==",
"dev": true,
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.188.0",
"@aws-sdk/property-provider": "3.188.0",
"@aws-sdk/types": "3.188.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-env": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.188.0.tgz",
@ -448,32 +388,6 @@
"node": ">= 12.0.0"
}
},
"node_modules/@aws-sdk/credential-providers": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.188.0.tgz",
"integrity": "sha512-RNf0nolOqPKGUzq2wZHo0qYz14r9W+liX5BeUK9+fqhZtYY1LOoL+Jb3SFCIMPSnYekmMfEZ7JSU00QgMrBsmA==",
"dev": true,
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.188.0",
"@aws-sdk/client-sso": "3.188.0",
"@aws-sdk/client-sts": "3.188.0",
"@aws-sdk/credential-provider-cognito-identity": "3.188.0",
"@aws-sdk/credential-provider-env": "3.188.0",
"@aws-sdk/credential-provider-imds": "3.188.0",
"@aws-sdk/credential-provider-ini": "3.188.0",
"@aws-sdk/credential-provider-node": "3.188.0",
"@aws-sdk/credential-provider-process": "3.188.0",
"@aws-sdk/credential-provider-sso": "3.188.0",
"@aws-sdk/credential-provider-web-identity": "3.188.0",
"@aws-sdk/property-provider": "3.188.0",
"@aws-sdk/shared-ini-file-loader": "3.188.0",
"@aws-sdk/types": "3.188.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/@aws-sdk/fetch-http-handler": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.188.0.tgz",
@ -8909,48 +8823,6 @@
"tslib": "^2.3.1"
}
},
"@aws-sdk/client-cognito-identity": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.188.0.tgz",
"integrity": "sha512-lpl8yxAjER3xUDYQxJR8oyQzfcw4TQbsgTY+kZzVTIsHVLIuZfJoWBNA/ONWkmuCOylH8jEu5jcZ3a45fyx5fg==",
"dev": true,
"requires": {
"@aws-crypto/sha256-browser": "2.0.0",
"@aws-crypto/sha256-js": "2.0.0",
"@aws-sdk/client-sts": "3.188.0",
"@aws-sdk/config-resolver": "3.188.0",
"@aws-sdk/credential-provider-node": "3.188.0",
"@aws-sdk/fetch-http-handler": "3.188.0",
"@aws-sdk/hash-node": "3.188.0",
"@aws-sdk/invalid-dependency": "3.188.0",
"@aws-sdk/middleware-content-length": "3.188.0",
"@aws-sdk/middleware-host-header": "3.188.0",
"@aws-sdk/middleware-logger": "3.188.0",
"@aws-sdk/middleware-recursion-detection": "3.188.0",
"@aws-sdk/middleware-retry": "3.188.0",
"@aws-sdk/middleware-serde": "3.188.0",
"@aws-sdk/middleware-signing": "3.188.0",
"@aws-sdk/middleware-stack": "3.188.0",
"@aws-sdk/middleware-user-agent": "3.188.0",
"@aws-sdk/node-config-provider": "3.188.0",
"@aws-sdk/node-http-handler": "3.188.0",
"@aws-sdk/protocol-http": "3.188.0",
"@aws-sdk/smithy-client": "3.188.0",
"@aws-sdk/types": "3.188.0",
"@aws-sdk/url-parser": "3.188.0",
"@aws-sdk/util-base64-browser": "3.188.0",
"@aws-sdk/util-base64-node": "3.188.0",
"@aws-sdk/util-body-length-browser": "3.188.0",
"@aws-sdk/util-body-length-node": "3.188.0",
"@aws-sdk/util-defaults-mode-browser": "3.188.0",
"@aws-sdk/util-defaults-mode-node": "3.188.0",
"@aws-sdk/util-user-agent-browser": "3.188.0",
"@aws-sdk/util-user-agent-node": "3.188.0",
"@aws-sdk/util-utf8-browser": "3.188.0",
"@aws-sdk/util-utf8-node": "3.188.0",
"tslib": "^2.3.1"
}
},
"@aws-sdk/client-sso": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.188.0.tgz",
@ -9043,18 +8915,6 @@
"tslib": "^2.3.1"
}
},
"@aws-sdk/credential-provider-cognito-identity": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.188.0.tgz",
"integrity": "sha512-PK+a5wiQT/xz3CVVXulkYBdvejrHSmQ/JI38jW0GPQaa6zWobk1kkNOLUTqcpAaiYsyZUm+3guDUSobdHWnJ2A==",
"dev": true,
"requires": {
"@aws-sdk/client-cognito-identity": "3.188.0",
"@aws-sdk/property-provider": "3.188.0",
"@aws-sdk/types": "3.188.0",
"tslib": "^2.3.1"
}
},
"@aws-sdk/credential-provider-env": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.188.0.tgz",
@ -9142,29 +9002,6 @@
"tslib": "^2.3.1"
}
},
"@aws-sdk/credential-providers": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.188.0.tgz",
"integrity": "sha512-RNf0nolOqPKGUzq2wZHo0qYz14r9W+liX5BeUK9+fqhZtYY1LOoL+Jb3SFCIMPSnYekmMfEZ7JSU00QgMrBsmA==",
"dev": true,
"requires": {
"@aws-sdk/client-cognito-identity": "3.188.0",
"@aws-sdk/client-sso": "3.188.0",
"@aws-sdk/client-sts": "3.188.0",
"@aws-sdk/credential-provider-cognito-identity": "3.188.0",
"@aws-sdk/credential-provider-env": "3.188.0",
"@aws-sdk/credential-provider-imds": "3.188.0",
"@aws-sdk/credential-provider-ini": "3.188.0",
"@aws-sdk/credential-provider-node": "3.188.0",
"@aws-sdk/credential-provider-process": "3.188.0",
"@aws-sdk/credential-provider-sso": "3.188.0",
"@aws-sdk/credential-provider-web-identity": "3.188.0",
"@aws-sdk/property-provider": "3.188.0",
"@aws-sdk/shared-ini-file-loader": "3.188.0",
"@aws-sdk/types": "3.188.0",
"tslib": "^2.3.1"
}
},
"@aws-sdk/fetch-http-handler": {
"version": "3.188.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.188.0.tgz",

2
package.json generated
View file

@ -26,7 +26,7 @@
},
"devDependencies": {
"@aws-sdk/credential-provider-env": "^3.186.0",
"@aws-sdk/credential-providers": "^3.188.0",
"@aws-sdk/property-provider": "^3.188.0",
"@jest/globals": "^29.1.2",
"@types/jest": "^29.1.2",
"@types/node": "^14",

View file

@ -2,7 +2,7 @@ import assert from 'assert';
import fs from 'fs';
import path from 'path';
import * as core from '@actions/core';
import { AssumeRoleCommandInput, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
import { AssumeRoleCommand, AssumeRoleCommandInput, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
import { errorMessage, getStsClient, isDefined } from './helpers';
const SANITIZATION_CHARACTER = '_';
@ -94,15 +94,16 @@ export async function assumeRole(params: assumeRoleParams) {
const keys = Object.keys(commonAssumeRoleParams) as Array<keyof typeof commonAssumeRoleParams>;
keys.forEach((k) => commonAssumeRoleParams[k] === undefined && delete commonAssumeRoleParams[k]);
let assumeRoleCommand: AssumeRoleWithWebIdentityCommand;
const sts = getStsClient(region);
switch (true) {
case !!webIdentityToken: {
delete commonAssumeRoleParams.Tags;
assumeRoleCommand = new AssumeRoleWithWebIdentityCommand({
...commonAssumeRoleParams,
WebIdentityToken: webIdentityToken,
});
break;
return sts.send(
new AssumeRoleWithWebIdentityCommand({
...commonAssumeRoleParams,
WebIdentityToken: webIdentityToken,
})
);
}
case !!webIdentityTokenFile: {
core.debug(
@ -117,21 +118,20 @@ export async function assumeRole(params: assumeRoleParams) {
}
try {
const widt = await fs.promises.readFile(webIdentityTokenFilePath, 'utf8');
const widt = fs.readFileSync(webIdentityTokenFilePath, 'utf8');
delete commonAssumeRoleParams.Tags;
assumeRoleCommand = new AssumeRoleWithWebIdentityCommand({
...commonAssumeRoleParams,
WebIdentityToken: widt,
});
return await sts.send(
new AssumeRoleWithWebIdentityCommand({
...commonAssumeRoleParams,
WebIdentityToken: widt,
})
);
} catch (error) {
throw new Error(`Web identity token file could not be read: ${errorMessage(error)}`);
}
break;
}
default:
throw new Error('No web identity token or web identity token file provided.');
default: {
return sts.send(new AssumeRoleCommand({ ...commonAssumeRoleParams }));
}
}
const sts = getStsClient(region);
return sts.send(assumeRoleCommand);
}

View file

@ -27,6 +27,7 @@ export async function cleanup() {
core.setFailed(errorMessage(error));
}
}
/* istanbul ignore next */
if (require.main === module) {
(async () => {
await cleanup();

View file

@ -51,19 +51,19 @@ function exportRegion(region: string) {
async function exportAccountId(region: string, maskAccountId?: boolean) {
// Get the AWS account ID
const client = getStsClient(region, USER_AGENT);
const identity = (await client.send(new GetCallerIdentityCommand({}))).Account;
if (!identity) {
const identity = await client.send(new GetCallerIdentityCommand({}));
const accountId = identity.Account;
if (!accountId) {
throw new Error('Could not get Account ID from STS. Did you set credentials?');
}
if (maskAccountId) {
core.setSecret(identity);
} else {
core.setOutput('aws-account-id', identity);
core.setSecret(accountId);
}
return identity;
core.setOutput('aws-account-id', accountId);
return accountId;
}
function loadCredentials() {
async function loadCredentials() {
// Previously, this function forced the SDK to re-resolve credentials with the default provider chain.
//
// This action typically sets credentials in the environment via environment variables. The SDK never refreshed those
@ -109,25 +109,19 @@ export async function run() {
(core.getInput('mask-aws-account-id', { required: false }) || 'true').toLowerCase() === 'true';
const roleToAssume = core.getInput('role-to-assume', { required: false });
const roleExternalId = core.getInput('role-external-id', { required: false });
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false';
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
// This wraps the logic for deciding if we should rely on the GH OIDC provider since we may need to reference
// the decision in a few differennt places. Consolidating it here makes the logic clearer elsewhere.
const useGitHubOIDCProvider = () => {
// The assumption here is that self-hosted runners won't be populating the `ACTIONS_ID_TOKEN_REQUEST_TOKEN`
// environment variable and they won't be providing a web idenity token file or access key either.
// V2 of the action might relax this a bit and create an explicit precedence for these so that customers
// can provide as much info as they want and we will follow the established credential loading precedence.
return !!(roleToAssume && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && !AccessKeyId && !webIdentityTokenFile);
};
const useGitHubOIDCProvider =
!!roleToAssume && !!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && !AccessKeyId && !webIdentityTokenFile;
const roleDurationSeconds =
parseInt(core.getInput('role-duration-seconds', { required: false })) ||
(SessionToken && SESSION_ROLE_DURATION) ||
(useGitHubOIDCProvider() && DEFAULT_ROLE_DURATION_FOR_OIDC_ROLES) ||
(useGitHubOIDCProvider && DEFAULT_ROLE_DURATION_FOR_OIDC_ROLES) ||
MAX_ACTION_RUNTIME;
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false';
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
@ -153,7 +147,7 @@ export async function run() {
// The only way to assume the role is via GitHub's OIDC provider.
let sourceAccountId: string;
let webIdentityToken: string;
if (useGitHubOIDCProvider()) {
if (useGitHubOIDCProvider) {
webIdentityToken = await core.getIDToken(audience);
// We don't validate the credentials here because we don't have them yet when using OIDC.
} else {
@ -203,6 +197,7 @@ export async function run() {
}
}
/* istanbul ignore next */
if (require.main === module) {
(async () => {
await run();

View file

@ -18,10 +18,10 @@ describe('Configure AWS Credentials', () => {
beforeEach(() => {
jest.resetModules();
jest.spyOn(core, 'exportVariable');
jest.spyOn(core, 'setSecret');
jest.spyOn(core, 'setOutput');
jest.spyOn(core, 'setFailed');
jest.spyOn(core, 'exportVariable').mockImplementation();
jest.spyOn(core, 'setSecret').mockImplementation();
jest.spyOn(core, 'setOutput').mockImplementation();
jest.spyOn(core, 'setFailed').mockImplementation();
process.env = { ...OLD_ENV, ...ACTION_ENVIRONMENT_VARIABLES };
});

747
test/index.test.ts Normal file
View file

@ -0,0 +1,747 @@
import assert from 'assert';
import * as core from '@actions/core';
import {
AssumeRoleCommand,
AssumeRoleWithWebIdentityCommand,
GetCallerIdentityCommand,
STSClient,
} from '@aws-sdk/client-sts';
import { fromEnv } from '@aws-sdk/credential-provider-env';
import { CredentialsProviderError } from '@aws-sdk/property-provider';
import { mockClient } from 'aws-sdk-client-mock';
import { withsleep, reset } from '../src/helpers';
import { run } from '../src/index';
// #region
const FAKE_ACCESS_KEY_ID = 'MY-AWS-ACCESS-KEY-ID';
const FAKE_SECRET_ACCESS_KEY = 'MY-AWS-SECRET-ACCESS-KEY';
const FAKE_SESSION_TOKEN = 'MY-AWS-SESSION-TOKEN';
const FAKE_STS_ACCESS_KEY_ID = 'STS-AWS-ACCESS-KEY-ID';
const FAKE_STS_SECRET_ACCESS_KEY = 'STS-AWS-SECRET-ACCESS-KEY';
const FAKE_STS_SESSION_TOKEN = 'STS-AWS-SESSION-TOKEN';
const FAKE_REGION = 'fake-region-1';
const FAKE_ACCOUNT_ID = '123456789012';
const FAKE_ROLE_ACCOUNT_ID = '111111111111';
const ROLE_NAME = 'MY-ROLE';
const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE';
const ENVIRONMENT_VARIABLE_OVERRIDES = {
SHOW_STACK_TRACE: 'false',
GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
GITHUB_WORKFLOW: 'MY-WORKFLOW-ID',
GITHUB_ACTION: 'MY-ACTION-NAME',
GITHUB_ACTOR: 'MY-USERNAME[bot]',
GITHUB_SHA: 'MY-COMMIT-ID',
GITHUB_REF: 'MY-BRANCH',
GITHUB_WORKSPACE: '/home/github',
};
const GITHUB_ACTOR_SANITIZED = 'MY-USERNAME_bot_';
const CREDS_INPUTS = {
'aws-access-key-id': FAKE_ACCESS_KEY_ID,
'aws-secret-access-key': FAKE_SECRET_ACCESS_KEY,
};
const DEFAULT_INPUTS = {
...CREDS_INPUTS,
'aws-session-token': FAKE_SESSION_TOKEN,
'aws-region': FAKE_REGION,
'mask-aws-account-id': 'TRUE',
};
const ASSUME_ROLE_INPUTS = { ...CREDS_INPUTS, 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION };
// #endregion
const mockedSTS = mockClient(STSClient);
function mockGetInput(requestResponse: Record<string, string>) {
return function (name: string, _options: unknown) {
return requestResponse[name];
};
}
jest.mock('fs', () => ({
...jest.requireActual('fs'),
existsSync: jest.fn(() => true),
readFileSync: jest.fn(() => 'testpayload'),
}));
jest.mock('@aws-sdk/credential-provider-env', () => ({
// This is the actual implementation in the SDK ^_^
fromEnv: jest.fn().mockImplementation(() => async () => {
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
const sessionToken = process.env.AWS_SESSION_TOKEN;
const expiration = process.env.AWS_CREDENTIAL_EXPIRATION;
return {
accessKeyId,
secretAccessKey,
sessionToken,
expiration,
};
}),
}));
describe('Configure AWS Credentials', () => {
const OLD_ENV = process.env;
beforeEach(() => {
jest.resetModules();
process.env = { ...OLD_ENV, ...ENVIRONMENT_VARIABLE_OVERRIDES };
jest.clearAllMocks();
mockedSTS.reset();
(fromEnv as jest.Mock).mockReset();
jest.spyOn(core, 'getIDToken').mockImplementation(() => Promise.resolve('testtoken'));
jest.spyOn(core, 'exportVariable').mockImplementation();
jest.spyOn(core, 'setSecret').mockImplementation();
jest.spyOn(core, 'setOutput').mockImplementation();
jest.spyOn(core, 'setFailed').mockImplementation();
jest.spyOn(core, 'debug').mockImplementation();
(fromEnv as jest.Mock)
.mockImplementationOnce(() => async () => ({
accessKeyId: FAKE_ACCESS_KEY_ID,
secretAccessKey: FAKE_SECRET_ACCESS_KEY,
}))
.mockImplementationOnce(() => async () => ({
accessKeyId: FAKE_STS_ACCESS_KEY_ID,
secretAccessKey: FAKE_STS_SECRET_ACCESS_KEY,
}));
mockedSTS
.on(GetCallerIdentityCommand)
.resolvesOnce({ Account: FAKE_ACCOUNT_ID })
.resolvesOnce({ Account: FAKE_ROLE_ACCOUNT_ID });
mockedSTS.on(AssumeRoleCommand).resolves({
Credentials: {
AccessKeyId: FAKE_STS_ACCESS_KEY_ID,
SecretAccessKey: FAKE_STS_SECRET_ACCESS_KEY,
SessionToken: FAKE_STS_SESSION_TOKEN,
Expiration: new Date(8640000000000000),
},
});
mockedSTS.on(AssumeRoleWithWebIdentityCommand).resolves({
Credentials: {
AccessKeyId: FAKE_STS_ACCESS_KEY_ID,
SecretAccessKey: FAKE_STS_SECRET_ACCESS_KEY,
SessionToken: FAKE_STS_SESSION_TOKEN,
Expiration: new Date(8640000000000000),
},
});
withsleep(() => {
return Promise.resolve();
});
});
afterEach(() => {
process.env = OLD_ENV;
reset();
});
test('exports env vars', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(DEFAULT_INPUTS));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(5);
expect(core.setSecret).toHaveBeenCalledTimes(4);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', FAKE_SESSION_TOKEN);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SESSION_TOKEN);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION);
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
});
test('action fails when github env vars are not set', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
delete process.env.GITHUB_SHA;
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'Missing required environment variables. Are you running in GitHub Actions?'
);
});
test('action does not require GITHUB_REF env var', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(DEFAULT_INPUTS));
delete process.env.GITHUB_REF;
await run();
expect(core.setFailed).toHaveBeenCalledTimes(0);
});
test('hosted runners can pull creds from a self-hosted environment', async () => {
const mockInputs = { 'aws-region': FAKE_REGION };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(2);
expect(core.setSecret).toHaveBeenCalledTimes(1);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION);
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
});
test('action with no accessible credentials fails', async () => {
const mockInputs = { 'aws-region': FAKE_REGION };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
(fromEnv as jest.Mock).mockReset();
(fromEnv as jest.Mock).mockImplementation(() => async () => {
throw new CredentialsProviderError('test');
});
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers'
);
});
test('action with empty credentials fails', async () => {
const mockInputs = { 'aws-region': FAKE_REGION };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
(fromEnv as jest.Mock).mockReset();
(fromEnv as jest.Mock).mockImplementation(
() => async () => Promise.resolve({ accessKeyId: '', secretAccessKey: '' })
);
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'Credentials could not be loaded, please check your action inputs: Access key ID empty after loading credentials'
);
});
test('action fails when credentials are not set in the SDK correctly', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(DEFAULT_INPUTS));
(fromEnv as jest.Mock).mockReset();
(fromEnv as jest.Mock).mockImplementationOnce(() => async () => Promise.resolve({ accessKeyId: '123' }));
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action'
);
});
test('session token is optional', async () => {
const mockInputs = { ...CREDS_INPUTS, 'aws-region': 'eu-west-1' };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(4);
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
});
test('existing env var creds are cleared', async () => {
const mockInputs = { ...CREDS_INPUTS, 'aws-region': 'eu-west-1' };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
process.env.AWS_ACCESS_KEY_ID = 'foo';
process.env.AWS_SECRET_ACCESS_KEY = 'bar';
process.env.AWS_SESSION_TOKEN = 'helloworld';
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(5);
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
});
test('validates region name', async () => {
const mockInputs = { ...CREDS_INPUTS, 'aws-region': '$AWS_REGION' };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
await run();
expect(core.setFailed).toHaveBeenCalledWith('Region is not valid: $AWS_REGION');
});
test('throws error if access key id exists but missing secret access key', async () => {
const inputsWIthoutSecretKey = { ...DEFAULT_INPUTS };
//@ts-expect-error deleting a required property to test failure condition
delete inputsWIthoutSecretKey['aws-secret-access-key'];
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(inputsWIthoutSecretKey));
await run();
expect(core.setFailed).toHaveBeenCalledWith(
"'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided"
);
});
test('can opt out of masking account ID', async () => {
const mockInputs = { ...CREDS_INPUTS, 'aws-region': 'us-east-1', 'mask-aws-account-id': 'false' };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(4);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'us-east-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'us-east-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledTimes(2);
});
test('error is caught by core.setFailed and caught', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(DEFAULT_INPUTS));
mockedSTS.reset();
mockedSTS.on(GetCallerIdentityCommand).rejects();
await run();
expect(core.setFailed).toHaveBeenCalled();
});
test('basic role assumption exports', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(1);
expect(core.exportVariable).toHaveBeenCalledTimes(7);
expect(core.setSecret).toHaveBeenCalledTimes(7);
expect(core.setOutput).toHaveBeenCalledTimes(2);
// first the source credentials are exported and masked
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_ACCOUNT_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(1, 'AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenNthCalledWith(2, 'AWS_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenNthCalledWith(3, 'AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(4, 'AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'aws-account-id', FAKE_ACCOUNT_ID);
// then the role credentials are exported and masked
expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenNthCalledWith(5, FAKE_STS_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenNthCalledWith(6, FAKE_STS_SESSION_TOKEN);
expect(core.setSecret).toHaveBeenNthCalledWith(7, FAKE_ROLE_ACCOUNT_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(5, 'AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(6, 'AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenNthCalledWith(7, 'AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN);
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'aws-account-id', FAKE_ROLE_ACCOUNT_ID);
});
test('assume role can pull source credentials from self-hosted environment', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(1);
expect(core.exportVariable).toHaveBeenCalledTimes(5);
expect(core.setSecret).toHaveBeenCalledTimes(5);
expect(core.setOutput).toHaveBeenCalledTimes(2);
// first the source account is exported and masked
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCOUNT_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(1, 'AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenNthCalledWith(2, 'AWS_REGION', FAKE_REGION);
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'aws-account-id', FAKE_ACCOUNT_ID);
// then the role credentials are exported and masked
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_SESSION_TOKEN);
expect(core.setSecret).toHaveBeenNthCalledWith(5, FAKE_ROLE_ACCOUNT_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(3, 'AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenNthCalledWith(4, 'AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenNthCalledWith(5, 'AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN);
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'aws-account-id', FAKE_ROLE_ACCOUNT_ID);
});
test('role assumption tags', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('role assumption duration provided', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'role-duration-seconds': '5' }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 5,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('role assumption session name provided', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'role-session-name': 'MySessionName' }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'MySessionName',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('sets durationSeconds to one hour when session token provided and no duration is provided', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'aws-session-token': FAKE_SESSION_TOKEN }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('sets durationSeconds to one 6 hours no session token or duration is provided', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('role name provided instead of ARN', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...CREDS_INPUTS, 'role-to-assume': ROLE_NAME, 'aws-region': FAKE_REGION }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: 'arn:aws:iam::123456789012:role/MY-ROLE',
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('web identity token file provided with absolute path', async () => {
jest.spyOn(core, 'getInput').mockImplementation(
mockGetInput({
'role-to-assume': ROLE_ARN,
'aws-region': FAKE_REGION,
'web-identity-token-file': '/fake/token/file',
})
);
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand)[0].args[0].input).toEqual({
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
WebIdentityToken: 'testpayload',
});
});
test('web identity token file provided with relative path', async () => {
jest.spyOn(core, 'getInput').mockImplementation(
mockGetInput({
'role-to-assume': ROLE_ARN,
'aws-region': FAKE_REGION,
'web-identity-token-file': 'fake/token/file',
})
);
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand)[0].args[0].input).toEqual({
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
WebIdentityToken: 'testpayload',
});
});
test('only role arn and region provided to use GH OIDC Token', async () => {
process.env.GITHUB_ACTIONS = 'true';
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand)[0].args[0].input).toEqual({
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
RoleSessionName: 'GitHubActions',
DurationSeconds: 3600,
WebIdentityToken: 'testtoken',
});
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_STS_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SESSION_TOKEN);
});
test('GH OIDC With custom role duration', async () => {
const CUSTOM_ROLE_DURATION = '1234';
process.env.GITHUB_ACTIONS = 'true';
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
jest.spyOn(core, 'getInput').mockImplementation(
mockGetInput({
'role-to-assume': ROLE_ARN,
'aws-region': FAKE_REGION,
'role-duration-seconds': CUSTOM_ROLE_DURATION,
})
);
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand)[0].args[0].input).toEqual({
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
RoleSessionName: 'GitHubActions',
DurationSeconds: parseInt(CUSTOM_ROLE_DURATION),
WebIdentityToken: 'testtoken',
});
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_STS_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SESSION_TOKEN);
});
test('role assumption fails after maximum trials using OIDC provider', async () => {
process.env.GITHUB_ACTIONS = 'true';
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION }));
mockedSTS.reset();
mockedSTS.on(AssumeRoleWithWebIdentityCommand).rejects();
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(12);
});
test('role external ID provided', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'role-external-id': 'abcdef' }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
ExternalId: 'abcdef',
});
});
test('workflow name sanitized in role assumption tags', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
process.env = {
...process.env,
GITHUB_WORKFLOW:
'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA',
};
const sanitizedWorkflowName =
'Workflow__________+_ -./:;<=>?@____________1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa';
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: sanitizedWorkflowName },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('skip tagging provided as true', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'role-skip-session-tagging': 'true' }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 21600,
Tags: undefined,
});
});
test('skip tagging provided as false', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'role-skip-session-tagging': 'false' }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 21600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('skip tagging not provided', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS }));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)[0].args[0].input).toEqual({
RoleArn: ROLE_ARN,
RoleSessionName: 'GitHubActions',
DurationSeconds: 21600,
Tags: [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW },
{ Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION },
{ Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED },
{ Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA },
{ Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF },
],
});
});
test('masks variables before exporting', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
const maskedValues: string[] = [];
const publicFields = ['AWS_REGION', 'AWS_DEFAULT_REGION'];
jest.spyOn(core, 'setSecret').mockImplementation((secret) => {
maskedValues.push(secret);
});
jest.spyOn(core, 'exportVariable').mockImplementation((name, value) => {
if (!maskedValues.includes(value) && !publicFields.includes(name)) {
throw new Error(value + ' for variable ' + name + ' is not masked yet!');
}
process.env[name] = value;
});
await run();
expect(core.exportVariable).toReturn();
});
});

2
tsconfig.dev.json generated
View file

@ -15,7 +15,7 @@
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"resolveJsonModule": true,
"strict": true,

2
tsconfig.json generated
View file

@ -17,7 +17,7 @@
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"resolveJsonModule": true,
"strict": true,