implement last changes for vNext

This commit is contained in:
peterwoodworth
2023-07-03 16:13:19 -07:00
parent 256d740e71
commit aa2675f083
8 changed files with 858 additions and 677 deletions
+1
View File
@@ -107,6 +107,7 @@ overrides:
'@typescript-eslint/prefer-includes': [warn] '@typescript-eslint/prefer-includes': [warn]
dot-notation: [off] dot-notation: [off]
'@typescript-eslint/dot-notation': [error] '@typescript-eslint/dot-notation': [error]
'@typescript-eslint/no-explicit-any': [off]
'@typescript-eslint/consistent-type-exports': [warn] '@typescript-eslint/consistent-type-exports': [warn]
'@typescript-eslint/consistent-type-imports': [warn] '@typescript-eslint/consistent-type-imports': [warn]
'@typescript-eslint/no-base-to-string': [error] '@typescript-eslint/no-base-to-string': [error]
+12 -3
View File
@@ -34,9 +34,6 @@ inputs:
description: The audience to use for the OIDC provider description: The audience to use for the OIDC provider
required: false required: false
default: sts.amazonaws.com default: sts.amazonaws.com
disable-oidc:
description: Strictly disable action from attempting to fetch credentials with OIDC
required: false
http-proxy: http-proxy:
description: 'Proxy to use for the AWS SDK agent' description: 'Proxy to use for the AWS SDK agent'
required: false required: false
@@ -61,6 +58,18 @@ inputs:
managed-session-policies: managed-session-policies:
description: 'List of managed session policies' description: 'List of managed session policies'
required: false required: false
output-credentials:
description: Whether to set credentials as step output
required: false
unset-current-credentials:
description: Whether to unset the existing credentials in your runner
required: false
disable-retry:
description: Whether to disable the retry and backoff mechanism when the assume role call fails
required: false
retry-max-attempts:
description: The maximum number of attempts it will attempt to retry the assume role call
required: false
outputs: outputs:
aws-account-id: aws-account-id:
description: The AWS account ID for the provided credentials description: The AWS account ID for the provided credentials
Generated Vendored
+23 -4
View File
@@ -17583,14 +17583,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isDefined = exports.errorMessage = exports.retryAndBackoff = exports.reset = exports.withsleep = exports.defaultSleep = exports.sanitizeGitHubVariables = exports.exportAccountId = exports.exportRegion = exports.exportCredentials = void 0; exports.isDefined = exports.errorMessage = exports.retryAndBackoff = exports.reset = exports.withsleep = exports.defaultSleep = exports.sanitizeGitHubVariables = exports.exportAccountId = exports.exportRegion = exports.unsetCredentials = exports.exportCredentials = void 0;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const client_sts_1 = __nccwpck_require__(2209); const client_sts_1 = __nccwpck_require__(2209);
const MAX_TAG_VALUE_LENGTH = 256; const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_'; const SANITIZATION_CHARACTER = '_';
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
// Setting the credentials as secrets masks them in Github Actions logs // Setting the credentials as secrets masks them in Github Actions logs
function exportCredentials(creds) { function exportCredentials(creds, outputCredentials) {
if (creds?.AccessKeyId) { if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId); core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId); core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
@@ -17607,8 +17607,27 @@ function exportCredentials(creds) {
// clear session token from previous credentials action // clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', ''); core.exportVariable('AWS_SESSION_TOKEN', '');
} }
if (outputCredentials) {
if (creds?.AccessKeyId) {
core.setOutput('aws-access-key-id', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.setOutput('aws-secret-access-key', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.setOutput('aws-session-token', creds.SessionToken);
}
}
} }
exports.exportCredentials = exportCredentials; exports.exportCredentials = exportCredentials;
function unsetCredentials() {
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_REGION', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
}
exports.unsetCredentials = unsetCredentials;
function exportRegion(region) { function exportRegion(region) {
core.exportVariable('AWS_DEFAULT_REGION', region); core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region); core.exportVariable('AWS_REGION', region);
@@ -17652,7 +17671,7 @@ function reset() {
} }
exports.reset = reset; exports.reset = reset;
// Retries the promise with exponential backoff if the error isRetryable up to maxRetries time. // Retries the promise with exponential backoff if the error isRetryable up to maxRetries time.
async function retryAndBackoff(fn, isRetryable, retries = 0, maxRetries = 12, base = 50) { async function retryAndBackoff(fn, isRetryable, maxRetries = 12, retries = 0, base = 50) {
try { try {
return await fn(); return await fn();
} }
@@ -17666,7 +17685,7 @@ async function retryAndBackoff(fn, isRetryable, retries = 0, maxRetries = 12, ba
if (retries === maxRetries) { if (retries === maxRetries) {
throw err; throw err;
} }
return await retryAndBackoff(fn, isRetryable, retries, maxRetries, base); return await retryAndBackoff(fn, isRetryable, maxRetries, retries, base);
} }
} }
exports.retryAndBackoff = retryAndBackoff; exports.retryAndBackoff = retryAndBackoff;
+3 -2
View File
@@ -1,6 +1,7 @@
import type { Credentials } from '@aws-sdk/client-sts'; import type { Credentials } from '@aws-sdk/client-sts';
import type { CredentialsClient } from './CredentialsClient'; import type { CredentialsClient } from './CredentialsClient';
export declare function exportCredentials(creds?: Partial<Credentials>): void; export declare function exportCredentials(creds?: Partial<Credentials>, outputCredentials?: boolean): void;
export declare function unsetCredentials(): void;
export declare function exportRegion(region: string): void; export declare function exportRegion(region: string): void;
export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean): Promise<string>; export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean): Promise<string>;
export declare function sanitizeGitHubVariables(name: string): string; export declare function sanitizeGitHubVariables(name: string): string;
@@ -8,7 +9,7 @@ export declare function defaultSleep(ms: number): Promise<unknown>;
declare let sleep: typeof defaultSleep; declare let sleep: typeof defaultSleep;
export declare function withsleep(s: typeof sleep): void; export declare function withsleep(s: typeof sleep): void;
export declare function reset(): void; export declare function reset(): void;
export declare function retryAndBackoff<T>(fn: () => Promise<T>, isRetryable: boolean, retries?: number, maxRetries?: number, base?: number): Promise<T>; export declare function retryAndBackoff<T>(fn: () => Promise<T>, isRetryable: boolean, maxRetries?: number, retries?: number, base?: number): Promise<T>;
export declare function errorMessage(error: unknown): string; export declare function errorMessage(error: unknown): string;
export declare function isDefined<T>(i: T | undefined | null): i is T; export declare function isDefined<T>(i: T | undefined | null): i is T;
export {}; export {};
Generated Vendored
+36 -10
View File
@@ -241,14 +241,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isDefined = exports.errorMessage = exports.retryAndBackoff = exports.reset = exports.withsleep = exports.defaultSleep = exports.sanitizeGitHubVariables = exports.exportAccountId = exports.exportRegion = exports.exportCredentials = void 0; exports.isDefined = exports.errorMessage = exports.retryAndBackoff = exports.reset = exports.withsleep = exports.defaultSleep = exports.sanitizeGitHubVariables = exports.exportAccountId = exports.exportRegion = exports.unsetCredentials = exports.exportCredentials = void 0;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const client_sts_1 = __nccwpck_require__(2209); const client_sts_1 = __nccwpck_require__(2209);
const MAX_TAG_VALUE_LENGTH = 256; const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_'; const SANITIZATION_CHARACTER = '_';
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
// Setting the credentials as secrets masks them in Github Actions logs // Setting the credentials as secrets masks them in Github Actions logs
function exportCredentials(creds) { function exportCredentials(creds, outputCredentials) {
if (creds?.AccessKeyId) { if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId); core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId); core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
@@ -265,8 +265,27 @@ function exportCredentials(creds) {
// clear session token from previous credentials action // clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', ''); core.exportVariable('AWS_SESSION_TOKEN', '');
} }
if (outputCredentials) {
if (creds?.AccessKeyId) {
core.setOutput('aws-access-key-id', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.setOutput('aws-secret-access-key', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.setOutput('aws-session-token', creds.SessionToken);
}
}
} }
exports.exportCredentials = exportCredentials; exports.exportCredentials = exportCredentials;
function unsetCredentials() {
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_REGION', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
}
exports.unsetCredentials = unsetCredentials;
function exportRegion(region) { function exportRegion(region) {
core.exportVariable('AWS_DEFAULT_REGION', region); core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region); core.exportVariable('AWS_REGION', region);
@@ -310,7 +329,7 @@ function reset() {
} }
exports.reset = reset; exports.reset = reset;
// Retries the promise with exponential backoff if the error isRetryable up to maxRetries time. // Retries the promise with exponential backoff if the error isRetryable up to maxRetries time.
async function retryAndBackoff(fn, isRetryable, retries = 0, maxRetries = 12, base = 50) { async function retryAndBackoff(fn, isRetryable, maxRetries = 12, retries = 0, base = 50) {
try { try {
return await fn(); return await fn();
} }
@@ -324,7 +343,7 @@ async function retryAndBackoff(fn, isRetryable, retries = 0, maxRetries = 12, ba
if (retries === maxRetries) { if (retries === maxRetries) {
throw err; throw err;
} }
return await retryAndBackoff(fn, isRetryable, retries, maxRetries, base); return await retryAndBackoff(fn, isRetryable, maxRetries, retries, base);
} }
} }
exports.retryAndBackoff = retryAndBackoff; exports.retryAndBackoff = retryAndBackoff;
@@ -398,12 +417,18 @@ async function run() {
const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false'; const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false';
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true'; const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
const proxyServer = core.getInput('http-proxy', { required: false }); const proxyServer = core.getInput('http-proxy', { required: false });
const disableOIDC = core.getInput('disable-oidc', { required: false });
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false }); const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false });
const managedSessionPolicies = []; const managedSessionPolicies = [];
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false'; const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true'; const roleChaining = roleChainingInput.toLowerCase() === 'true';
const outputCredentialsInput = core.getInput('output-credentials', { required: false }) || 'false';
const outputCredentials = outputCredentialsInput.toLowerCase() === 'true';
const unsetCurrentCredentialsInput = core.getInput('unset-current-credentials', { required: false }) || 'false';
const unsetCurrentCredentials = unsetCurrentCredentialsInput.toLowerCase() === 'true';
const disableRetryInput = core.getInput('disable-retry', { required: false }) || 'false';
const disableRetry = disableRetryInput.toLowerCase() === 'true';
const maxRetries = parseInt(core.getInput('retry-max-attempts', { required: false })) || 12;
for (const managedSessionPolicy of managedSessionPoliciesInput) { for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({ arn: managedSessionPolicy }); managedSessionPolicies.push({ arn: managedSessionPolicy });
} }
@@ -412,11 +437,10 @@ async function run() {
// The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted. // The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted.
// This is necessary to authenticate with OIDC, but not strictly set just for OIDC. If it is not set and all other // This is necessary to authenticate with OIDC, but not strictly set just for OIDC. If it is not set and all other
// checks pass, it is likely but not guaranteed that the user needs but lacks this permission in their workflow. // checks pass, it is likely but not guaranteed that the user needs but lacks this permission in their workflow.
// So, we will log a warning when it is the only piece absent, as well as add an opportunity to manually disable the entire check. // So, we will log a warning when it is the only piece absent
if (!!roleToAssume && if (!!roleToAssume &&
!webIdentityTokenFile && !webIdentityTokenFile &&
!AccessKeyId && !AccessKeyId &&
!disableOIDC &&
!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] && !process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!roleChaining) { !roleChaining) {
core.info('It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission?'); core.info('It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission?');
@@ -425,9 +449,11 @@ async function run() {
!!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] && !!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!AccessKeyId && !AccessKeyId &&
!webIdentityTokenFile && !webIdentityTokenFile &&
!disableOIDC &&
!roleChaining); !roleChaining);
}; };
if (unsetCurrentCredentials) {
(0, helpers_1.unsetCredentials)();
}
if (!region.match(REGION_REGEX)) { if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`); throw new Error(`Region is not valid: ${region}`);
} }
@@ -477,9 +503,9 @@ async function run() {
inlineSessionPolicy, inlineSessionPolicy,
managedSessionPolicies, managedSessionPolicies,
}); });
}, true); }, !disableRetry, maxRetries);
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser.AssumedRoleId}`); core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser.AssumedRoleId}`);
(0, helpers_1.exportCredentials)(roleCredentials.Credentials); (0, helpers_1.exportCredentials)(roleCredentials.Credentials, outputCredentials);
// We need to validate the credentials in 2 of our use-cases // We need to validate the credentials in 2 of our use-cases
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable // First: self-hosted runners. If the GITHUB_ACTIONS environment variable
// is set to `true` then we are NOT in a self-hosted runner. // is set to `true` then we are NOT in a self-hosted runner.
+23 -3
View File
@@ -8,7 +8,7 @@ const SANITIZATION_CHARACTER = '_';
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
// Setting the credentials as secrets masks them in Github Actions logs // Setting the credentials as secrets masks them in Github Actions logs
export function exportCredentials(creds?: Partial<Credentials>) { export function exportCredentials(creds?: Partial<Credentials>, outputCredentials?: boolean) {
if (creds?.AccessKeyId) { if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId); core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId); core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
@@ -26,6 +26,26 @@ export function exportCredentials(creds?: Partial<Credentials>) {
// clear session token from previous credentials action // clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', ''); core.exportVariable('AWS_SESSION_TOKEN', '');
} }
if (outputCredentials) {
if (creds?.AccessKeyId) {
core.setOutput('aws-access-key-id', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.setOutput('aws-secret-access-key', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.setOutput('aws-session-token', creds.SessionToken);
}
}
}
export function unsetCredentials() {
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_REGION', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
} }
export function exportRegion(region: string) { export function exportRegion(region: string) {
@@ -74,8 +94,8 @@ export function reset() {
export async function retryAndBackoff<T>( export async function retryAndBackoff<T>(
fn: () => Promise<T>, fn: () => Promise<T>,
isRetryable: boolean, isRetryable: boolean,
retries = 0,
maxRetries = 12, maxRetries = 12,
retries = 0,
base = 50 base = 50
): Promise<T> { ): Promise<T> {
try { try {
@@ -90,7 +110,7 @@ export async function retryAndBackoff<T>(
if (retries === maxRetries) { if (retries === maxRetries) {
throw err; throw err;
} }
return await retryAndBackoff(fn, isRetryable, retries, maxRetries, base); return await retryAndBackoff(fn, isRetryable, maxRetries, retries, base);
} }
} }
+40 -21
View File
@@ -1,7 +1,14 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import { assumeRole } from './assumeRole'; import { assumeRole } from './assumeRole';
import { CredentialsClient } from './CredentialsClient'; import { CredentialsClient } from './CredentialsClient';
import { errorMessage, retryAndBackoff, exportRegion, exportCredentials, exportAccountId } from './helpers'; import {
errorMessage,
retryAndBackoff,
exportRegion,
exportCredentials,
exportAccountId,
unsetCredentials,
} from './helpers';
const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds) const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds)
const ROLE_SESSION_NAME = 'GitHubActions'; const ROLE_SESSION_NAME = 'GitHubActions';
@@ -26,12 +33,18 @@ export async function run() {
const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false'; const roleSkipSessionTaggingInput = core.getInput('role-skip-session-tagging', { required: false }) || 'false';
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true'; const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
const proxyServer = core.getInput('http-proxy', { required: false }); const proxyServer = core.getInput('http-proxy', { required: false });
const disableOIDC = core.getInput('disable-oidc', { required: false });
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false }); const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false });
const managedSessionPolicies: any[] = []; const managedSessionPolicies: any[] = [];
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false'; const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true'; const roleChaining = roleChainingInput.toLowerCase() === 'true';
const outputCredentialsInput = core.getInput('output-credentials', { required: false }) || 'false';
const outputCredentials = outputCredentialsInput.toLowerCase() === 'true';
const unsetCurrentCredentialsInput = core.getInput('unset-current-credentials', { required: false }) || 'false';
const unsetCurrentCredentials = unsetCurrentCredentialsInput.toLowerCase() === 'true';
const disableRetryInput = core.getInput('disable-retry', { required: false }) || 'false';
const disableRetry = disableRetryInput.toLowerCase() === 'true';
const maxRetries = parseInt(core.getInput('retry-max-attempts', { required: false })) || 12;
for (const managedSessionPolicy of managedSessionPoliciesInput) { for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({ arn: managedSessionPolicy }); managedSessionPolicies.push({ arn: managedSessionPolicy });
} }
@@ -41,12 +54,11 @@ export async function run() {
// The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted. // The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted.
// This is necessary to authenticate with OIDC, but not strictly set just for OIDC. If it is not set and all other // This is necessary to authenticate with OIDC, but not strictly set just for OIDC. If it is not set and all other
// checks pass, it is likely but not guaranteed that the user needs but lacks this permission in their workflow. // checks pass, it is likely but not guaranteed that the user needs but lacks this permission in their workflow.
// So, we will log a warning when it is the only piece absent, as well as add an opportunity to manually disable the entire check. // So, we will log a warning when it is the only piece absent
if ( if (
!!roleToAssume && !!roleToAssume &&
!webIdentityTokenFile && !webIdentityTokenFile &&
!AccessKeyId && !AccessKeyId &&
!disableOIDC &&
!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] && !process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!roleChaining !roleChaining
) { ) {
@@ -59,11 +71,14 @@ export async function run() {
!!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] && !!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!AccessKeyId && !AccessKeyId &&
!webIdentityTokenFile && !webIdentityTokenFile &&
!disableOIDC &&
!roleChaining !roleChaining
); );
}; };
if (unsetCurrentCredentials) {
unsetCredentials();
}
if (!region.match(REGION_REGEX)) { if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`); throw new Error(`Region is not valid: ${region}`);
} }
@@ -101,23 +116,27 @@ export async function run() {
// Get role credentials if configured to do so // Get role credentials if configured to do so
if (roleToAssume) { if (roleToAssume) {
const roleCredentials = await retryAndBackoff(async () => { const roleCredentials = await retryAndBackoff(
return assumeRole({ async () => {
credentialsClient, return assumeRole({
sourceAccountId, credentialsClient,
roleToAssume, sourceAccountId,
roleExternalId, roleToAssume,
roleDuration, roleExternalId,
roleSessionName, roleDuration,
roleSkipSessionTagging, roleSessionName,
webIdentityTokenFile, roleSkipSessionTagging,
webIdentityToken, webIdentityTokenFile,
inlineSessionPolicy, webIdentityToken,
managedSessionPolicies, inlineSessionPolicy,
}); managedSessionPolicies,
}, true); });
},
!disableRetry,
maxRetries
);
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser!.AssumedRoleId!}`); core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser!.AssumedRoleId!}`);
exportCredentials(roleCredentials.Credentials); exportCredentials(roleCredentials.Credentials, outputCredentials);
// We need to validate the credentials in 2 of our use-cases // We need to validate the credentials in 2 of our use-cases
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable // First: self-hosted runners. If the GITHUB_ACTIONS environment variable
// is set to `true` then we are NOT in a self-hosted runner. // is set to `true` then we are NOT in a self-hosted runner.
+86
View File
@@ -23,6 +23,10 @@ const FAKE_ACCOUNT_ID = '123456789012';
const FAKE_ROLE_ACCOUNT_ID = '111111111111'; const FAKE_ROLE_ACCOUNT_ID = '111111111111';
const ROLE_NAME = 'MY-ROLE'; const ROLE_NAME = 'MY-ROLE';
const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE'; const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE';
const MANAGED_SESSION_POLICY_INPUT = [
'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess',
'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess',
];
const ENVIRONMENT_VARIABLE_OVERRIDES = { const ENVIRONMENT_VARIABLE_OVERRIDES = {
SHOW_STACK_TRACE: 'false', SHOW_STACK_TRACE: 'false',
GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME', GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
@@ -54,6 +58,12 @@ function mockGetInput(requestResponse: Record<string, string>) {
}; };
} }
function mockGetMultilineInput(requestResponse: Record<string, string[]>) {
return function (name: string, _options: unknown): string[] {
return requestResponse[name]!;
};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
jest.mock('fs', () => ({ jest.mock('fs', () => ({
...jest.requireActual('fs'), ...jest.requireActual('fs'),
@@ -481,6 +491,40 @@ describe('Configure AWS Credentials', () => {
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(12); expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(12);
}); });
test('role assumption fails after one trial when disabling retry', 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, 'disable-retry': 'true' })
);
mockedSTS.reset();
mockedSTS.on(AssumeRoleWithWebIdentityCommand).rejects();
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(1);
});
test('max retries is configurable', 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,
'retry-max-attempts': '15',
})
);
mockedSTS.reset();
mockedSTS.on(AssumeRoleWithWebIdentityCommand).rejects();
await run();
expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(15);
});
test('role external ID provided', async () => { test('role external ID provided', async () => {
jest jest
.spyOn(core, 'getInput') .spyOn(core, 'getInput')
@@ -614,4 +658,46 @@ describe('Configure AWS Credentials', () => {
expect(core.exportVariable).toReturn(); expect(core.exportVariable).toReturn();
}); });
test('inline policy and managed session policies are provided in assume role calls', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'inline-session-policy': 'inline' }));
jest
.spyOn(core, 'getMultilineInput')
.mockImplementation(mockGetMultilineInput({ 'managed-session-policies': MANAGED_SESSION_POLICY_INPUT }));
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 },
],
Policy: 'inline',
PolicyArns: [
{ arn: 'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess' },
{ arn: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' },
],
});
});
test('unsets credentials if enabled', async () => {
jest
.spyOn(core, 'getInput')
.mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'unset-current-credentials': 'true' }));
await run();
expect(core.exportVariable).toHaveBeenCalledTimes(9);
});
}); });