mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-02 05:55:10 +09:00
fix: allow kubelet token symlink (#1805)
* Revert "chore: Update dist" This reverts commit5548f3441b. * Revert "chore: document container credentials provider support (and delete transitive tags in AssumeRoleWithWebIdentity) (#1780)" This reverts commit77cd089899. * Revert "chore: replay 6.2 devel changes onto main (#1800)" This reverts commit87eb0cf693. * fix: allow kubelet token symlink Closes #1804
This commit is contained in:
+10
-15
@@ -3,18 +3,15 @@ import { STSClient } from '@aws-sdk/client-sts';
|
||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
import { ProxyAgent } from 'proxy-agent';
|
||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
||||
import { errorMessage, getCallerIdentity } from './helpers';
|
||||
import { ProxyResolver } from './ProxyResolver';
|
||||
|
||||
if (!process.env.AWS_EXECUTION_ENV) {
|
||||
process.env.AWS_EXECUTION_ENV = 'GitHubActions';
|
||||
}
|
||||
const USER_AGENT = 'configure-aws-credentials-for-github-actions';
|
||||
|
||||
export interface CredentialsClientProps {
|
||||
region?: string;
|
||||
proxyServer?: string;
|
||||
noProxy?: string;
|
||||
stsEndpoint?: string;
|
||||
roleChaining: boolean;
|
||||
}
|
||||
|
||||
@@ -22,7 +19,6 @@ export class CredentialsClient {
|
||||
public region?: string;
|
||||
private _stsClient?: STSClient;
|
||||
private readonly requestHandler?: NodeHttpHandler;
|
||||
private readonly stsEndpoint?: string;
|
||||
private roleChaining?: boolean;
|
||||
|
||||
constructor(props: CredentialsClientProps) {
|
||||
@@ -45,20 +41,19 @@ export class CredentialsClient {
|
||||
httpAgent: handler,
|
||||
});
|
||||
}
|
||||
if (props.stsEndpoint) {
|
||||
this.stsEndpoint = props.stsEndpoint;
|
||||
}
|
||||
this.roleChaining = props.roleChaining;
|
||||
}
|
||||
|
||||
public get stsClient(): STSClient {
|
||||
if (!this._stsClient || this.roleChaining) {
|
||||
this._stsClient = new STSClient({
|
||||
customUserAgent: buildCustomUserAgent(),
|
||||
...(this.region !== undefined && { region: this.region }),
|
||||
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
|
||||
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
|
||||
});
|
||||
const config = { customUserAgent: USER_AGENT } as {
|
||||
customUserAgent: string;
|
||||
region?: string;
|
||||
requestHandler?: NodeHttpHandler;
|
||||
};
|
||||
if (this.region !== undefined) config.region = this.region;
|
||||
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
|
||||
this._stsClient = new STSClient(config);
|
||||
}
|
||||
return this._stsClient;
|
||||
}
|
||||
|
||||
+15
-120
@@ -42,7 +42,6 @@ async function assumeRoleWithWebIdentityTokenFile(
|
||||
core.info('Assuming role with web identity token file');
|
||||
try {
|
||||
delete params.Tags;
|
||||
delete params.TransitiveTagKeys;
|
||||
const creds = await client.send(
|
||||
new AssumeRoleWithWebIdentityCommand({
|
||||
...params,
|
||||
@@ -78,98 +77,6 @@ export interface assumeRoleParams {
|
||||
webIdentityToken?: string;
|
||||
inlineSessionPolicy?: string;
|
||||
managedSessionPolicies?: { arn: string }[];
|
||||
customTags?: string;
|
||||
}
|
||||
|
||||
const TAG_KEY_REGEX = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]+$/u;
|
||||
const TAG_VALUE_REGEX = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
|
||||
const MAX_TAG_KEY_LENGTH = 128;
|
||||
const MAX_TAG_VALUE_LENGTH = 256;
|
||||
const MAX_SESSION_TAGS = 50;
|
||||
|
||||
// Identity/audit primitives. Always emitted and cannot be overridden by custom-tags.
|
||||
const PROTECTED_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||
{ key: 'Repository', envVar: 'GITHUB_REPOSITORY' },
|
||||
{ key: 'Workflow', envVar: 'GITHUB_WORKFLOW' },
|
||||
{ key: 'Action', envVar: 'GITHUB_ACTION' },
|
||||
{ key: 'Actor', envVar: 'GITHUB_ACTOR' },
|
||||
{ key: 'Commit', envVar: 'GITHUB_SHA' },
|
||||
{ key: 'Branch', envVar: 'GITHUB_REF' },
|
||||
];
|
||||
|
||||
// Convenience metadata. Custom-tags may override (suppresses the default for that key).
|
||||
// Listed in priority order; lower-priority entries are dropped first if the user's custom-tags
|
||||
// would push the total above MAX_SESSION_TAGS.
|
||||
const OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||
{ key: 'EventName', envVar: 'GITHUB_EVENT_NAME' },
|
||||
{ key: 'BaseRef', envVar: 'GITHUB_BASE_REF' },
|
||||
{ key: 'HeadRef', envVar: 'GITHUB_HEAD_REF' },
|
||||
{ key: 'RefName', envVar: 'GITHUB_REF_NAME' },
|
||||
{ key: 'RunId', envVar: 'GITHUB_RUN_ID' },
|
||||
{ key: 'RefType', envVar: 'GITHUB_REF_TYPE' },
|
||||
{ key: 'Job', envVar: 'GITHUB_JOB' },
|
||||
{ key: 'TriggeringActor', envVar: 'GITHUB_TRIGGERING_ACTOR' },
|
||||
];
|
||||
|
||||
const PROTECTED_TAG_KEYS = new Set<string>(['GitHub', ...PROTECTED_TAG_SOURCES.map((s) => s.key)]);
|
||||
|
||||
export function parseAndValidateCustomTags(customTags: string, existingTags: Tag[]): Tag[] {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(customTags);
|
||||
} catch {
|
||||
throw new Error('custom-tags: input is not valid JSON');
|
||||
}
|
||||
|
||||
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
||||
throw new Error('custom-tags: input must be a JSON object (not an array or primitive)');
|
||||
}
|
||||
|
||||
const newTags: Tag[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (typeof value === 'object') {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' must be a string, number, or boolean (not an object or array)`,
|
||||
);
|
||||
}
|
||||
|
||||
const stringValue = String(value);
|
||||
|
||||
if (key.length === 0 || key.length > MAX_TAG_KEY_LENGTH) {
|
||||
throw new Error(`custom-tags: key '${key}' must be between 1 and ${MAX_TAG_KEY_LENGTH} characters`);
|
||||
}
|
||||
if (stringValue.length > MAX_TAG_VALUE_LENGTH) {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' exceeds maximum length of ${MAX_TAG_VALUE_LENGTH} characters`,
|
||||
);
|
||||
}
|
||||
if (!TAG_KEY_REGEX.test(key)) {
|
||||
throw new Error(
|
||||
`custom-tags: key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
||||
);
|
||||
}
|
||||
if (stringValue.length > 0 && !TAG_VALUE_REGEX.test(stringValue)) {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
||||
);
|
||||
}
|
||||
if (PROTECTED_TAG_KEYS.has(key)) {
|
||||
throw new Error(
|
||||
`custom-tags: key '${key}' conflicts with a protected session tag set by this action and cannot be overridden`,
|
||||
);
|
||||
}
|
||||
|
||||
newTags.push({ Key: key, Value: stringValue });
|
||||
}
|
||||
|
||||
if (existingTags.length + newTags.length > MAX_SESSION_TAGS) {
|
||||
throw new Error(
|
||||
`custom-tags: total session tags (${existingTags.length + newTags.length}) would exceed the AWS limit of ${MAX_SESSION_TAGS}`,
|
||||
);
|
||||
}
|
||||
|
||||
return newTags;
|
||||
}
|
||||
|
||||
export async function assumeRole(params: assumeRoleParams) {
|
||||
@@ -186,7 +93,6 @@ export async function assumeRole(params: assumeRoleParams) {
|
||||
webIdentityToken,
|
||||
inlineSessionPolicy,
|
||||
managedSessionPolicies,
|
||||
customTags,
|
||||
} = { ...params };
|
||||
|
||||
// Load GitHub environment variables
|
||||
@@ -195,37 +101,26 @@ export async function assumeRole(params: assumeRoleParams) {
|
||||
throw new Error('Missing required environment variables. Are you running in GitHub Actions?');
|
||||
}
|
||||
|
||||
// Build session tags. Values are sanitized because the AWS tag value spec is more
|
||||
// restrictive than permissible characters in environment variables.
|
||||
const protectedTags: Tag[] = [{ Key: 'GitHub', Value: 'Actions' }];
|
||||
for (const { key, envVar } of PROTECTED_TAG_SOURCES) {
|
||||
const value = process.env[envVar];
|
||||
if (value) {
|
||||
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||
}
|
||||
// Load role session tags
|
||||
const tagArray: Tag[] = [
|
||||
{ Key: 'GitHub', Value: 'Actions' },
|
||||
{ Key: 'Repository', Value: GITHUB_REPOSITORY },
|
||||
{ Key: 'Workflow', Value: sanitizeGitHubVariables(GITHUB_WORKFLOW) },
|
||||
{ Key: 'Action', Value: GITHUB_ACTION },
|
||||
{ Key: 'Actor', Value: sanitizeGitHubVariables(GITHUB_ACTOR) },
|
||||
{ Key: 'Commit', Value: GITHUB_SHA },
|
||||
];
|
||||
if (process.env.GITHUB_REF) {
|
||||
tagArray.push({
|
||||
Key: 'Branch',
|
||||
Value: sanitizeGitHubVariables(process.env.GITHUB_REF),
|
||||
});
|
||||
}
|
||||
|
||||
const parsedCustomTags: Tag[] = customTags ? parseAndValidateCustomTags(customTags, protectedTags) : [];
|
||||
const customTagKeys = new Set(parsedCustomTags.map((t) => t.Key));
|
||||
|
||||
const availableOverrideableSlots = MAX_SESSION_TAGS - protectedTags.length - parsedCustomTags.length;
|
||||
const overrideableTags: Tag[] = [];
|
||||
for (const { key, envVar } of OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY) {
|
||||
if (overrideableTags.length >= availableOverrideableSlots) break;
|
||||
if (customTagKeys.has(key)) continue;
|
||||
const value = process.env[envVar];
|
||||
if (value) {
|
||||
overrideableTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||
}
|
||||
}
|
||||
|
||||
const tagArray: Tag[] = [...protectedTags, ...overrideableTags, ...parsedCustomTags];
|
||||
|
||||
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
||||
if (!tags) {
|
||||
core.debug('Role session tagging has been skipped.');
|
||||
} else {
|
||||
core.debug(`${tags.length} role session tags are being used:`);
|
||||
core.debug(`${tags.length} role session tags are being used.`);
|
||||
}
|
||||
|
||||
//only populate transitiveTagKeys array if user is actually using session tagging
|
||||
|
||||
+24
-29
@@ -3,32 +3,11 @@ import * as path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||
import type { UserAgent } from '@smithy/types';
|
||||
import type { CredentialsClient } from './CredentialsClient';
|
||||
|
||||
const MAX_TAG_VALUE_LENGTH = 256;
|
||||
const SANITIZATION_CHARACTER = '_';
|
||||
const SPECIAL_CHARS_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/;
|
||||
const USER_AGENT_PREFIX = 'configure-aws-credentials-for-github-actions';
|
||||
const UA_FIELDS: ReadonlyArray<{ env: string; label: string; pattern: RegExp }> = [
|
||||
{ env: 'GITHUB_ACTION', label: 'action', pattern: /^[A-Za-z0-9_-]{1,128}$/ },
|
||||
{ env: 'GITHUB_RUN_ID', label: 'run_id', pattern: /^[0-9]{1,20}$/ },
|
||||
{ env: 'GITHUB_RUN_ATTEMPT', label: 'attempt', pattern: /^[0-9]{1,10}$/ },
|
||||
];
|
||||
|
||||
export function buildCustomUserAgent(): UserAgent {
|
||||
const tokens: UserAgent = [[USER_AGENT_PREFIX]];
|
||||
for (const { env, label, pattern } of UA_FIELDS) {
|
||||
const value = process.env[env];
|
||||
if (value === undefined) continue;
|
||||
if (pattern.test(value)) {
|
||||
tokens.push(['md', `${label}#${value}`]);
|
||||
} else {
|
||||
core.warning(`${env} has unexpected format; omitting from User-Agent`);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export function translateEnvVariables() {
|
||||
const envVars = [
|
||||
@@ -212,7 +191,6 @@ export async function retryAndBackoff<T>(
|
||||
maxRetries = 12,
|
||||
retries = 0,
|
||||
base = 50,
|
||||
label?: string,
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
@@ -224,21 +202,20 @@ export async function retryAndBackoff<T>(
|
||||
// It's retryable, so sleep and retry.
|
||||
const delay = Math.random() * (2 ** retries * base);
|
||||
const nextRetry = retries + 1;
|
||||
const opName = label ? ` ${label}` : '';
|
||||
|
||||
core.info(
|
||||
`Retry${opName}: attempt ${nextRetry} of ${maxRetries} failed: ${errorMessage(err)}. ` +
|
||||
core.debug(
|
||||
`retryAndBackoff: attempt ${nextRetry} of ${maxRetries} failed: ${errorMessage(err)}. ` +
|
||||
`Retrying after ${Math.floor(delay)}ms.`,
|
||||
);
|
||||
|
||||
await sleep(delay);
|
||||
|
||||
if (nextRetry >= maxRetries) {
|
||||
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
||||
core.debug('retryAndBackoff: reached max retries; giving up.');
|
||||
throw err;
|
||||
}
|
||||
|
||||
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
||||
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +274,20 @@ export function getBooleanInput(name: string, options?: core.InputOptions & { de
|
||||
// O_NOFOLLOW is undefined on Windows. This sets it to 0 if it's not defined.
|
||||
const O_NOFOLLOW: number = (fs.constants as { O_NOFOLLOW?: number }).O_NOFOLLOW ?? 0;
|
||||
|
||||
export function isAllowListed(filePath: string): boolean {
|
||||
// Kubelet projects service-account tokens through a symlink chain
|
||||
// (token -> ..data/token, ..data -> ..<timestamp>/). The containing path is
|
||||
// kubelet-controlled, so we allow symlink-following reads of this fixed
|
||||
// location only.
|
||||
const KUBERNETES_TOKEN_PATH_REGEX = /^\/var\/run\/secrets\/[^/]+\/serviceaccount\/token$/;
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
// No Kubernetes token paths on Windows
|
||||
return KUBERNETES_TOKEN_PATH_REGEX.test(path.posix.normalize(filePath));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isSymlink(filePath: string): boolean {
|
||||
try {
|
||||
return fs.lstatSync(filePath).isSymbolicLink();
|
||||
@@ -328,10 +319,14 @@ function assertRegularFile(fd: number, filePath: string): void {
|
||||
// ELOOP: too many symbolic links (from NOFOLLOW)
|
||||
|
||||
export function readFileUtf8(filePath: string): string | null {
|
||||
refuseSymlinkOnPath(filePath);
|
||||
const allowSymlink = isAllowListed(filePath);
|
||||
if (!allowSymlink) {
|
||||
refuseSymlinkOnPath(filePath);
|
||||
}
|
||||
const openFlags = fs.constants.O_RDONLY | (allowSymlink ? 0 : O_NOFOLLOW);
|
||||
let fd: number;
|
||||
try {
|
||||
fd = fs.openSync(filePath, fs.constants.O_RDONLY | O_NOFOLLOW);
|
||||
fd = fs.openSync(filePath, openFlags);
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException).code;
|
||||
if (code === 'ENOENT') return null;
|
||||
|
||||
+48
-90
@@ -19,7 +19,6 @@ import { writeProfileFiles } from './profileManager';
|
||||
const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds)
|
||||
const ROLE_SESSION_NAME = 'GitHubActions';
|
||||
const REGION_REGEX = /^[a-z0-9-]+$/g;
|
||||
const ROLE_SESSION_NAME_REGEX = /^[\w+=,.@-]*$/;
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
@@ -44,7 +43,6 @@ export async function run() {
|
||||
const roleSkipSessionTagging = getBooleanInput('role-skip-session-tagging', { required: false });
|
||||
const transitiveTagKeys = core.getMultilineInput('transitive-tag-keys', { required: false });
|
||||
const proxyServer = core.getInput('http-proxy', { required: false }) || process.env.HTTP_PROXY;
|
||||
const customTags = core.getInput('custom-tags', { required: false });
|
||||
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
|
||||
const managedSessionPolicies = core.getMultilineInput('managed-session-policies', { required: false }).map((p) => {
|
||||
return { arn: p };
|
||||
@@ -65,7 +63,6 @@ export async function run() {
|
||||
.map((s) => s.trim());
|
||||
const forceSkipOidc = getBooleanInput('force-skip-oidc', { required: false });
|
||||
const noProxy = core.getInput('no-proxy', { required: false });
|
||||
const stsEndpoint = core.getInput('sts-endpoint', { required: false });
|
||||
const globalTimeout = Number.parseInt(core.getInput('action-timeout-s', { required: false })) || 0;
|
||||
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
@@ -91,9 +88,6 @@ export async function run() {
|
||||
maxRetries = 1;
|
||||
}
|
||||
|
||||
const withRetry = <T>(fn: () => Promise<T>, label: string): Promise<T> =>
|
||||
retryAndBackoff(fn, !disableRetry, maxRetries, 0, 50, label);
|
||||
|
||||
// Logic to decide whether to attempt to use OIDC or not
|
||||
const useGitHubOIDCProvider = () => {
|
||||
if (forceSkipOidc) return false;
|
||||
@@ -130,33 +124,15 @@ export async function run() {
|
||||
throw new Error(`Region is not valid: ${region}`);
|
||||
}
|
||||
|
||||
if (roleSessionName.length < 2 || roleSessionName.length > 64) {
|
||||
throw new Error(
|
||||
`Role session name must be between 2 and 64 characters, got ${roleSessionName.length}: '${roleSessionName}'`,
|
||||
);
|
||||
}
|
||||
if (!roleSessionName.match(ROLE_SESSION_NAME_REGEX)) {
|
||||
throw new Error(
|
||||
`Role session name is not valid: '${roleSessionName}'. Must satisfy regular expression pattern: [\\w+=,.@-]*`,
|
||||
);
|
||||
}
|
||||
|
||||
exportRegion(region, outputEnvCredentials);
|
||||
|
||||
// Instantiate credentials client
|
||||
const clientProps: {
|
||||
region: string;
|
||||
proxyServer?: string;
|
||||
noProxy?: string;
|
||||
stsEndpoint?: string;
|
||||
roleChaining: boolean;
|
||||
} = {
|
||||
const clientProps: { region: string; proxyServer?: string; noProxy?: string; roleChaining: boolean } = {
|
||||
region,
|
||||
roleChaining,
|
||||
};
|
||||
if (proxyServer) clientProps.proxyServer = proxyServer;
|
||||
if (noProxy) clientProps.noProxy = noProxy;
|
||||
if (stsEndpoint) clientProps.stsEndpoint = stsEndpoint;
|
||||
const credentialsClient = new CredentialsClient(clientProps);
|
||||
let sourceAccountId: string;
|
||||
let webIdentityToken: string;
|
||||
@@ -176,9 +152,13 @@ export async function run() {
|
||||
// Else, export credentials provided as input
|
||||
if (useGitHubOIDCProvider()) {
|
||||
try {
|
||||
webIdentityToken = await withRetry(async () => {
|
||||
return core.getIDToken(audience);
|
||||
}, 'getIDToken');
|
||||
webIdentityToken = await retryAndBackoff(
|
||||
async () => {
|
||||
return core.getIDToken(audience);
|
||||
},
|
||||
!disableRetry,
|
||||
maxRetries,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`getIDToken call failed: ${errorMessage(error)}`);
|
||||
}
|
||||
@@ -199,77 +179,59 @@ export async function run() {
|
||||
}
|
||||
} else if (!webIdentityTokenFile && !roleChaining) {
|
||||
// Proceed only if credentials can be picked up
|
||||
await withRetry(
|
||||
() => credentialsClient.validateCredentials(undefined, roleChaining, expectedAccountIds),
|
||||
'validateCredentials',
|
||||
);
|
||||
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
await credentialsClient.validateCredentials(undefined, roleChaining, expectedAccountIds);
|
||||
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
|
||||
}
|
||||
|
||||
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.
|
||||
// Skip when output-env-credentials is false: input IAM keys were not written to env, so
|
||||
// the default chain would resolve to ambient runner credentials and the access-key check
|
||||
// would spuriously fail (see #1554).
|
||||
if (outputEnvCredentials) {
|
||||
await withRetry(
|
||||
() => credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds),
|
||||
'validateCredentials',
|
||||
);
|
||||
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
}
|
||||
await credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds);
|
||||
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
|
||||
}
|
||||
if (customTags && (useGitHubOIDCProvider() || webIdentityTokenFile)) {
|
||||
core.warning(
|
||||
"'custom-tags' is set but will be ignored because session tags cannot be applied when using OIDC or web identity token authentication. " +
|
||||
'Tags are controlled by the identity provider token claims in these authentication flows.',
|
||||
);
|
||||
}
|
||||
|
||||
// Get role credentials if configured to do so
|
||||
if (roleToAssume) {
|
||||
let roleCredentials: AssumeRoleCommandOutput;
|
||||
do {
|
||||
roleCredentials = await withRetry(async () => {
|
||||
return assumeRole({
|
||||
credentialsClient,
|
||||
sourceAccountId,
|
||||
roleToAssume,
|
||||
roleExternalId,
|
||||
roleDuration,
|
||||
roleSessionName,
|
||||
roleSkipSessionTagging,
|
||||
transitiveTagKeys,
|
||||
webIdentityTokenFile,
|
||||
webIdentityToken,
|
||||
inlineSessionPolicy,
|
||||
managedSessionPolicies,
|
||||
customTags,
|
||||
});
|
||||
}, 'AssumeRole');
|
||||
roleCredentials = await retryAndBackoff(
|
||||
async () => {
|
||||
return assumeRole({
|
||||
credentialsClient,
|
||||
sourceAccountId,
|
||||
roleToAssume,
|
||||
roleExternalId,
|
||||
roleDuration,
|
||||
roleSessionName,
|
||||
roleSkipSessionTagging,
|
||||
transitiveTagKeys,
|
||||
webIdentityTokenFile,
|
||||
webIdentityToken,
|
||||
inlineSessionPolicy,
|
||||
managedSessionPolicies,
|
||||
});
|
||||
},
|
||||
!disableRetry,
|
||||
maxRetries,
|
||||
);
|
||||
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
|
||||
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
|
||||
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
|
||||
// Validate that the SDK can pick up the assumed-role credentials from the environment.
|
||||
// Skip when output-env-credentials is false: the credentials were never written to env,
|
||||
// so the default credential provider chain would resolve to ambient runner credentials
|
||||
// (e.g. an EC2 instance profile) and the access-key-id check would spuriously fail.
|
||||
// Skip when using a profile: validation runs after the profile file is written below.
|
||||
if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile && outputEnvCredentials) {
|
||||
await withRetry(
|
||||
() =>
|
||||
credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials?.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
),
|
||||
'validateCredentials',
|
||||
// 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 we are using a profile, don't validate credentials yet (since they most likely won't be in the environment).
|
||||
// Wait until after creds are written to the profile file to try validation.
|
||||
if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile) {
|
||||
await credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials?.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
);
|
||||
}
|
||||
if (outputEnvCredentials) {
|
||||
await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
await exportAccountId(credentialsClient, maskAccountId);
|
||||
}
|
||||
|
||||
// Write profile files if profile mode is enabled
|
||||
@@ -282,14 +244,10 @@ export async function run() {
|
||||
// We then validate the credentials to make sure they work.
|
||||
if (AccessKeyId || !process.env.GITHUB_ACTIONS) {
|
||||
writeProfileFiles(awsProfile, roleCredentials.Credentials, region, true);
|
||||
await withRetry(
|
||||
() =>
|
||||
credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials?.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
),
|
||||
'validateCredentials',
|
||||
await credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
);
|
||||
} else {
|
||||
writeProfileFiles(awsProfile, roleCredentials.Credentials, region, overwriteAwsProfile);
|
||||
|
||||
Reference in New Issue
Block a user