feat: role-chaining
This commit is contained in:
parent
20f59875fe
commit
d26f2d03f8
8 changed files with 66 additions and 69 deletions
29
action.yml
29
action.yml
|
|
@ -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
2
dist/cleanup/index.js
generated
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
6
dist/cleanup/src/CredentialsClient.d.ts
generated
vendored
6
dist/cleanup/src/CredentialsClient.d.ts
generated
vendored
|
|
@ -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
48
dist/index.js
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
15
src/index.ts
15
src/index.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue