diff --git a/README.md b/README.md index 47e739c66..ef6fbfe8d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,39 @@ We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/I * [Rotate the credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials) used in GitHub Actions workflows regularly. * [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`. + +Example: +```yaml + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + 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-duration-seconds: 1200 +``` + +### Session tagging +The session will have the name "GitHubActions" and be tagged with the following tags: +(`GITHUB_` environment variable definitions can be [found here](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables)) + +| Key | Value| +| --- | --- | +| GitHub | "Actions" | +| Repository | GITHUB_REPOSITORY | +| Workflow | GITHUB_WORKFLOW | +| Action | GITHUB_ACTION | +| Actor | GITHUB_ACTOR | +| Branch | GITHUB_REF | +| Commit | GITHUB_SHA | + +_Note: all tag values must conform to [the requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html). Particularly, `GITHUB_WORKFLOW` will be truncated if it's too long. If `GITHUB_ACTOR` or `GITHUB_WORKFLOW` contain invalid charcters, the characters will be replaced with an '*'._ + ## License Summary This code is made available under the MIT license. diff --git a/index.js b/index.js index 744f8eacd..b6afab0d0 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,8 @@ const util = require('util'); // That seems like a reasonable default to use if no role duration is defined. const MAX_ACTION_RUNTIME = 6 * 3600; const USER_AGENT = 'configure-aws-credentials-for-github-actions'; +const MAX_TAG_VALUE_LENGTH = 256; +const SANITIZATION_CHARACTER = '*' async function assumeRole(params) { // Assume a role to get short-lived credentials using longer-lived credentials. @@ -36,9 +38,9 @@ async function assumeRole(params) { Tags: [ {Key: 'GitHub', Value: 'Actions'}, {Key: 'Repository', Value: GITHUB_REPOSITORY}, - {Key: 'Workflow', Value: GITHUB_WORKFLOW}, + {Key: 'Workflow', Value: sanitizeGithubWorkflowName(GITHUB_WORKFLOW)}, {Key: 'Action', Value: GITHUB_ACTION}, - {Key: 'Actor', Value: GITHUB_ACTOR}, + {Key: 'Actor', Value: sanitizeGithubActor(GITHUB_ACTOR)}, {Key: 'Branch', Value: GITHUB_REF}, {Key: 'Commit', Value: GITHUB_SHA}, ] @@ -53,6 +55,21 @@ async function assumeRole(params) { }); } +function sanitizeGithubActor(actor) { + // In some circumstances the actor may contain square brackets. For example, if they're a bot ('[bot]') + // Square brackets are not allowed in AWS session tags + return actor.replace(/\[|\]/g, SANITIZATION_CHARACTER) +} + +function sanitizeGithubWorkflowName(name) { + // Workflow names can be almost any valid UTF-8 string, but tags are more restrictive. + // This replaces anything not conforming to the tag restrictions by inverting the regular expression. + // See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html. + const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_.:/=+-@]/gu, SANITIZATION_CHARACTER); + const nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH) + return nameTruncated +} + function exportCredentials(params){ // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. // Setting the credentials as secrets masks them in Github Actions logs diff --git a/index.test.js b/index.test.js index 9db2f8df9..9b8617e2e 100644 --- a/index.test.js +++ b/index.test.js @@ -19,10 +19,11 @@ const ENVIRONMENT_VARIABLE_OVERRIDES = { GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME', GITHUB_WORKFLOW: 'MY-WORKFLOW-ID', GITHUB_ACTION: 'MY-ACTION-NAME', - GITHUB_ACTOR: 'MY-USERNAME', + GITHUB_ACTOR: 'MY-USERNAME[bot]', GITHUB_REF: 'MY-BRANCH', GITHUB_SHA: 'MY-COMMIT-ID', }; +const GITHUB_ACTOR_SANITIZED = 'MY-USERNAME*bot*' function mockGetInput(requestResponse) { return function (name, options) { // eslint-disable-line no-unused-vars @@ -208,7 +209,7 @@ describe('Configure AWS Credentials', () => { {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: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTOR}, + {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, ] @@ -230,7 +231,33 @@ describe('Configure AWS Credentials', () => { {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: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTOR}, + {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, + {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, + {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, + ] + }) + }); + + 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' + + 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: sanitizedWorkflowName}, + {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}, ]