1
0
Fork 0
mirror of synced 2026-06-05 11:15:14 +00:00

feat: role-chaining

This commit is contained in:
peterwoodworth 2023-06-21 16:45:34 -07:00
commit d26f2d03f8
No known key found for this signature in database
GPG key ID: 01931412FD685922
8 changed files with 66 additions and 69 deletions

View file

@ -9,15 +9,14 @@ branding:
color: orange
icon: cloud
inputs:
audience:
description: The audience to use for the OIDC provider
required: false
default: sts.amazonaws.com
aws-access-key-id:
description: AWS Access Key ID. This input is required if running in the GitHub hosted environment. It is optional if running in a self-hosted environment that already has AWS credentials, for example on an EC2 instance.
required: false
aws-region:
description: AWS Region, e.g. us-east-2
required: true
role-to-assume:
description: The Amazon Resource Name (ARN) of the role to assume. Use the provided credentials to assume an IAM role and configure the Actions environment with the assumed role credentials rather than with the provided credentials.
required: false
aws-access-key-id:
description: AWS Access Key ID. This input is required if running in the GitHub hosted environment. It is optional if running in a self-hosted environment that already has AWS credentials, for example on an EC2 instance.
required: false
aws-secret-access-key:
description: AWS Access Key ID. This input is required if running in the GitHub hosted environment. It is optional if running in a self-hosted environment that already has AWS credentials, for example on an EC2 instance.
@ -25,6 +24,16 @@ inputs:
aws-session-token:
description: AWS Session Token
required: false
web-identity-token-file:
description: Use the web identity token file from the provided file system path in order to assume an IAM role using a web identity, e.g. from within an Amazon EKS worker node.
required: false
role-chaining:
description: 'Use existing credentials from the environment to assume a new role'
required: false
audience:
description: The audience to use for the OIDC provider
required: false
default: sts.amazonaws.com
disable-oidc:
description: Strictly disable action from attempting to fetch credentials with OIDC
required: false
@ -46,12 +55,6 @@ inputs:
role-skip-session-tagging:
description: Skip session tagging during role assumption
required: false
role-to-assume:
description: The Amazon Resource Name (ARN) of the role to assume. Use the provided credentials to assume an IAM role and configure the Actions environment with the assumed role credentials rather than with the provided credentials.
required: false
web-identity-token-file:
description: Use the web identity token file from the provided file system path in order to assume an IAM role using a web identity, e.g. from within an Amazon EKS worker node.
required: false
inline-session-policy:
description: 'Inline session policy'
required: false

2
dist/cleanup/index.js generated vendored
View file

