1
0
Fork 0
mirror of synced 2026-06-05 09:35:13 +00:00

fix: branch name is not sanitized, slight refactor

This commit is contained in:
peterwoodworth 2023-03-22 15:08:39 -07:00
commit b723544115
3 changed files with 63 additions and 75 deletions

View file

@ -5,7 +5,7 @@ import * as core from '@actions/core';
import type { AssumeRoleCommandInput, STSClient, Tag } from '@aws-sdk/client-sts';
import { AssumeRoleCommand, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
import type { CredentialsClient } from './CredentialsClient';
import { errorMessage, isDefined, sanitizeGithubActor, sanitizeGithubWorkflowName } from './helpers';
import { errorMessage, isDefined, sanitizeGitHubVariables } from './helpers';
async function assumeRoleWithOIDC(params: AssumeRoleCommandInput, client: STSClient, webIdentityToken: string) {
delete params.Tags;
@ -96,13 +96,13 @@ export async function assumeRole(params: assumeRoleParams) {
const tagArray: Tag[] = [
{ Key: 'GitHub', Value: 'Actions' },
{ Key: 'Repository', Value: GITHUB_REPOSITORY },
{ Key: 'Workflow', Value: sanitizeGithubWorkflowName(GITHUB_WORKFLOW) },
{ Key: 'Workflow', Value: sanitizeGitHubVariables(GITHUB_WORKFLOW) },
{ Key: 'Action', Value: GITHUB_ACTION },
{ Key: 'Actor', Value: sanitizeGithubActor(GITHUB_ACTOR) },
{ Key: 'Actor', Value: sanitizeGitHubVariables(GITHUB_ACTOR) },
{ Key: 'Commit', Value: GITHUB_SHA },
];
if (process.env['GITHUB_REF']) {
tagArray.push({ Key: 'Branch', Value: process.env['GITHUB_REF'] });
tagArray.push({ Key: 'Branch', Value: sanitizeGitHubVariables(process.env['GITHUB_REF']) });
}
const tags = roleSkipSessionTagging ? undefined : tagArray;
if (!tags) {

View file

@ -6,29 +6,56 @@ import type { CredentialsClient } from './CredentialsClient';
const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_';
export function sanitizeGithubActor(actor: string) {
// In some circumstances the actor may contain square brackets. For example, if they're a bot ('[bot]')
// Square brackets are not allowed in AWS session tags
return actor.replace(/\[|\]/g, SANITIZATION_CHARACTER);
// 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
export function exportCredentials(creds?: Partial<Credentials>) {
if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.setSecret(creds.SecretAccessKey);
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.setSecret(creds.SessionToken);
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
} else if (process.env['AWS_SESSION_TOKEN']) {
// clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', '');
}
}
export function sanitizeGithubWorkflowName(name: string) {
// Workflow names can be almost any valid UTF-8 string, but tags are more restrictive.
// This replaces anything not conforming to the tag restrictions by inverting the regular expression.
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_:/=+.-@-]/gu, SANITIZATION_CHARACTER);
export function exportRegion(region: string) {
core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region);
}
// Obtains account ID from STS Client and sets it as output
export async function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: string) {
const client = credentialsClient.getStsClient();
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(accountId);
}
core.setOutput('aws-account-id', accountId);
return accountId;
}
// Tags have a more restrictive set of acceptable characters than GitHub environment variables can.
// This replaces anything not conforming to the tag restrictions by inverting the regular expression.
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
export function sanitizeGitHubVariables(name: string) {
const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_.:/=+\-@]/gu, SANITIZATION_CHARACTER);
const nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH);
return nameTruncated;
}
/* c8 ignore start */
export function errorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function isDefined<T>(i: T | undefined | null): i is T {
return i !== undefined && i !== null;
}
/* c8 ignore stop */
export async function defaultSleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
@ -38,11 +65,12 @@ let sleep = defaultSleep;
export function withsleep(s: typeof sleep) {
sleep = s;
}
export function reset() {
sleep = defaultSleep;
}
// retryAndBackoff retries with exponential backoff the promise if the error isRetryable upto maxRetries time.
// Retries the promise with exponential backoff if the error isRetryable up to maxRetries time.
export async function retryAndBackoff<T>(
fn: () => Promise<T>,
isRetryable: boolean,
@ -66,53 +94,12 @@ export async function retryAndBackoff<T>(
}
}
export function exportCredentials(creds?: Partial<Credentials>) {
// 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
// AWS_ACCESS_KEY_ID:
// Specifies an AWS access key associated with an IAM user or role
if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
}
// AWS_SECRET_ACCESS_KEY:
// Specifies the secret key associated with the access key. This is essentially the "password" for the access key.
if (creds?.SecretAccessKey) {
core.setSecret(creds.SecretAccessKey);
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
}
// AWS_SESSION_TOKEN:
// Specifies the session token value that is required if you are using temporary security credentials.
if (creds?.SessionToken) {
core.setSecret(creds.SessionToken);
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
} else if (process.env['AWS_SESSION_TOKEN']) {
// clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', '');
}
/* c8 ignore start */
export function errorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function exportRegion(region: string) {
// AWS_DEFAULT_REGION and AWS_REGION:
// Specifies the AWS Region to send requests to
core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region);
}
export async function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: string) {
// Get the AWS account ID
const client = credentialsClient.getStsClient();
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(accountId);
}
core.setOutput('aws-account-id', accountId);
return accountId;
export function isDefined<T>(i: T | undefined | null): i is T {
return i !== undefined && i !== null;
}
/* c8 ignore stop */

View file

@ -3,7 +3,7 @@ import { assumeRole } from './assumeRole';
import { CredentialsClient } from './CredentialsClient';
import { errorMessage, retryAndBackoff, exportRegion, exportCredentials, exportAccountId } from './helpers';
const DEFAULT_ROLE_DURATION = 3600; // One hour
const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds)
const ROLE_SESSION_NAME = 'GitHubActions';
const REGION_REGEX = /^[a-z0-9-]+$/g;
@ -32,7 +32,7 @@ export async function run() {
// 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,
// 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, as well as add an opportunity to manually disable the entire check.
@ -80,14 +80,13 @@ export async function run() {
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
}
// Attempt to load credentials from the GitHub OIDC provider.
// If a user provides an IAM Role Arn and DOESN'T provide an Access Key Id
// The only way to assume the role is via GitHub's OIDC provider.
// If OIDC is being used, generate token
// Else, validate that the SDK can pick up credentials
let sourceAccountId: string;
let webIdentityToken: string;
if (useGitHubOIDCProvider()) {
webIdentityToken = await core.getIDToken(audience);
// We don't validate the credentials here because we don't have them yet when using OIDC.
// Implement #359
} else {
// Regardless of whether any source credentials were provided as inputs,
// validate that the SDK can actually pick up credentials. This validates
@ -124,7 +123,9 @@ export async function run() {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
}
await exportAccountId(credentialsClient, maskAccountId);
// implement #432
} else {
// implement #370
core.info('Proceeding with IAM user credentials');
}
} catch (error) {