feat: Add option to provide external ID (#32)

Fixes #28
This commit is contained in:
Clare Liguori
2020-03-02 18:11:27 -08:00
committed by GitHub
parent 4d0082acf8
commit 1c435bbd5e
4 changed files with 62 additions and 11 deletions
+6 -4
View File
@@ -51,9 +51,9 @@ We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/I
* [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows. * [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows.
## Assuming a role ## Assuming a role
If you would like to use the credentials you provide to this action to assume a role, you can do so by specifying the role ARN in `role-to-assume`. If you would like to use the static credentials you provide to this action to assume a role, you can do so by specifying the role ARN in `role-to-assume`.
The role credentials will then be output instead of the ones you have provided. The role credentials will then be configured in the Actions environment instead of the static credentials you have provided.
The default session duration is 6 hours, but if you would like to adjust this you can pass a duration to `role-duration-seconds`. The default session duration is 6 hours, but if you would like to adjust this you can pass a duration to `role-duration-seconds`.
The default session name is GitHubActions, and you can modify it by specifying the desired name in `role-session-name`. The default session name is GitHubActions, and you can modify it by specifying the desired name in `role-session-name`.
Example: Example:
@@ -64,10 +64,12 @@ Example:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2 aws-region: us-east-2
role-to-assume: arn:aws:iam::123456789100:role/role-to-assume role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }}
role-duration-seconds: 1200 role-duration-seconds: 1200
role-session-name: MySessionName role-session-name: MySessionName
``` ```
In this example, the secret `AWS_ROLE_TO_ASSUME` contains a string like `arn:aws:iam::123456789100:role/role-to-assume`.
### Session tagging ### Session tagging
The session will have the name "GitHubActions" and be tagged with the following tags: The session will have the name "GitHubActions" and be tagged with the following tags:
+11 -2
View File
@@ -17,10 +17,16 @@ inputs:
description: 'AWS Region, e.g. us-east-2' description: 'AWS Region, e.g. us-east-2'
required: true required: true
mask-aws-account-id: mask-aws-account-id:
description: "Whether to set the AWS account ID for these credentials as a secret value, so that it is masked in logs. Valid values are 'true' and 'false'. Defaults to true" description: >-
Whether to set the AWS account ID for these credentials as a secret value,
so that it is masked in logs. Valid values are 'true' and 'false'.
Defaults to true
required: false required: false
role-to-assume: role-to-assume:
description: "Use the provided credentials to assume a Role and output the assumed credentials for that Role rather than the provided credentials" description: >-
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 required: false
role-duration-seconds: role-duration-seconds:
description: "Role duration in seconds (default: 6 hours)" description: "Role duration in seconds (default: 6 hours)"
@@ -28,6 +34,9 @@ inputs:
role-session-name: role-session-name:
description: 'Role session name (default: GitHubActions)' description: 'Role session name (default: GitHubActions)'
required: false required: false
role-external-id:
description: 'The external ID of the role to assume'
required: false
outputs: outputs:
aws-account-id: aws-account-id:
description: 'The AWS account ID for the provided credentials' description: 'The AWS account ID for the provided credentials'
+21 -4
View File
@@ -15,7 +15,16 @@ async function assumeRole(params) {
// Assume a role to get short-lived credentials using longer-lived credentials. // Assume a role to get short-lived credentials using longer-lived credentials.
const isDefined = i => !!i; const isDefined = i => !!i;
const {roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, sessionToken, region} = params; const {
roleToAssume,
roleExternalId,
roleDurationSeconds,
roleSessionName,
accessKeyId,
secretAccessKey,
sessionToken,
region,
} = params;
assert( assert(
[roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, region].every(isDefined), [roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, region].every(isDefined),
"Missing required input when assuming a Role." "Missing required input when assuming a Role."
@@ -32,7 +41,8 @@ async function assumeRole(params) {
const sts = new aws.STS({ const sts = new aws.STS({
accessKeyId, secretAccessKey, sessionToken, region, endpoint, customUserAgent: USER_AGENT accessKeyId, secretAccessKey, sessionToken, region, endpoint, customUserAgent: USER_AGENT
}); });
return sts.assumeRole({
const assumeRoleRequest = {
RoleArn: roleToAssume, RoleArn: roleToAssume,
RoleSessionName: roleSessionName, RoleSessionName: roleSessionName,
DurationSeconds: roleDurationSeconds, DurationSeconds: roleDurationSeconds,
@@ -45,7 +55,13 @@ async function assumeRole(params) {
{Key: 'Branch', Value: GITHUB_REF}, {Key: 'Branch', Value: GITHUB_REF},
{Key: 'Commit', Value: GITHUB_SHA}, {Key: 'Commit', Value: GITHUB_SHA},
] ]
}) };
if (roleExternalId) {
assumeRoleRequest.ExternalId = roleExternalId;
}
return sts.assumeRole(assumeRoleRequest)
.promise() .promise()
.then(function (data) { .then(function (data) {
return { return {
@@ -121,13 +137,14 @@ async function run() {
const sessionToken = core.getInput('aws-session-token', { required: false }); const sessionToken = core.getInput('aws-session-token', { required: false });
const maskAccountId = core.getInput('mask-aws-account-id', { required: false }); const maskAccountId = core.getInput('mask-aws-account-id', { required: false });
const roleToAssume = core.getInput('role-to-assume', {required: false}); const roleToAssume = core.getInput('role-to-assume', {required: false});
const roleExternalId = core.getInput('role-external-id', { required: false });
const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME; const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME;
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME; const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
// Get role credentials if configured to do so // Get role credentials if configured to do so
if (roleToAssume) { if (roleToAssume) {
const roleCredentials = await assumeRole( const roleCredentials = await assumeRole(
{accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleDurationSeconds, roleSessionName} {accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleExternalId, roleDurationSeconds, roleSessionName}
); );
exportCredentials(roleCredentials); exportCredentials(roleCredentials);
} else { } else {
+24 -1
View File
@@ -260,11 +260,34 @@ describe('Configure AWS Credentials', () => {
}) })
}); });
test('role external ID provided', async () => {
core.getInput = jest
.fn()
.mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-external-id': 'abcdef'}));
await run();
expect(mockStsAssumeRole).toHaveBeenCalledWith({
RoleArn: ROLE_NAME,
RoleSessionName: 'GitHubActions',
DurationSeconds: 6 * 3600,
Tags: [
{Key: 'GitHub', Value: 'Actions'},
{Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
{Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW},
{Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
{Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
{Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
{Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
],
ExternalId: 'abcdef'
})
});
test('workflow name sanitized in role assumption tags', async () => { test('workflow name sanitized in role assumption tags', async () => {
core.getInput = jest core.getInput = jest
.fn() .fn()
.mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
process.env = {...process.env, GITHUB_WORKFLOW: 'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA'}; process.env = {...process.env, GITHUB_WORKFLOW: 'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA'};
const sanitizedWorkflowName = 'Workflow__________+, -./:;<=>?@____________1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa' const sanitizedWorkflowName = 'Workflow__________+, -./:;<=>?@____________1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa'