Skip to content

Commit

Permalink
feat: Add option to provide external ID (#32)
Browse files Browse the repository at this point in the history
Fixes #28
  • Loading branch information
clareliguori committed Mar 3, 2020
1 parent 4d0082a commit 1c435bb
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 11 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
## 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`.
The role credentials will then be output instead of the ones 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`.
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 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 name is GitHubActions, and you can modify it by specifying the desired name in `role-session-name`.

Example:
Expand All @@ -64,10 +64,12 @@ Example:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
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-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
The session will have the name "GitHubActions" and be tagged with the following tags:
Expand Down
13 changes: 11 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,26 @@ inputs:
description: 'AWS Region, e.g. us-east-2'
required: true
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
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
role-duration-seconds:
description: "Role duration in seconds (default: 6 hours)"
required: false
role-session-name:
description: 'Role session name (default: GitHubActions)'
required: false
role-external-id:
description: 'The external ID of the role to assume'
required: false
outputs:
aws-account-id:
description: 'The AWS account ID for the provided credentials'
Expand Down
25 changes: 21 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ async function assumeRole(params) {
// Assume a role to get short-lived credentials using longer-lived credentials.
const isDefined = i => !!i;

const {roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, sessionToken, region} = params;
const {
roleToAssume,
roleExternalId,
roleDurationSeconds,
roleSessionName,
accessKeyId,
secretAccessKey,
sessionToken,
region,
} = params;
assert(
[roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, region].every(isDefined),
"Missing required input when assuming a Role."
Expand All @@ -32,7 +41,8 @@ async function assumeRole(params) {
const sts = new aws.STS({
accessKeyId, secretAccessKey, sessionToken, region, endpoint, customUserAgent: USER_AGENT
});
return sts.assumeRole({

const assumeRoleRequest = {
RoleArn: roleToAssume,
RoleSessionName: roleSessionName,
DurationSeconds: roleDurationSeconds,
Expand All @@ -45,7 +55,13 @@ async function assumeRole(params) {
{Key: 'Branch', Value: GITHUB_REF},
{Key: 'Commit', Value: GITHUB_SHA},
]
})
};

if (roleExternalId) {
assumeRoleRequest.ExternalId = roleExternalId;
}

return sts.assumeRole(assumeRoleRequest)
.promise()
.then(function (data) {
return {
Expand Down Expand Up @@ -121,13 +137,14 @@ async function run() {
const sessionToken = core.getInput('aws-session-token', { required: false });
const maskAccountId = core.getInput('mask-aws-account-id', { 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 roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;

// Get role credentials if configured to do so
if (roleToAssume) {
const roleCredentials = await assumeRole(
{accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleDurationSeconds, roleSessionName}
{accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleExternalId, roleDurationSeconds, roleSessionName}
);
exportCredentials(roleCredentials);
} else {
Expand Down
25 changes: 24 additions & 1 deletion index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
core.getInput = jest
.fn()
.mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));

process.env = {...process.env, GITHUB_WORKFLOW: 'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA'};

const sanitizedWorkflowName = 'Workflow__________+, -./:;<=>?@____________1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa'
Expand Down

0 comments on commit 1c435bb

Please sign in to comment.