@ -17616,7 +17616,7 @@ function exportRegion(region) {
exports.exportRegion = exportRegion;
// Obtains account ID from STS Client and sets it as output
async function exportAccountId(credentialsClient, maskAccountId) {
const client = credentialsClient.getStsClient();
const client = credentialsClient.stsClient;
const identity = await client.send(new client_sts_1.GetCallerIdentityCommand({}));
const accountId = identity.Account;
if (!accountId) {

View file

@ -5,10 +5,10 @@ export interface CredentialsClientProps {
}
export declare class CredentialsClient {
region?: string;
private stsClient?;
private _stsClient?;
private readonly requestHandler?;
constructor(props: CredentialsClientProps);
getStsClient(): STSClient;
validateCredentials(expectedAccessKeyId?: string): Promise<void>;
get stsClient(): STSClient;
validateCredentials(expectedAccessKeyId?: string, roleChaining?: boolean): Promise<void>;
private loadCredentials;
}

48
dist/index.js generated vendored
View file

@ -16,14 +16,9 @@ const helpers_1 = __nccwpck_require__(9787);
const USER_AGENT = 'configure-aws-credentials-for-github-actions';
class CredentialsClient {
constructor(props) {
if (props.region) {
this.region = props.region;
}
else {
(0, core_1.info)('No region provided, using global STS endpoint');
}
this.region = props.region;
if (props.proxyServer) {
(0, core_1.info)('Configurint proxy handler for STS client');
(0, core_1.info)('Configuring proxy handler for STS client');
const handler = new https_proxy_agent_1.HttpsProxyAgent(props.proxyServer);
this.requestHandler = new node_http_handler_1.NodeHttpHandler({
httpAgent: handler,
@ -31,18 +26,17 @@ class CredentialsClient {
});
}
}
getStsClient() {
if (!this.stsClient) {
this.stsClient = new client_sts_1.STSClient({
region: this.region ? this.region : undefined,
get stsClient() {
if (!this._stsClient) {
this._stsClient = new client_sts_1.STSClient({
region: this.region,
customUserAgent: USER_AGENT,
requestHandler: this.requestHandler ? this.requestHandler : undefined,
useGlobalEndpoint: this.region ? false : true,
});
}
return this.stsClient;
return this._stsClient;
}
async validateCredentials(expectedAccessKeyId) {
async validateCredentials(expectedAccessKeyId, roleChaining) {
let credentials;
try {
credentials = await this.loadCredentials();
@ -53,9 +47,11 @@ class CredentialsClient {
catch (error) {
throw new Error(`Credentials could not be loaded, please check your action inputs: ${(0, helpers_1.errorMessage)(error)}`);
}
const actualAccessKeyId = credentials.accessKeyId;
if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) {
throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action');
if (!roleChaining) {
const actualAccessKeyId = credentials.accessKeyId;
if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) {
throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action');
}
}
}
async loadCredentials() {
@ -197,7 +193,7 @@ async function assumeRole(params) {
const keys = Object.keys(commonAssumeRoleParams);
keys.forEach((k) => commonAssumeRoleParams[k] === undefined && delete commonAssumeRoleParams[k]);
// Instantiate STS client
const stsClient = credentialsClient.getStsClient();
const stsClient = credentialsClient.stsClient;
// Assume role using one of three methods
switch (true) {
case !!webIdentityToken: {
@ -278,7 +274,7 @@ function exportRegion(region) {
exports.exportRegion = exportRegion;
// Obtains account ID from STS Client and sets it as output
async function exportAccountId(credentialsClient, maskAccountId) {
const client = credentialsClient.getStsClient();
const client = credentialsClient.stsClient;
const identity = await client.send(new client_sts_1.GetCallerIdentityCommand({}));
const accountId = identity.Account;
if (!accountId) {
@ -390,9 +386,7 @@ async function run() {
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: false }) ||
process.env['AWS_REGION'] ||
process.env['AWS_DEFAULT_REGION'];
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 maskAccountId = core.getInput('mask-aws-account-id', { required: false });
@ -410,6 +404,8 @@ async function run() {
for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({ arn: managedSessionPolicy });
}
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
// 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.
@ -420,14 +416,16 @@ async function run() {
!webIdentityTokenFile &&
!AccessKeyId &&
!disableOIDC &&
!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']) {
!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 &&
!disableOIDC);
!disableOIDC &&
!roleChaining);
};
// Validate and export region
if (region) {
@ -463,7 +461,7 @@ async function run() {
// cases where this action is on a self-hosted runner that doesn't have credentials
// configured correctly, and cases where the user intended to provide input
// credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId);
await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId);
}
// Get role credentials if configured to do so

View file

@ -13,17 +13,13 @@ export interface CredentialsClientProps {
export class CredentialsClient {
public region?: string;
private stsClient?: STSClient;
private _stsClient?: STSClient;
private readonly requestHandler?: NodeHttpHandler;
constructor(props: CredentialsClientProps) {
if (props.region) {
this.region = props.region;
} else {
info('No region provided, using global STS endpoint');
}
this.region = props.region;
if (props.proxyServer) {
info('Configurint proxy handler for STS client');
info('Configuring proxy handler for STS client');
const handler = new HttpsProxyAgent(props.proxyServer);
this.requestHandler = new NodeHttpHandler({
httpAgent: handler,
@ -32,19 +28,18 @@ export class CredentialsClient {
}
}
public getStsClient(): STSClient {
if (!this.stsClient) {
this.stsClient = new STSClient({
region: this.region ? this.region : undefined,
public get stsClient(): STSClient {
if (!this._stsClient) {
this._stsClient = new STSClient({
region: this.region,
customUserAgent: USER_AGENT,
requestHandler: this.requestHandler ? this.requestHandler : undefined,
useGlobalEndpoint: this.region ? false : true,
});
}
return this.stsClient;
return this._stsClient;
}
public async validateCredentials(expectedAccessKeyId?: string) {
public async validateCredentials(expectedAccessKeyId?: string, roleChaining?: boolean) {
let credentials;
try {
credentials = await this.loadCredentials();
@ -55,12 +50,12 @@ export class CredentialsClient {
throw new Error(`Credentials could not be loaded, please check your action inputs: ${errorMessage(error)}`);
}
const actualAccessKeyId = credentials.accessKeyId;
if (!roleChaining) {
const actualAccessKeyId = credentials.accessKeyId;
if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) {
throw new Error(
'Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action'
);
if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) {
throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action');
}
}
}

View file

@ -139,7 +139,7 @@ export async function assumeRole(params: assumeRoleParams) {
keys.forEach((k) => commonAssumeRoleParams[k] === undefined && delete commonAssumeRoleParams[k]);
// Instantiate STS client
const stsClient = credentialsClient.getStsClient();
const stsClient = credentialsClient.stsClient;
// Assume role using one of three methods
switch (true) {

View file

@ -35,7 +35,7 @@ export function exportRegion(region: string) {
// Obtains account ID from STS Client and sets it as output
export async function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: string) {
const client = credentialsClient.getStsClient();
const client = credentialsClient.stsClient;
const identity = await client.send(new GetCallerIdentityCommand({}));
const accountId = identity.Account;
if (!accountId) {

View file

@ -14,10 +14,7 @@ export async function run() {
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: false }) ||
process.env['AWS_REGION'] ||
process.env['AWS_DEFAULT_REGION'];
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 maskAccountId = core.getInput('mask-aws-account-id', { required: false });
@ -35,6 +32,8 @@ export async function run() {
for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({arn: managedSessionPolicy});
}
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
// Logic to decide whether to attempt to use OIDC or not
const useGitHubOIDCProvider = () => {
@ -47,7 +46,8 @@ export async function run() {
!webIdentityTokenFile &&
!AccessKeyId &&
!disableOIDC &&
!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
!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?'
@ -58,7 +58,8 @@ export async function run() {
!!process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] &&
!AccessKeyId &&
!webIdentityTokenFile &&
!disableOIDC
!disableOIDC &&
!roleChaining
);
};
@ -98,7 +99,7 @@ export async function run() {
// cases where this action is on a self-hosted runner that doesn't have credentials
// configured correctly, and cases where the user intended to provide input
// credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId);
await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
}