1
0
Fork 0
mirror of synced 2026-06-05 12:38:19 +00:00
configure-aws-credentials/src/index.ts

179 lines
7.7 KiB
TypeScript

import * as core from '@actions/core';
import { assumeRole } from './assumeRole';
import { CredentialsClient } from './CredentialsClient';
import {
errorMessage,
retryAndBackoff,
exportRegion,
exportCredentials,
exportAccountId,
unsetCredentials,
} from './helpers';
const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds)
const ROLE_SESSION_NAME = 'GitHubActions';
const REGION_REGEX = /^[a-z0-9-]+$/g;
export async function run() {
try {
// Get inputs
const AccessKeyId = core.getInput('aws-access-key-id', { required: false });
const SecretAccessKey = core.getInput('aws-secret-access-key', { required: false });
const sessionTokenInput = core.getInput('aws-session-token', { required: false });
const SessionToken = sessionTokenInput === '' ? undefined : sessionTokenInput;
const region = core.getInput('aws-region', { required: true });
const roleToAssume = core.getInput('role-to-assume', { required: false });
const audience = core.getInput('audience', { required: false });
const maskAccountIdInput = core.getInput('mask-aws-account-id', { required: false }) || 'false';
const maskAccountId = maskAccountIdInput.toLowerCase() === 'true';
const roleExternalId = core.getInput('role-external-id', { required: false });
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
const roleDuration = parseInt(core.getInput('role-duration-seconds', { required: false })) || DEFAULT_ROLE_DURATION;
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 proxyServer = core.getInput('http-proxy', { required: false });
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false });
const managedSessionPolicies: any[] = [];
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
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) {
managedSessionPolicies.push({ arn: managedSessionPolicy });
}
// Logic to decide whether to attempt to use OIDC or not
const useGitHubOIDCProvider = () => {
// 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
// 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
if (
!!roleToAssume &&
!webIdentityTokenFile &&
!AccessKeyId &&
!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!roleChaining
) {
core.info(
'It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission?'
);
}
return (
!!roleToAssume &&
!!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!AccessKeyId &&
!webIdentityTokenFile &&
!roleChaining
);
};
if (unsetCurrentCredentials) {
unsetCredentials();
}
if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
}
exportRegion(region);
// Instantiate credentials client
const credentialsClient = new CredentialsClient({ region, proxyServer });
let sourceAccountId: string;
let webIdentityToken: string;
// If OIDC is being used, generate token
// Else, validate that the SDK can pick up credentials
if (useGitHubOIDCProvider()) {
try {
webIdentityToken = await retryAndBackoff(
async () => {
return core.getIDToken(audience);
},
!disableRetry,
maxRetries
);
} catch (error) {
throw new Error(`getIDToken call failed: ${errorMessage(error)}`);
}
} else if (AccessKeyId) {
if (!SecretAccessKey) {
throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided");
}
// The STS client for calling AssumeRole pulls creds from the environment.
// Plus, in the assume role case, if the AssumeRole call fails, we want
// the source credentials and account ID to already be masked as secrets
// in any error messages.
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
} else if (!webIdentityTokenFile && !roleChaining) {
throw new Error('Could not determine how to assume credentials. Please check your inputs and try again.');
}
if (AccessKeyId || roleChaining) {
// Validate that the SDK can actually pick up credentials.
// This validates cases where this action is using existing environment credentials,
// and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
}
// Get role credentials if configured to do so
if (roleToAssume) {
const roleCredentials = await retryAndBackoff(
async () => {
return assumeRole({
credentialsClient,
sourceAccountId,
roleToAssume,
roleExternalId,
roleDuration,
roleSessionName,
roleSkipSessionTagging,
webIdentityTokenFile,
webIdentityToken,
inlineSessionPolicy,
managedSessionPolicies,
});
},
!disableRetry,
maxRetries
);
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser!.AssumedRoleId!}`);
exportCredentials(roleCredentials.Credentials, outputCredentials);
// We need to validate the credentials in 2 of our use-cases
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable
// is set to `true` then we are NOT in a self-hosted runner.
// Second: Customer provided credentials manually (IAM User keys stored in GH Secrets)
if (!process.env['GITHUB_ACTIONS'] || AccessKeyId) {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
}
await exportAccountId(credentialsClient, maskAccountId);
} else {
core.info('Proceeding with IAM user credentials');
}
} catch (error) {
core.setFailed(errorMessage(error));
const showStackTrace = process.env['SHOW_STACK_TRACE'];
if (showStackTrace === 'true') {
throw error;
}
}
}
/* c8 ignore start */
if (require.main === module) {
(async () => {
await run();
})().catch((error) => {
core.setFailed(errorMessage(error));
});
}