├── .github ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── check.yml │ ├── package.yml │ └── codeql-analysis.yml ├── CODE_OF_CONDUCT.md ├── .eslintrc.json ├── package.json ├── LICENSE ├── .mergify.yml ├── .gitignore ├── cleanup.js ├── cleanup.test.js ├── action.yml ├── CONTRIBUTING.md ├── CHANGELOG.md ├── README.md ├── index.js ├── THIRD-PARTY ├── dist └── cleanup │ └── index.js └── index.test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | day: tuesday 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | [pull_request] 3 | 4 | name: Check 5 | 6 | jobs: 7 | check: 8 | name: Run Unit Tests 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Run tests 14 | run: | 15 | npm ci 16 | npm test 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018 15 | }, 16 | "rules": { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Package 7 | 8 | jobs: 9 | check: 10 | name: Package distribution file 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | ref: master 17 | - name: Package 18 | run: | 19 | npm ci 20 | npm test 21 | npm run package 22 | - name: Commit 23 | run: | 24 | git config --global user.name "GitHub Actions" 25 | git add dist/ 26 | git commit -m "chore: Update dist" || echo "No changes to commit" 27 | git push origin HEAD:master 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-actions-configure-aws-credentials", 3 | "version": "1.5.5", 4 | "description": "Configure AWS Credentials", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint **.js", 8 | "package": "ncc build index.js -o dist && ncc build cleanup.js -o dist/cleanup", 9 | "test": "npm run lint && jest --coverage --verbose" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/aws-actions/configure-aws-credentials.git" 14 | }, 15 | "keywords": [ 16 | "AWS", 17 | "GitHub", 18 | "Actions", 19 | "JavaScript" 20 | ], 21 | "author": "AWS", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/aws-actions/configure-aws-credentials/issues" 25 | }, 26 | "homepage": "https://github.com/aws-actions/configure-aws-credentials#readme", 27 | "dependencies": { 28 | "@actions/core": "^1.2.6", 29 | "aws-sdk": "^2.815.0" 30 | }, 31 | "devDependencies": { 32 | "@zeit/ncc": "^0.22.3", 33 | "eslint": "^7.16.0", 34 | "jest": "^26.6.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2019 Amazon.com, Inc. or its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatically merge on CI success and review approval 3 | conditions: 4 | - base~=master|integ-tests 5 | - "#approved-reviews-by>=1" 6 | - approved-reviews-by=@aws-actions/aws-ecs-devx 7 | - -approved-reviews-by~=author 8 | - status-success=Run Unit Tests 9 | - status-success=Semantic Pull Request 10 | - label!=work-in-progress 11 | - -title~=(WIP|wip) 12 | - -merged 13 | - -closed 14 | - author!=dependabot[bot] 15 | actions: 16 | merge: 17 | method: squash 18 | strict: smart 19 | strict_method: merge 20 | 21 | - name: Automatically approve and merge Dependabot PRs 22 | conditions: 23 | - base=master 24 | - author=dependabot[bot] 25 | - status-success=Run Unit Tests 26 | - status-success=Semantic Pull Request 27 | - -title~=(WIP|wip) 28 | - -label~=(blocked|do-not-merge) 29 | - -merged 30 | - -closed 31 | actions: 32 | review: 33 | type: APPROVE 34 | merge: 35 | method: squash 36 | strict: smart+fasttrack 37 | strict_method: merge 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Editors 4 | .vscode 5 | .idea 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Other Dependency directories 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # next.js build output 66 | .next 67 | -------------------------------------------------------------------------------- /cleanup.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | 3 | /** 4 | * When the GitHub Actions job is done, clean up any environment variables that 5 | * may have been set by the configure-aws-credentials steps in the job. 6 | * 7 | * Environment variables are not intended to be shared across different jobs in 8 | * the same GitHub Actions workflow: GitHub Actions documentation states that 9 | * each job runs in a fresh instance. However, doing our own cleanup will 10 | * give us additional assurance that these environment variables are not shared 11 | * with any other jobs. 12 | */ 13 | 14 | async function cleanup() { 15 | try { 16 | // The GitHub Actions toolkit does not have an option to completely unset 17 | // environment variables, so we overwrite the current value with an empty 18 | // string. The AWS CLI and AWS SDKs will behave correctly: they treat an 19 | // empty string value as if the environment variable does not exist. 20 | core.exportVariable('AWS_ACCESS_KEY_ID', ''); 21 | core.exportVariable('AWS_SECRET_ACCESS_KEY', ''); 22 | core.exportVariable('AWS_SESSION_TOKEN', ''); 23 | core.exportVariable('AWS_DEFAULT_REGION', ''); 24 | core.exportVariable('AWS_REGION', ''); 25 | } 26 | catch (error) { 27 | core.setFailed(error.message); 28 | } 29 | } 30 | 31 | module.exports = cleanup; 32 | 33 | /* istanbul ignore next */ 34 | if (require.main === module) { 35 | cleanup(); 36 | } 37 | -------------------------------------------------------------------------------- /cleanup.test.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const cleanup = require('./cleanup.js'); 3 | 4 | jest.mock('@actions/core'); 5 | 6 | const FAKE_ACCESS_KEY_ID = 'MY-AWS-ACCESS-KEY-ID'; 7 | const FAKE_SECRET_ACCESS_KEY = 'MY-AWS-SECRET-ACCESS-KEY'; 8 | const FAKE_SESSION_TOKEN = 'MY-AWS-SESSION-TOKEN'; 9 | const FAKE_REGION = 'fake-region-1'; 10 | const ACTION_ENVIRONMENT_VARIABLES = { 11 | AWS_ACCESS_KEY_ID: FAKE_ACCESS_KEY_ID, 12 | AWS_SECRET_ACCESS_KEY: FAKE_SECRET_ACCESS_KEY, 13 | AWS_SESSION_TOKEN: FAKE_SESSION_TOKEN, 14 | AWS_DEFAULT_REGION: FAKE_REGION, 15 | AWS_REGION: FAKE_REGION, 16 | }; 17 | 18 | describe('Configure AWS Credentials', () => { 19 | const OLD_ENV = process.env; 20 | 21 | beforeEach(() => { 22 | jest.resetModules(); 23 | process.env = {...OLD_ENV, ...ACTION_ENVIRONMENT_VARIABLES}; 24 | }); 25 | 26 | afterEach(() => { 27 | process.env = OLD_ENV; 28 | }); 29 | 30 | test('replaces AWS credential and region env vars with empty strings', async () => { 31 | await cleanup(); 32 | expect(core.setFailed).toHaveBeenCalledTimes(0); 33 | expect(core.exportVariable).toHaveBeenCalledTimes(5); 34 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', ''); 35 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', ''); 36 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', ''); 37 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', ''); 38 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', ''); 39 | }); 40 | 41 | test('error is caught and fails the action', async () => { 42 | core.exportVariable.mockReset(); 43 | core.exportVariable.mockImplementation(() => { 44 | throw new Error(); 45 | }); 46 | 47 | await cleanup(); 48 | 49 | expect(core.setFailed).toBeCalled(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: '"Configure AWS Credentials" Action For GitHub Actions' 2 | description: 'Configure AWS credential and region environment variables for use with the AWS CLI and AWS SDKs' 3 | branding: 4 | icon: 'cloud' 5 | color: 'orange' 6 | inputs: 7 | aws-access-key-id: 8 | description: >- 9 | AWS Access Key ID. This input is required if running in the GitHub hosted environment. 10 | It is optional if running in a self-hosted environment that already has AWS credentials, 11 | for example on an EC2 instance. 12 | required: false 13 | aws-secret-access-key: 14 | description: >- 15 | AWS Secret Access Key. This input is required if running in the GitHub hosted environment. 16 | It is optional if running in a self-hosted environment that already has AWS credentials, 17 | for example on an EC2 instance. 18 | required: false 19 | aws-session-token: 20 | description: 'AWS Session Token' 21 | required: false 22 | aws-region: 23 | description: 'AWS Region, e.g. us-east-2' 24 | required: true 25 | mask-aws-account-id: 26 | description: >- 27 | Whether to set the AWS account ID for these credentials as a secret value, 28 | so that it is masked in logs. Valid values are 'true' and 'false'. 29 | Defaults to true 30 | required: false 31 | role-to-assume: 32 | description: >- 33 | Use the provided credentials to assume an IAM role and configure the Actions 34 | environment with the assumed role credentials rather than with the provided 35 | credentials 36 | required: false 37 | role-duration-seconds: 38 | description: "Role duration in seconds (default: 6 hours)" 39 | required: false 40 | role-session-name: 41 | description: 'Role session name (default: GitHubActions)' 42 | required: false 43 | role-external-id: 44 | description: 'The external ID of the role to assume' 45 | required: false 46 | role-skip-session-tagging: 47 | description: 'Skip session tagging during role assumption' 48 | required: false 49 | outputs: 50 | aws-account-id: 51 | description: 'The AWS account ID for the provided credentials' 52 | runs: 53 | using: 'node12' 54 | main: 'dist/index.js' 55 | post: 'dist/cleanup/index.js' 56 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 0 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-actions/configure-aws-credentials/issues), or [recently closed](https://github.com/aws-actions/configure-aws-credentials/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-actions/configure-aws-credentials/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-actions/configure-aws-credentials/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.5.5](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.4...v1.5.5) (2020-11-24) 6 | 7 | ### [1.5.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.3...v1.5.4) (2020-10-29) 8 | 9 | ### [1.5.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.2...v1.5.3) (2020-10-05) 10 | 11 | ### [1.5.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.1...v1.5.2) (2020-08-25) 12 | 13 | ### [1.5.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.0...v1.5.1) (2020-08-11) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * make GITHUB_REF env var optional ([#82](https://github.com/aws-actions/configure-aws-credentials/issues/82)) ([ba5041f](https://github.com/aws-actions/configure-aws-credentials/commit/ba5041f7bb4990ac5d10d9009de69e639ebee3df)), closes [#92](https://github.com/aws-actions/configure-aws-credentials/issues/92) 19 | 20 | ## [1.5.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.4...v1.5.0) (2020-07-29) 21 | 22 | 23 | ### Features 24 | 25 | * Add post-job action cleanup of credentials and region env vars ([#101](https://github.com/aws-actions/configure-aws-credentials/issues/101)) ([d19cafc](https://github.com/aws-actions/configure-aws-credentials/commit/d19cafcdd1be7e3358f84574a00df37af494036a)) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * Mask assume role response in debug output ([#102](https://github.com/aws-actions/configure-aws-credentials/issues/102)) ([df7d846](https://github.com/aws-actions/configure-aws-credentials/commit/df7d84616183de7ed37e53e1980284a07e56b216)) 31 | 32 | ### [1.4.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.3...v1.4.4) (2020-07-17) 33 | 34 | ### [1.4.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.2...v1.4.3) (2020-07-14) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * Make tagging optional ([#92](https://github.com/aws-actions/configure-aws-credentials/issues/92)) ([baf85d8](https://github.com/aws-actions/configure-aws-credentials/commit/baf85d8be969f190df9bc9153f06958c32ef3828)) 40 | 41 | ### [1.4.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.1...v1.4.2) (2020-06-30) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * add comma to set of special characters ([#78](https://github.com/aws-actions/configure-aws-credentials/issues/78)) ([f04843b](https://github.com/aws-actions/configure-aws-credentials/commit/f04843b510a6c8adf77eed907a616cf00a99970d)) 47 | 48 | ### [1.4.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.0...v1.4.1) (2020-06-09) 49 | 50 | ## [1.4.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.5...v1.4.0) (2020-06-03) 51 | 52 | 53 | ### Features 54 | 55 | * Refresh and validate credentials after setting env var creds ([#71](https://github.com/aws-actions/configure-aws-credentials/issues/71)) ([472e549](https://github.com/aws-actions/configure-aws-credentials/commit/472e549195ba1f153e9fb72e39dc2a094e5de13e)) 56 | 57 | ### [1.3.5](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.4...v1.3.5) (2020-05-27) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * clear session token env var if present for non-session credentials ([#65](https://github.com/aws-actions/configure-aws-credentials/issues/65)) ([0c2c1f7](https://github.com/aws-actions/configure-aws-credentials/commit/0c2c1f7c129971b6f433551b1f4ba4a6a9cc8b70)) 63 | 64 | ### [1.3.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.3...v1.3.4) (2020-05-18) 65 | 66 | ### [1.3.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.2...v1.3.3) (2020-04-02) 67 | 68 | ### [1.3.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.1...v1.3.2) (2020-03-18) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * let the AWS SDK determine the STS regional endpoint ([#48](https://github.com/aws-actions/configure-aws-credentials/issues/48)) ([fc72bd3](https://github.com/aws-actions/configure-aws-credentials/commit/fc72bd38dbe25493f5113760c9c6e1ef2f6f9a0e)) 74 | 75 | ### [1.3.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.0...v1.3.1) (2020-03-06) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * validate region input string ([#44](https://github.com/aws-actions/configure-aws-credentials/issues/44)) ([3d568d2](https://github.com/aws-actions/configure-aws-credentials/commit/3d568d2c4359304d46d9bd1b4d9f69e088ccbf7b)) 81 | 82 | ## [1.3.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.2.0...v1.3.0) (2020-03-06) 83 | 84 | 85 | ### Features 86 | 87 | * don't require access key credentials for self-hosted runners ([#42](https://github.com/aws-actions/configure-aws-credentials/issues/42)) ([a20ed60](https://github.com/aws-actions/configure-aws-credentials/commit/a20ed6025224ca999786c8d4e687f119cfedec65)) 88 | 89 | ## [1.2.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.2...v1.2.0) (2020-03-06) 90 | 91 | 92 | ### Features 93 | 94 | * Add option to provide external ID ([#32](https://github.com/aws-actions/configure-aws-credentials/issues/32)) ([1c435bb](https://github.com/aws-actions/configure-aws-credentials/commit/1c435bbd5e1f1d36cdd703da5c4d6ee1ad91efac)), closes [#28](https://github.com/aws-actions/configure-aws-credentials/issues/28) 95 | * Have an ability to configure session name ([#29](https://github.com/aws-actions/configure-aws-credentials/issues/29)) ([4d0082a](https://github.com/aws-actions/configure-aws-credentials/commit/4d0082acf8b4102597f2570a056a320194f13e63)) 96 | * infer role ARN if given role name ([#35](https://github.com/aws-actions/configure-aws-credentials/issues/35)) ([96c6f7e](https://github.com/aws-actions/configure-aws-credentials/commit/96c6f7e07b5fabc5a907fce84745ea625eeb005d)) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * mask both source and role credentials ([#40](https://github.com/aws-actions/configure-aws-credentials/issues/40)) ([816f5cc](https://github.com/aws-actions/configure-aws-credentials/commit/816f5cc0cf79541b2a6d639e8f93ae43aadaf09c)) 102 | 103 | ### [1.1.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.1...v1.1.2) (2020-02-12) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * change sanitization character from '*' to '_' ([55f6a14](https://github.com/aws-actions/configure-aws-credentials/commit/55f6a14016cb47190b15751dcce450441bba35e3)) 109 | 110 | ### [1.1.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.0...v1.1.1) (2020-02-07) 111 | 112 | ## [1.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.0.1...v1.1.0) (2020-02-06) 113 | 114 | 115 | ### Features 116 | 117 | * add support for assuming a role ([#17](https://github.com/aws-actions/configure-aws-credentials/issues/17)) ([25960ab](https://github.com/aws-actions/configure-aws-credentials/commit/25960ab0950f92074b17fa0cb8ff33eeaa1615f0)) 118 | * Build and integ test scripts for pipeline ([52bc82a](https://github.com/aws-actions/configure-aws-credentials/commit/52bc82a29bb08f2f8f87a92f380149945eb8d6f1)) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * create workflows dir in integ-tests ([3de962e](https://github.com/aws-actions/configure-aws-credentials/commit/3de962edc9fe1169ff6032bef94b934be53211d1)) 124 | * remove buildspecs ([260c6cc](https://github.com/aws-actions/configure-aws-credentials/commit/260c6cc0ed07cbd8ff2a7a74066ad9f67ecc82f7)) 125 | * remove release script ([1436a16](https://github.com/aws-actions/configure-aws-credentials/commit/1436a160af84c3a086a812f98daf457a042274cb)) 126 | * resolve commit ID in integ test script ([77f2df7](https://github.com/aws-actions/configure-aws-credentials/commit/77f2df7a41882637145683bb1acf60bb04fddc24)) 127 | * sanitize AWS session tags ([#20](https://github.com/aws-actions/configure-aws-credentials/issues/20)) ([4faf8cd](https://github.com/aws-actions/configure-aws-credentials/commit/4faf8cd19a5b6cc50c9c66c89dd32d7e6e51bd8a)) 128 | * set role credentials as secrets to mask them in logs ([#19](https://github.com/aws-actions/configure-aws-credentials/issues/19)) ([e2fd53a](https://github.com/aws-actions/configure-aws-credentials/commit/e2fd53ab66a094843f790497dcc950894c245786)) 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## "Configure AWS Credentials" Action For GitHub Actions 2 | 3 | Configure AWS credential and region environment variables for use in other GitHub Actions. The environment variables will be detected by both the AWS SDKs and the AWS CLI to determine the credentials and region to use for AWS API calls. 4 | 5 | **Table of Contents** 6 | 7 | 8 | 9 | - [Usage](#usage) 10 | - [Credentials](#credentials) 11 | - [Assuming a Role](#assuming-a-role) 12 | + [Permissions for assuming a role](#permissions-for-assuming-a-role) 13 | + [Session tagging](#session-tagging) 14 | - [Self-Hosted Runners](#self-hosted-runners) 15 | - [License Summary](#license-summary) 16 | - [Security Disclosures](#security-disclosures) 17 | 18 | 19 | 20 | ## Usage 21 | 22 | Add the following step to your workflow: 23 | 24 | ```yaml 25 | - name: Configure AWS Credentials 26 | uses: aws-actions/configure-aws-credentials@v1 27 | with: 28 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 29 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 30 | aws-region: us-east-2 31 | ``` 32 | 33 | For example, you can use this action with the AWS CLI available in [GitHub's hosted virtual environments](https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners). 34 | You can also run this action multiple times to use different AWS accounts, regions, or IAM roles in the same GitHub Actions workflow job. 35 | 36 | ```yaml 37 | jobs: 38 | deploy: 39 | name: Upload to Amazon S3 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v2 45 | 46 | - name: Configure AWS credentials from Test account 47 | uses: aws-actions/configure-aws-credentials@v1 48 | with: 49 | aws-access-key-id: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }} 50 | aws-secret-access-key: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }} 51 | aws-region: us-east-1 52 | 53 | - name: Copy files to the test website with the AWS CLI 54 | run: | 55 | aws s3 sync . s3://my-s3-test-website-bucket 56 | 57 | - name: Configure AWS credentials from Production account 58 | uses: aws-actions/configure-aws-credentials@v1 59 | with: 60 | aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }} 61 | aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }} 62 | aws-region: us-west-2 63 | 64 | - name: Copy files to the production website with the AWS CLI 65 | run: | 66 | aws s3 sync . s3://my-s3-prod-website-bucket 67 | ``` 68 | 69 | See [action.yml](action.yml) for the full documentation for this action's inputs and outputs. 70 | 71 | ## Credentials 72 | 73 | We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) for the AWS credentials used in GitHub Actions workflows, including: 74 | * Do not store credentials in your repository's code. You may use [GitHub Actions secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) to store credentials and redact credentials from GitHub Actions workflow logs. 75 | * [Create an individual IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#create-iam-users) with an access key for use in GitHub Actions workflows, preferably one per repository. Do not use the AWS account root user access key. 76 | * [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows. 77 | * [Rotate the credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials) used in GitHub Actions workflows regularly. 78 | * [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. 79 | 80 | ## Assuming a Role 81 | 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`. 82 | The role credentials will then be configured in the Actions environment instead of the static credentials you have provided. 83 | The default session duration is 6 hours, but if you would like to adjust this you can pass a duration to `role-duration-seconds`. 84 | The default session name is GitHubActions, and you can modify it by specifying the desired name in `role-session-name`. 85 | 86 | Example: 87 | ```yaml 88 | - name: Configure AWS Credentials 89 | uses: aws-actions/configure-aws-credentials@v1 90 | with: 91 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 92 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 93 | aws-region: us-east-2 94 | role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} 95 | role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} 96 | role-duration-seconds: 1200 97 | role-session-name: MySessionName 98 | ``` 99 | In this example, the secret `AWS_ROLE_TO_ASSUME` contains a string like `arn:aws:iam::123456789100:role/my-github-actions-role`. To assume a role in the same account as the static credentials, you can simply specify the role name, like `role-to-assume: my-github-actions-role`. 100 | 101 | ### Permissions for assuming a role 102 | 103 | In order to assume a role, the IAM user for the static credentials must have the following permissions: 104 | ```json 105 | { 106 | "Version": "2012-10-17", 107 | "Statement": [ 108 | { 109 | "Action": [ 110 | "sts:AssumeRole", 111 | "sts:TagSession" 112 | ], 113 | "Resource": "arn:aws:iam::123456789012:role/my-github-actions-role", 114 | "Effect": "Allow" 115 | } 116 | ] 117 | } 118 | ``` 119 | 120 | The role's trust policy must allow the IAM user to assume the role: 121 | ```json 122 | { 123 | "Version": "2012-10-17", 124 | "Statement": [ 125 | { 126 | "Sid": "AllowIamUserAssumeRole", 127 | "Effect": "Allow", 128 | "Action": "sts:AssumeRole", 129 | "Principal": {"AWS": "arn:aws:iam::123456789012:user/my-github-actions-user"}, 130 | "Condition": { 131 | "StringEquals": {"sts:ExternalId": "Example987"} 132 | } 133 | }, 134 | { 135 | "Sid": "AllowPassSessionTags", 136 | "Effect": "Allow", 137 | "Action": "sts:TagSession", 138 | "Principal": {"AWS": "arn:aws:iam::123456789012:user/my-github-actions-user"} 139 | } 140 | ] 141 | } 142 | ``` 143 | 144 | ### Session tagging 145 | The session will have the name "GitHubActions" and be tagged with the following tags: 146 | (`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)) 147 | 148 | | Key | Value| 149 | | --- | --- | 150 | | GitHub | "Actions" | 151 | | Repository | GITHUB_REPOSITORY | 152 | | Workflow | GITHUB_WORKFLOW | 153 | | Action | GITHUB_ACTION | 154 | | Actor | GITHUB_ACTOR | 155 | | Branch | GITHUB_REF | 156 | | Commit | GITHUB_SHA | 157 | 158 | _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 '*'._ 159 | 160 | The action will use session tagging by default during role assumption. You can skip this session tagging by providing `role-skip-session-tagging` as true in the action's inputs: 161 | 162 | ```yaml 163 | uses: aws-actions/configure-aws-credentials@v1 164 | with: 165 | role-skip-session-tagging: true 166 | ``` 167 | 168 | ## Self-Hosted Runners 169 | 170 | If you run your GitHub Actions in a [self-hosted runner](https://help.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) that already has access to AWS credentials, such as an EC2 instance, then you do not need to provide IAM user access key credentials to this action. 171 | 172 | If no access key credentials are given in the action inputs, this action will use credentials from the runner environment using the [default methods for the AWS SDK for Javascript](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html). 173 | 174 | You can use this action to simply configure the region and account ID in the environment, and then use the runner's credentials for all AWS API calls made by your Actions workflow: 175 | ```yaml 176 | uses: aws-actions/configure-aws-credentials@v1 177 | with: 178 | aws-region: us-east-2 179 | ``` 180 | In this case, your runner's credentials must have permissions to call any AWS APIs called by your Actions workflow. 181 | 182 | Or, you can use this action to assume a role, and then use the role credentials for all AWS API calls made by your Actions workflow: 183 | ```yaml 184 | uses: aws-actions/configure-aws-credentials@v1 185 | with: 186 | aws-region: us-east-2 187 | role-to-assume: my-github-actions-role 188 | ``` 189 | In this case, your runner's credentials must have permissions to assume the role. 190 | 191 | ## License Summary 192 | 193 | This code is made available under the MIT license. 194 | 195 | ## Security Disclosures 196 | 197 | If you would like to report a potential security issue in this project, please do not create a GitHub issue. Instead, please follow the instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). 198 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const aws = require('aws-sdk'); 3 | const assert = require('assert'); 4 | 5 | // The max time that a GitHub action is allowed to run is 6 hours. 6 | // That seems like a reasonable default to use if no role duration is defined. 7 | const MAX_ACTION_RUNTIME = 6 * 3600; 8 | const USER_AGENT = 'configure-aws-credentials-for-github-actions'; 9 | const MAX_TAG_VALUE_LENGTH = 256; 10 | const SANITIZATION_CHARACTER = '_'; 11 | const ROLE_SESSION_NAME = 'GitHubActions'; 12 | const REGION_REGEX = /^[a-z0-9-]+$/g; 13 | 14 | async function assumeRole(params) { 15 | // Assume a role to get short-lived credentials using longer-lived credentials. 16 | const isDefined = i => !!i; 17 | 18 | const { 19 | sourceAccountId, 20 | roleToAssume, 21 | roleExternalId, 22 | roleDurationSeconds, 23 | roleSessionName, 24 | region, 25 | roleSkipSessionTagging 26 | } = params; 27 | assert( 28 | [sourceAccountId, roleToAssume, roleDurationSeconds, roleSessionName, region].every(isDefined), 29 | "Missing required input when assuming a Role." 30 | ); 31 | 32 | const {GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_SHA} = process.env; 33 | assert( 34 | [GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_SHA].every(isDefined), 35 | 'Missing required environment value. Are you running in GitHub Actions?' 36 | ); 37 | 38 | const sts = getStsClient(region); 39 | 40 | let roleArn = roleToAssume; 41 | if (!roleArn.startsWith('arn:aws')) { 42 | // Supports only 'aws' partition. Customers in other partitions ('aws-cn') will need to provide full ARN 43 | roleArn = `arn:aws:iam::${sourceAccountId}:role/${roleArn}`; 44 | } 45 | const tagArray = [ 46 | {Key: 'GitHub', Value: 'Actions'}, 47 | {Key: 'Repository', Value: GITHUB_REPOSITORY}, 48 | {Key: 'Workflow', Value: sanitizeGithubWorkflowName(GITHUB_WORKFLOW)}, 49 | {Key: 'Action', Value: GITHUB_ACTION}, 50 | {Key: 'Actor', Value: sanitizeGithubActor(GITHUB_ACTOR)}, 51 | {Key: 'Commit', Value: GITHUB_SHA}, 52 | ]; 53 | 54 | if (isDefined(process.env.GITHUB_REF)) { 55 | tagArray.push({Key: 'Branch', Value: process.env.GITHUB_REF}); 56 | } 57 | 58 | const roleSessionTags = roleSkipSessionTagging ? undefined : tagArray; 59 | 60 | const assumeRoleRequest = { 61 | RoleArn: roleArn, 62 | RoleSessionName: roleSessionName, 63 | DurationSeconds: roleDurationSeconds, 64 | Tags: roleSessionTags 65 | }; 66 | 67 | if (roleExternalId) { 68 | assumeRoleRequest.ExternalId = roleExternalId; 69 | } 70 | 71 | return sts.assumeRole(assumeRoleRequest) 72 | .promise() 73 | .then(function (data) { 74 | return { 75 | accessKeyId: data.Credentials.AccessKeyId, 76 | secretAccessKey: data.Credentials.SecretAccessKey, 77 | sessionToken: data.Credentials.SessionToken, 78 | }; 79 | }); 80 | } 81 | 82 | function sanitizeGithubActor(actor) { 83 | // In some circumstances the actor may contain square brackets. For example, if they're a bot ('[bot]') 84 | // Square brackets are not allowed in AWS session tags 85 | return actor.replace(/\[|\]/g, SANITIZATION_CHARACTER) 86 | } 87 | 88 | function sanitizeGithubWorkflowName(name) { 89 | // Workflow names can be almost any valid UTF-8 string, but tags are more restrictive. 90 | // This replaces anything not conforming to the tag restrictions by inverting the regular expression. 91 | // See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html. 92 | const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_:/=+.-@-]/gu, SANITIZATION_CHARACTER); 93 | const nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH) 94 | return nameTruncated 95 | } 96 | 97 | function exportCredentials(params){ 98 | // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. 99 | // Setting the credentials as secrets masks them in Github Actions logs 100 | const {accessKeyId, secretAccessKey, sessionToken} = params; 101 | 102 | // AWS_ACCESS_KEY_ID: 103 | // Specifies an AWS access key associated with an IAM user or role 104 | core.setSecret(accessKeyId); 105 | core.exportVariable('AWS_ACCESS_KEY_ID', accessKeyId); 106 | 107 | // AWS_SECRET_ACCESS_KEY: 108 | // Specifies the secret key associated with the access key. This is essentially the "password" for the access key. 109 | core.setSecret(secretAccessKey); 110 | core.exportVariable('AWS_SECRET_ACCESS_KEY', secretAccessKey); 111 | 112 | // AWS_SESSION_TOKEN: 113 | // Specifies the session token value that is required if you are using temporary security credentials. 114 | if (sessionToken) { 115 | core.setSecret(sessionToken); 116 | core.exportVariable('AWS_SESSION_TOKEN', sessionToken); 117 | } else if (process.env.AWS_SESSION_TOKEN) { 118 | // clear session token from previous credentials action 119 | core.exportVariable('AWS_SESSION_TOKEN', ''); 120 | } 121 | } 122 | 123 | function exportRegion(region) { 124 | // AWS_DEFAULT_REGION and AWS_REGION: 125 | // Specifies the AWS Region to send requests to 126 | core.exportVariable('AWS_DEFAULT_REGION', region); 127 | core.exportVariable('AWS_REGION', region); 128 | } 129 | 130 | async function exportAccountId(maskAccountId, region) { 131 | // Get the AWS account ID 132 | const sts = getStsClient(region); 133 | const identity = await sts.getCallerIdentity().promise(); 134 | const accountId = identity.Account; 135 | if (!maskAccountId || maskAccountId.toLowerCase() == 'true') { 136 | core.setSecret(accountId); 137 | } 138 | core.setOutput('aws-account-id', accountId); 139 | return accountId; 140 | } 141 | 142 | function loadCredentials() { 143 | // Force the SDK to re-resolve credentials with the default provider chain. 144 | // 145 | // This action typically sets credentials in the environment via environment variables. 146 | // The SDK never refreshes those env-var-based credentials after initial load. 147 | // In case there were already env-var creds set in the actions environment when this action 148 | // loaded, this action needs to refresh the SDK creds after overwriting those environment variables. 149 | // 150 | // The credentials object needs to be entirely recreated (instead of simply refreshed), 151 | // because the credential object type could change when this action writes env var creds. 152 | // For example, the first load could return EC2 instance metadata credentials 153 | // in a self-hosted runner, and the second load could return environment credentials 154 | // from an assume-role call in this action. 155 | aws.config.credentials = null; 156 | 157 | return new Promise((resolve, reject) => { 158 | aws.config.getCredentials((err) => { 159 | if (err) { 160 | reject(err); 161 | } 162 | resolve(aws.config.credentials); 163 | }) 164 | }); 165 | } 166 | 167 | async function validateCredentials(expectedAccessKeyId) { 168 | let credentials; 169 | try { 170 | credentials = await loadCredentials(); 171 | 172 | if (!credentials.accessKeyId) { 173 | throw new Error('Access key ID empty after loading credentials'); 174 | } 175 | } catch (error) { 176 | throw new Error(`Credentials could not be loaded, please check your action inputs: ${error.message}`); 177 | } 178 | 179 | const actualAccessKeyId = credentials.accessKeyId; 180 | 181 | if (expectedAccessKeyId && expectedAccessKeyId != actualAccessKeyId) { 182 | throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action'); 183 | } 184 | } 185 | 186 | function getStsClient(region) { 187 | return new aws.STS({ 188 | region, 189 | stsRegionalEndpoints: 'regional', 190 | customUserAgent: USER_AGENT 191 | }); 192 | } 193 | 194 | async function run() { 195 | try { 196 | // Get inputs 197 | const accessKeyId = core.getInput('aws-access-key-id', { required: false }); 198 | const secretAccessKey = core.getInput('aws-secret-access-key', { required: false }); 199 | const region = core.getInput('aws-region', { required: true }); 200 | const sessionToken = core.getInput('aws-session-token', { required: false }); 201 | const maskAccountId = core.getInput('mask-aws-account-id', { required: false }); 202 | const roleToAssume = core.getInput('role-to-assume', {required: false}); 203 | const roleExternalId = core.getInput('role-external-id', { required: false }); 204 | const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME; 205 | const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME; 206 | const roleSkipSessionTagging = core.getInput('role-skip-session-tagging', { required: false }); 207 | 208 | if (!region.match(REGION_REGEX)) { 209 | throw new Error(`Region is not valid: ${region}`); 210 | } 211 | 212 | exportRegion(region); 213 | 214 | // Always export the source credentials and account ID. 215 | // The STS client for calling AssumeRole pulls creds from the environment. 216 | // Plus, in the assume role case, if the AssumeRole call fails, we want 217 | // the source credentials and accound ID to already be masked as secrets 218 | // in any error messages. 219 | if (accessKeyId) { 220 | if (!secretAccessKey) { 221 | throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided"); 222 | } 223 | 224 | exportCredentials({accessKeyId, secretAccessKey, sessionToken}); 225 | } 226 | 227 | // Regardless of whether any source credentials were provided as inputs, 228 | // validate that the SDK can actually pick up credentials. This validates 229 | // cases where this action is on a self-hosted runner that doesn't have credentials 230 | // configured correctly, and cases where the user intended to provide input 231 | // credentials but the secrets inputs resolved to empty strings. 232 | await validateCredentials(accessKeyId); 233 | 234 | const sourceAccountId = await exportAccountId(maskAccountId, region); 235 | 236 | // Get role credentials if configured to do so 237 | if (roleToAssume) { 238 | const roleCredentials = await assumeRole({ 239 | sourceAccountId, 240 | region, 241 | roleToAssume, 242 | roleExternalId, 243 | roleDurationSeconds, 244 | roleSessionName, 245 | roleSkipSessionTagging 246 | }); 247 | exportCredentials(roleCredentials); 248 | await validateCredentials(roleCredentials.accessKeyId); 249 | await exportAccountId(maskAccountId, region); 250 | } 251 | } 252 | catch (error) { 253 | core.setFailed(error.message); 254 | 255 | const showStackTrace = process.env.SHOW_STACK_TRACE; 256 | 257 | if (showStackTrace === 'true') { 258 | throw(error) 259 | } 260 | 261 | } 262 | } 263 | 264 | module.exports = run; 265 | 266 | /* istanbul ignore next */ 267 | if (require.main === module) { 268 | run(); 269 | } 270 | -------------------------------------------------------------------------------- /THIRD-PARTY: -------------------------------------------------------------------------------- 1 | ** AWS SDK for JavaScript; version 2.562.0 -- https://github.com/aws/aws-sdk-js 2 | Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Apache License 5 | 6 | Version 2.0, January 2004 7 | 8 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND 9 | DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, and 14 | distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by the 17 | copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all other 20 | entities that control, are controlled by, or are under common control 21 | with that entity. For the purposes of this definition, "control" means 22 | (i) the power, direct or indirect, to cause the direction or management 23 | of such entity, whether by contract or otherwise, or (ii) ownership of 24 | fifty percent (50%) or more of the outstanding shares, or (iii) 25 | beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity exercising 28 | permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation source, 32 | and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but not limited 36 | to compiled object code, generated documentation, and conversions to 37 | other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or Object 40 | form, made available under the License, as indicated by a copyright 41 | notice that is included in or attached to the work (an example is 42 | provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object form, 45 | that is based on (or derived from) the Work and for which the editorial 46 | revisions, annotations, elaborations, or other modifications represent, 47 | as a whole, an original work of authorship. For the purposes of this 48 | License, Derivative Works shall not include works that remain separable 49 | from, or merely link (or bind by name) to the interfaces of, the Work and 50 | Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including the original 53 | version of the Work and any modifications or additions to that Work or 54 | Derivative Works thereof, that is intentionally submitted to Licensor for 55 | inclusion in the Work by the copyright owner or by an individual or Legal 56 | Entity authorized to submit on behalf of the copyright owner. For the 57 | purposes of this definition, "submitted" means any form of electronic, 58 | verbal, or written communication sent to the Licensor or its 59 | representatives, including but not limited to communication on electronic 60 | mailing lists, source code control systems, and issue tracking systems 61 | that are managed by, or on behalf of, the Licensor for the purpose of 62 | discussing and improving the Work, but excluding communication that is 63 | conspicuously marked or otherwise designated in writing by the copyright 64 | owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity on 67 | behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of this 71 | License, each Contributor hereby grants to You a perpetual, worldwide, 72 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 73 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 74 | sublicense, and distribute the Work and such Derivative Works in Source or 75 | Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of this 78 | License, each Contributor hereby grants to You a perpetual, worldwide, 79 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 80 | this section) patent license to make, have made, use, offer to sell, sell, 81 | import, and otherwise transfer the Work, where such license applies only to 82 | those patent claims licensable by such Contributor that are necessarily 83 | infringed by their Contribution(s) alone or by combination of their 84 | Contribution(s) with the Work to which such Contribution(s) was submitted. 85 | If You institute patent litigation against any entity (including a 86 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 87 | Contribution incorporated within the Work constitutes direct or contributory 88 | patent infringement, then any patent licenses granted to You under this 89 | License for that Work shall terminate as of the date such litigation is 90 | filed. 91 | 92 | 4. Redistribution. You may reproduce and distribute copies of the Work or 93 | Derivative Works thereof in any medium, with or without modifications, and 94 | in Source or Object form, provided that You meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or Derivative Works a 97 | copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices stating 100 | that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works that You 103 | distribute, all copyright, patent, trademark, and attribution notices 104 | from the Source form of the Work, excluding those notices that do not 105 | pertain to any part of the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must include 109 | a readable copy of the attribution notices contained within such NOTICE 110 | file, excluding those notices that do not pertain to any part of the 111 | Derivative Works, in at least one of the following places: within a 112 | NOTICE text file distributed as part of the Derivative Works; within the 113 | Source form or documentation, if provided along with the Derivative 114 | Works; or, within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents of the 116 | NOTICE file are for informational purposes only and do not modify the 117 | License. You may add Your own attribution notices within Derivative Works 118 | that You distribute, alongside or as an addendum to the NOTICE text from 119 | the Work, provided that such additional attribution notices cannot be 120 | construed as modifying the License. 121 | 122 | You may add Your own copyright statement to Your modifications and may 123 | provide additional or different license terms and conditions for use, 124 | reproduction, or distribution of Your modifications, or for any such 125 | Derivative Works as a whole, provided Your use, reproduction, and 126 | distribution of the Work otherwise complies with the conditions stated in 127 | this License. 128 | 129 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 130 | Contribution intentionally submitted for inclusion in the Work by You to the 131 | Licensor shall be under the terms and conditions of this License, without 132 | any additional terms or conditions. Notwithstanding the above, nothing 133 | herein shall supersede or modify the terms of any separate license agreement 134 | you may have executed with Licensor regarding such Contributions. 135 | 136 | 6. Trademarks. This License does not grant permission to use the trade 137 | names, trademarks, service marks, or product names of the Licensor, except 138 | as required for reasonable and customary use in describing the origin of the 139 | Work and reproducing the content of the NOTICE file. 140 | 141 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 142 | writing, Licensor provides the Work (and each Contributor provides its 143 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 144 | KIND, either express or implied, including, without limitation, any 145 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 146 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 147 | the appropriateness of using or redistributing the Work and assume any risks 148 | associated with Your exercise of permissions under this License. 149 | 150 | 8. Limitation of Liability. In no event and under no legal theory, whether 151 | in tort (including negligence), contract, or otherwise, unless required by 152 | applicable law (such as deliberate and grossly negligent acts) or agreed to 153 | in writing, shall any Contributor be liable to You for damages, including 154 | any direct, indirect, special, incidental, or consequential damages of any 155 | character arising as a result of this License or out of the use or inability 156 | to use the Work (including but not limited to damages for loss of goodwill, 157 | work stoppage, computer failure or malfunction, or any and all other 158 | commercial damages or losses), even if such Contributor has been advised of 159 | the possibility of such damages. 160 | 161 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 162 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 163 | acceptance of support, warranty, indemnity, or other liability obligations 164 | and/or rights consistent with this License. However, in accepting such 165 | obligations, You may act only on Your own behalf and on Your sole 166 | responsibility, not on behalf of any other Contributor, and only if You 167 | agree to indemnify, defend, and hold each Contributor harmless for any 168 | liability incurred by, or claims asserted against, such Contributor by 169 | reason of your accepting any such warranty or additional liability. END OF 170 | TERMS AND CONDITIONS 171 | 172 | APPENDIX: How to apply the Apache License to your work. 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets "[]" replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same "printed page" as the copyright notice for easier identification 180 | within third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | 186 | you may not use this file except in compliance with the License. 187 | 188 | You may obtain a copy of the License at 189 | 190 | http://www.apache.org/licenses/LICENSE-2.0 191 | 192 | Unless required by applicable law or agreed to in writing, software 193 | 194 | distributed under the License is distributed on an "AS IS" BASIS, 195 | 196 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 197 | 198 | See the License for the specific language governing permissions and 199 | 200 | limitations under the License. 201 | 202 | * For AWS SDK for JavaScript see also this required NOTICE: 203 | Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights 204 | Reserved. 205 | 206 | ------ 207 | 208 | ** GitHub Actions Toolkit; version 1.2.0 -- https://github.com/actions/toolkit 209 | Copyright 2019 GitHub 210 | 211 | MIT License 212 | 213 | Copyright (c) 214 | 215 | Permission is hereby granted, free of charge, to any person obtaining a copy of 216 | this software and associated documentation files (the "Software"), to deal in 217 | the Software without restriction, including without limitation the rights to 218 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 219 | of the Software, and to permit persons to whom the Software is furnished to do 220 | so, subject to the following conditions: 221 | 222 | The above copyright notice and this permission notice shall be included in all 223 | copies or substantial portions of the Software. 224 | 225 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 226 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 227 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 228 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 229 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 230 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 231 | SOFTWARE. 232 | -------------------------------------------------------------------------------- /dist/cleanup/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules, runtime) { // webpackBootstrap 3 | /******/ "use strict"; 4 | /******/ // The module cache 5 | /******/ var installedModules = {}; 6 | /******/ 7 | /******/ // The require function 8 | /******/ function __webpack_require__(moduleId) { 9 | /******/ 10 | /******/ // Check if module is in cache 11 | /******/ if(installedModules[moduleId]) { 12 | /******/ return installedModules[moduleId].exports; 13 | /******/ } 14 | /******/ // Create a new module (and put it into the cache) 15 | /******/ var module = installedModules[moduleId] = { 16 | /******/ i: moduleId, 17 | /******/ l: false, 18 | /******/ exports: {} 19 | /******/ }; 20 | /******/ 21 | /******/ // Execute the module function 22 | /******/ var threw = true; 23 | /******/ try { 24 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 25 | /******/ threw = false; 26 | /******/ } finally { 27 | /******/ if(threw) delete installedModules[moduleId]; 28 | /******/ } 29 | /******/ 30 | /******/ // Flag the module as loaded 31 | /******/ module.l = true; 32 | /******/ 33 | /******/ // Return the exports of the module 34 | /******/ return module.exports; 35 | /******/ } 36 | /******/ 37 | /******/ 38 | /******/ __webpack_require__.ab = __dirname + "/"; 39 | /******/ 40 | /******/ // the startup function 41 | /******/ function startup() { 42 | /******/ // Load entry module and return exports 43 | /******/ return __webpack_require__(175); 44 | /******/ }; 45 | /******/ 46 | /******/ // run startup 47 | /******/ return startup(); 48 | /******/ }) 49 | /************************************************************************/ 50 | /******/ ({ 51 | 52 | /***/ 82: 53 | /***/ (function(__unusedmodule, exports) { 54 | 55 | "use strict"; 56 | 57 | // We use any as a valid input type 58 | /* eslint-disable @typescript-eslint/no-explicit-any */ 59 | Object.defineProperty(exports, "__esModule", { value: true }); 60 | /** 61 | * Sanitizes an input into a string so it can be passed into issueCommand safely 62 | * @param input input to sanitize into a string 63 | */ 64 | function toCommandValue(input) { 65 | if (input === null || input === undefined) { 66 | return ''; 67 | } 68 | else if (typeof input === 'string' || input instanceof String) { 69 | return input; 70 | } 71 | return JSON.stringify(input); 72 | } 73 | exports.toCommandValue = toCommandValue; 74 | //# sourceMappingURL=utils.js.map 75 | 76 | /***/ }), 77 | 78 | /***/ 87: 79 | /***/ (function(module) { 80 | 81 | module.exports = require("os"); 82 | 83 | /***/ }), 84 | 85 | /***/ 102: 86 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 87 | 88 | "use strict"; 89 | 90 | // For internal use, subject to change. 91 | var __importStar = (this && this.__importStar) || function (mod) { 92 | if (mod && mod.__esModule) return mod; 93 | var result = {}; 94 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 95 | result["default"] = mod; 96 | return result; 97 | }; 98 | Object.defineProperty(exports, "__esModule", { value: true }); 99 | // We use any as a valid input type 100 | /* eslint-disable @typescript-eslint/no-explicit-any */ 101 | const fs = __importStar(__webpack_require__(747)); 102 | const os = __importStar(__webpack_require__(87)); 103 | const utils_1 = __webpack_require__(82); 104 | function issueCommand(command, message) { 105 | const filePath = process.env[`GITHUB_${command}`]; 106 | if (!filePath) { 107 | throw new Error(`Unable to find environment variable for file command ${command}`); 108 | } 109 | if (!fs.existsSync(filePath)) { 110 | throw new Error(`Missing file at path: ${filePath}`); 111 | } 112 | fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { 113 | encoding: 'utf8' 114 | }); 115 | } 116 | exports.issueCommand = issueCommand; 117 | //# sourceMappingURL=file-command.js.map 118 | 119 | /***/ }), 120 | 121 | /***/ 175: 122 | /***/ (function(module, __unusedexports, __webpack_require__) { 123 | 124 | const core = __webpack_require__(470); 125 | 126 | /** 127 | * When the GitHub Actions job is done, clean up any environment variables that 128 | * may have been set by the configure-aws-credentials steps in the job. 129 | * 130 | * Environment variables are not intended to be shared across different jobs in 131 | * the same GitHub Actions workflow: GitHub Actions documentation states that 132 | * each job runs in a fresh instance. However, doing our own cleanup will 133 | * give us additional assurance that these environment variables are not shared 134 | * with any other jobs. 135 | */ 136 | 137 | async function cleanup() { 138 | try { 139 | // The GitHub Actions toolkit does not have an option to completely unset 140 | // environment variables, so we overwrite the current value with an empty 141 | // string. The AWS CLI and AWS SDKs will behave correctly: they treat an 142 | // empty string value as if the environment variable does not exist. 143 | core.exportVariable('AWS_ACCESS_KEY_ID', ''); 144 | core.exportVariable('AWS_SECRET_ACCESS_KEY', ''); 145 | core.exportVariable('AWS_SESSION_TOKEN', ''); 146 | core.exportVariable('AWS_DEFAULT_REGION', ''); 147 | core.exportVariable('AWS_REGION', ''); 148 | } 149 | catch (error) { 150 | core.setFailed(error.message); 151 | } 152 | } 153 | 154 | module.exports = cleanup; 155 | 156 | /* istanbul ignore next */ 157 | if (require.main === require.cache[eval('__filename')]) { 158 | cleanup(); 159 | } 160 | 161 | 162 | /***/ }), 163 | 164 | /***/ 431: 165 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 166 | 167 | "use strict"; 168 | 169 | var __importStar = (this && this.__importStar) || function (mod) { 170 | if (mod && mod.__esModule) return mod; 171 | var result = {}; 172 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 173 | result["default"] = mod; 174 | return result; 175 | }; 176 | Object.defineProperty(exports, "__esModule", { value: true }); 177 | const os = __importStar(__webpack_require__(87)); 178 | const utils_1 = __webpack_require__(82); 179 | /** 180 | * Commands 181 | * 182 | * Command Format: 183 | * ::name key=value,key=value::message 184 | * 185 | * Examples: 186 | * ::warning::This is the message 187 | * ::set-env name=MY_VAR::some value 188 | */ 189 | function issueCommand(command, properties, message) { 190 | const cmd = new Command(command, properties, message); 191 | process.stdout.write(cmd.toString() + os.EOL); 192 | } 193 | exports.issueCommand = issueCommand; 194 | function issue(name, message = '') { 195 | issueCommand(name, {}, message); 196 | } 197 | exports.issue = issue; 198 | const CMD_STRING = '::'; 199 | class Command { 200 | constructor(command, properties, message) { 201 | if (!command) { 202 | command = 'missing.command'; 203 | } 204 | this.command = command; 205 | this.properties = properties; 206 | this.message = message; 207 | } 208 | toString() { 209 | let cmdStr = CMD_STRING + this.command; 210 | if (this.properties && Object.keys(this.properties).length > 0) { 211 | cmdStr += ' '; 212 | let first = true; 213 | for (const key in this.properties) { 214 | if (this.properties.hasOwnProperty(key)) { 215 | const val = this.properties[key]; 216 | if (val) { 217 | if (first) { 218 | first = false; 219 | } 220 | else { 221 | cmdStr += ','; 222 | } 223 | cmdStr += `${key}=${escapeProperty(val)}`; 224 | } 225 | } 226 | } 227 | } 228 | cmdStr += `${CMD_STRING}${escapeData(this.message)}`; 229 | return cmdStr; 230 | } 231 | } 232 | function escapeData(s) { 233 | return utils_1.toCommandValue(s) 234 | .replace(/%/g, '%25') 235 | .replace(/\r/g, '%0D') 236 | .replace(/\n/g, '%0A'); 237 | } 238 | function escapeProperty(s) { 239 | return utils_1.toCommandValue(s) 240 | .replace(/%/g, '%25') 241 | .replace(/\r/g, '%0D') 242 | .replace(/\n/g, '%0A') 243 | .replace(/:/g, '%3A') 244 | .replace(/,/g, '%2C'); 245 | } 246 | //# sourceMappingURL=command.js.map 247 | 248 | /***/ }), 249 | 250 | /***/ 470: 251 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 252 | 253 | "use strict"; 254 | 255 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 256 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 257 | return new (P || (P = Promise))(function (resolve, reject) { 258 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 259 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 260 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 261 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 262 | }); 263 | }; 264 | var __importStar = (this && this.__importStar) || function (mod) { 265 | if (mod && mod.__esModule) return mod; 266 | var result = {}; 267 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 268 | result["default"] = mod; 269 | return result; 270 | }; 271 | Object.defineProperty(exports, "__esModule", { value: true }); 272 | const command_1 = __webpack_require__(431); 273 | const file_command_1 = __webpack_require__(102); 274 | const utils_1 = __webpack_require__(82); 275 | const os = __importStar(__webpack_require__(87)); 276 | const path = __importStar(__webpack_require__(622)); 277 | /** 278 | * The code to exit an action 279 | */ 280 | var ExitCode; 281 | (function (ExitCode) { 282 | /** 283 | * A code indicating that the action was successful 284 | */ 285 | ExitCode[ExitCode["Success"] = 0] = "Success"; 286 | /** 287 | * A code indicating that the action was a failure 288 | */ 289 | ExitCode[ExitCode["Failure"] = 1] = "Failure"; 290 | })(ExitCode = exports.ExitCode || (exports.ExitCode = {})); 291 | //----------------------------------------------------------------------- 292 | // Variables 293 | //----------------------------------------------------------------------- 294 | /** 295 | * Sets env variable for this action and future actions in the job 296 | * @param name the name of the variable to set 297 | * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify 298 | */ 299 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 300 | function exportVariable(name, val) { 301 | const convertedVal = utils_1.toCommandValue(val); 302 | process.env[name] = convertedVal; 303 | const filePath = process.env['GITHUB_ENV'] || ''; 304 | if (filePath) { 305 | const delimiter = '_GitHubActionsFileCommandDelimeter_'; 306 | const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; 307 | file_command_1.issueCommand('ENV', commandValue); 308 | } 309 | else { 310 | command_1.issueCommand('set-env', { name }, convertedVal); 311 | } 312 | } 313 | exports.exportVariable = exportVariable; 314 | /** 315 | * Registers a secret which will get masked from logs 316 | * @param secret value of the secret 317 | */ 318 | function setSecret(secret) { 319 | command_1.issueCommand('add-mask', {}, secret); 320 | } 321 | exports.setSecret = setSecret; 322 | /** 323 | * Prepends inputPath to the PATH (for this action and future actions) 324 | * @param inputPath 325 | */ 326 | function addPath(inputPath) { 327 | const filePath = process.env['GITHUB_PATH'] || ''; 328 | if (filePath) { 329 | file_command_1.issueCommand('PATH', inputPath); 330 | } 331 | else { 332 | command_1.issueCommand('add-path', {}, inputPath); 333 | } 334 | process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; 335 | } 336 | exports.addPath = addPath; 337 | /** 338 | * Gets the value of an input. The value is also trimmed. 339 | * 340 | * @param name name of the input to get 341 | * @param options optional. See InputOptions. 342 | * @returns string 343 | */ 344 | function getInput(name, options) { 345 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; 346 | if (options && options.required && !val) { 347 | throw new Error(`Input required and not supplied: ${name}`); 348 | } 349 | return val.trim(); 350 | } 351 | exports.getInput = getInput; 352 | /** 353 | * Sets the value of an output. 354 | * 355 | * @param name name of the output to set 356 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 357 | */ 358 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 359 | function setOutput(name, value) { 360 | command_1.issueCommand('set-output', { name }, value); 361 | } 362 | exports.setOutput = setOutput; 363 | /** 364 | * Enables or disables the echoing of commands into stdout for the rest of the step. 365 | * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. 366 | * 367 | */ 368 | function setCommandEcho(enabled) { 369 | command_1.issue('echo', enabled ? 'on' : 'off'); 370 | } 371 | exports.setCommandEcho = setCommandEcho; 372 | //----------------------------------------------------------------------- 373 | // Results 374 | //----------------------------------------------------------------------- 375 | /** 376 | * Sets the action status to failed. 377 | * When the action exits it will be with an exit code of 1 378 | * @param message add error issue message 379 | */ 380 | function setFailed(message) { 381 | process.exitCode = ExitCode.Failure; 382 | error(message); 383 | } 384 | exports.setFailed = setFailed; 385 | //----------------------------------------------------------------------- 386 | // Logging Commands 387 | //----------------------------------------------------------------------- 388 | /** 389 | * Gets whether Actions Step Debug is on or not 390 | */ 391 | function isDebug() { 392 | return process.env['RUNNER_DEBUG'] === '1'; 393 | } 394 | exports.isDebug = isDebug; 395 | /** 396 | * Writes debug message to user log 397 | * @param message debug message 398 | */ 399 | function debug(message) { 400 | command_1.issueCommand('debug', {}, message); 401 | } 402 | exports.debug = debug; 403 | /** 404 | * Adds an error issue 405 | * @param message error issue message. Errors will be converted to string via toString() 406 | */ 407 | function error(message) { 408 | command_1.issue('error', message instanceof Error ? message.toString() : message); 409 | } 410 | exports.error = error; 411 | /** 412 | * Adds an warning issue 413 | * @param message warning issue message. Errors will be converted to string via toString() 414 | */ 415 | function warning(message) { 416 | command_1.issue('warning', message instanceof Error ? message.toString() : message); 417 | } 418 | exports.warning = warning; 419 | /** 420 | * Writes info to log with console.log. 421 | * @param message info message 422 | */ 423 | function info(message) { 424 | process.stdout.write(message + os.EOL); 425 | } 426 | exports.info = info; 427 | /** 428 | * Begin an output group. 429 | * 430 | * Output until the next `groupEnd` will be foldable in this group 431 | * 432 | * @param name The name of the output group 433 | */ 434 | function startGroup(name) { 435 | command_1.issue('group', name); 436 | } 437 | exports.startGroup = startGroup; 438 | /** 439 | * End an output group. 440 | */ 441 | function endGroup() { 442 | command_1.issue('endgroup'); 443 | } 444 | exports.endGroup = endGroup; 445 | /** 446 | * Wrap an asynchronous function call in a group. 447 | * 448 | * Returns the same type as the function itself. 449 | * 450 | * @param name The name of the group 451 | * @param fn The function to wrap in the group 452 | */ 453 | function group(name, fn) { 454 | return __awaiter(this, void 0, void 0, function* () { 455 | startGroup(name); 456 | let result; 457 | try { 458 | result = yield fn(); 459 | } 460 | finally { 461 | endGroup(); 462 | } 463 | return result; 464 | }); 465 | } 466 | exports.group = group; 467 | //----------------------------------------------------------------------- 468 | // Wrapper action state 469 | //----------------------------------------------------------------------- 470 | /** 471 | * Saves state for current action, the state can only be retrieved by this action's post job execution. 472 | * 473 | * @param name name of the state to store 474 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 475 | */ 476 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 477 | function saveState(name, value) { 478 | command_1.issueCommand('save-state', { name }, value); 479 | } 480 | exports.saveState = saveState; 481 | /** 482 | * Gets the value of an state set by this action's main execution. 483 | * 484 | * @param name name of the state to get 485 | * @returns string 486 | */ 487 | function getState(name) { 488 | return process.env[`STATE_${name}`] || ''; 489 | } 490 | exports.getState = getState; 491 | //# sourceMappingURL=core.js.map 492 | 493 | /***/ }), 494 | 495 | /***/ 622: 496 | /***/ (function(module) { 497 | 498 | module.exports = require("path"); 499 | 500 | /***/ }), 501 | 502 | /***/ 747: 503 | /***/ (function(module) { 504 | 505 | module.exports = require("fs"); 506 | 507 | /***/ }) 508 | 509 | /******/ }); -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const assert = require('assert'); 3 | const aws = require('aws-sdk'); 4 | const run = require('./index.js'); 5 | 6 | jest.mock('@actions/core'); 7 | 8 | const FAKE_ACCESS_KEY_ID = 'MY-AWS-ACCESS-KEY-ID'; 9 | const FAKE_SECRET_ACCESS_KEY = 'MY-AWS-SECRET-ACCESS-KEY'; 10 | const FAKE_SESSION_TOKEN = 'MY-AWS-SESSION-TOKEN'; 11 | const FAKE_STS_ACCESS_KEY_ID = 'STS-AWS-ACCESS-KEY-ID'; 12 | const FAKE_STS_SECRET_ACCESS_KEY = 'STS-AWS-SECRET-ACCESS-KEY'; 13 | const FAKE_STS_SESSION_TOKEN = 'STS-AWS-SESSION-TOKEN'; 14 | const FAKE_REGION = 'fake-region-1'; 15 | const FAKE_ACCOUNT_ID = '123456789012'; 16 | const FAKE_ROLE_ACCOUNT_ID = '111111111111'; 17 | const ROLE_NAME = 'MY-ROLE'; 18 | const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE'; 19 | const ENVIRONMENT_VARIABLE_OVERRIDES = { 20 | SHOW_STACK_TRACE: 'true', 21 | GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME', 22 | GITHUB_WORKFLOW: 'MY-WORKFLOW-ID', 23 | GITHUB_ACTION: 'MY-ACTION-NAME', 24 | GITHUB_ACTOR: 'MY-USERNAME[bot]', 25 | GITHUB_SHA: 'MY-COMMIT-ID', 26 | GITHUB_REF: 'MY-BRANCH', 27 | }; 28 | const GITHUB_ACTOR_SANITIZED = 'MY-USERNAME_bot_' 29 | 30 | function mockGetInput(requestResponse) { 31 | return function (name, options) { // eslint-disable-line no-unused-vars 32 | return requestResponse[name] 33 | } 34 | } 35 | const CREDS_INPUTS = { 36 | 'aws-access-key-id': FAKE_ACCESS_KEY_ID, 37 | 'aws-secret-access-key': FAKE_SECRET_ACCESS_KEY 38 | }; 39 | const DEFAULT_INPUTS = { 40 | ...CREDS_INPUTS, 41 | 'aws-session-token': FAKE_SESSION_TOKEN, 42 | 'aws-region': FAKE_REGION, 43 | 'mask-aws-account-id': 'TRUE' 44 | }; 45 | const ASSUME_ROLE_INPUTS = {...CREDS_INPUTS, 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION}; 46 | 47 | const mockStsCallerIdentity = jest.fn(); 48 | const mockStsAssumeRole = jest.fn(); 49 | 50 | jest.mock('aws-sdk', () => { 51 | return { 52 | config: { 53 | getCredentials: jest.fn() 54 | }, 55 | STS: jest.fn(() => ({ 56 | getCallerIdentity: mockStsCallerIdentity, 57 | assumeRole: mockStsAssumeRole, 58 | })) 59 | }; 60 | }); 61 | 62 | describe('Configure AWS Credentials', () => { 63 | const OLD_ENV = process.env; 64 | 65 | beforeEach(() => { 66 | jest.resetModules(); 67 | process.env = {...OLD_ENV, ...ENVIRONMENT_VARIABLE_OVERRIDES}; 68 | 69 | jest.clearAllMocks(); 70 | 71 | core.getInput = jest 72 | .fn() 73 | .mockImplementation(mockGetInput(DEFAULT_INPUTS)); 74 | 75 | mockStsCallerIdentity.mockReset(); 76 | mockStsCallerIdentity 77 | .mockReturnValueOnce({ 78 | promise() { 79 | return Promise.resolve({ Account: FAKE_ACCOUNT_ID }); 80 | } 81 | }) 82 | .mockReturnValueOnce({ 83 | promise() { 84 | return Promise.resolve({ Account: FAKE_ROLE_ACCOUNT_ID }); 85 | } 86 | }); 87 | 88 | aws.config.getCredentials.mockReset(); 89 | aws.config.getCredentials 90 | .mockImplementationOnce(callback => { 91 | if (!aws.config.credentials) { 92 | aws.config.credentials = { 93 | accessKeyId: FAKE_ACCESS_KEY_ID, 94 | secretAccessKey: FAKE_SECRET_ACCESS_KEY 95 | } 96 | } 97 | callback(null); 98 | }) 99 | .mockImplementationOnce(callback => { 100 | if (!aws.config.credentials) { 101 | aws.config.credentials = { 102 | accessKeyId: FAKE_STS_ACCESS_KEY_ID, 103 | secretAccessKey: FAKE_STS_SECRET_ACCESS_KEY 104 | } 105 | } 106 | callback(null); 107 | }); 108 | 109 | mockStsAssumeRole.mockImplementation(() => { 110 | return { 111 | promise() { 112 | return Promise.resolve({ 113 | Credentials: { 114 | AccessKeyId: FAKE_STS_ACCESS_KEY_ID, 115 | SecretAccessKey: FAKE_STS_SECRET_ACCESS_KEY, 116 | SessionToken: FAKE_STS_SESSION_TOKEN 117 | } 118 | }); 119 | } 120 | } 121 | }); 122 | }); 123 | 124 | afterEach(() => { 125 | process.env = OLD_ENV; 126 | }); 127 | 128 | test('exports env vars', async () => { 129 | await run(); 130 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(0); 131 | expect(core.exportVariable).toHaveBeenCalledTimes(5); 132 | expect(core.setSecret).toHaveBeenCalledTimes(4); 133 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); 134 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID); 135 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); 136 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY); 137 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', FAKE_SESSION_TOKEN); 138 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_SESSION_TOKEN); 139 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION); 140 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION); 141 | expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); 142 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID); 143 | }); 144 | 145 | test('action fails when github env vars are not set', async () => { 146 | process.env.SHOW_STACK_TRACE = 'false'; 147 | core.getInput = jest 148 | .fn() 149 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 150 | delete process.env.GITHUB_SHA; 151 | 152 | await run(); 153 | expect(core.setFailed).toHaveBeenCalledWith('Missing required environment value. Are you running in GitHub Actions?'); 154 | }); 155 | 156 | test('action does not require GITHUB_REF env var', async () => { 157 | core.getInput = jest 158 | .fn() 159 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 160 | delete process.env.GITHUB_REF; 161 | 162 | await run(); 163 | }); 164 | 165 | test('hosted runners can pull creds from a self-hosted environment', async () => { 166 | const mockInputs = {'aws-region': FAKE_REGION}; 167 | core.getInput = jest 168 | .fn() 169 | .mockImplementation(mockGetInput(mockInputs)); 170 | 171 | await run(); 172 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(0); 173 | expect(core.exportVariable).toHaveBeenCalledTimes(2); 174 | expect(core.setSecret).toHaveBeenCalledTimes(1); 175 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION); 176 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION); 177 | expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); 178 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID); 179 | }); 180 | 181 | test('action with no accessible credentials fails', async () => { 182 | process.env.SHOW_STACK_TRACE = 'false'; 183 | const mockInputs = {'aws-region': FAKE_REGION}; 184 | core.getInput = jest 185 | .fn() 186 | .mockImplementation(mockGetInput(mockInputs)); 187 | aws.config.getCredentials.mockReset(); 188 | aws.config.getCredentials.mockImplementation(callback => { 189 | callback(new Error('No credentials to load')); 190 | }); 191 | 192 | await run(); 193 | 194 | expect(core.setFailed).toHaveBeenCalledWith("Credentials could not be loaded, please check your action inputs: No credentials to load"); 195 | }); 196 | 197 | test('action with empty credentials fails', async () => { 198 | process.env.SHOW_STACK_TRACE = 'false'; 199 | const mockInputs = {'aws-region': FAKE_REGION}; 200 | core.getInput = jest 201 | .fn() 202 | .mockImplementation(mockGetInput(mockInputs)); 203 | aws.config.getCredentials.mockReset(); 204 | aws.config.getCredentials.mockImplementation(callback => { 205 | aws.config.credentials = { 206 | accessKeyId: '' 207 | } 208 | callback(null); 209 | }); 210 | 211 | await run(); 212 | 213 | expect(core.setFailed).toHaveBeenCalledWith("Credentials could not be loaded, please check your action inputs: Access key ID empty after loading credentials"); 214 | }); 215 | 216 | test('action fails when credentials are not set in the SDK correctly', async () => { 217 | process.env.SHOW_STACK_TRACE = 'false'; 218 | core.getInput = jest 219 | .fn() 220 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 221 | aws.config.getCredentials.mockReset(); 222 | aws.config.getCredentials.mockImplementation(callback => { 223 | aws.config.credentials = { 224 | accessKeyId: FAKE_ACCESS_KEY_ID 225 | } 226 | callback(null); 227 | }); 228 | 229 | await run(); 230 | 231 | expect(core.setFailed).toHaveBeenCalledWith("Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action"); 232 | }); 233 | 234 | test('session token is optional', async () => { 235 | const mockInputs = {...CREDS_INPUTS, 'aws-region': 'eu-west-1'}; 236 | core.getInput = jest 237 | .fn() 238 | .mockImplementation(mockGetInput(mockInputs)); 239 | 240 | await run(); 241 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(0); 242 | expect(core.exportVariable).toHaveBeenCalledTimes(4); 243 | expect(core.setSecret).toHaveBeenCalledTimes(3); 244 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); 245 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID); 246 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); 247 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY); 248 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1'); 249 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1'); 250 | expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); 251 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID); 252 | }); 253 | 254 | test('existing env var creds are cleared', async () => { 255 | const mockInputs = {...CREDS_INPUTS, 'aws-region': 'eu-west-1'}; 256 | core.getInput = jest 257 | .fn() 258 | .mockImplementation(mockGetInput(mockInputs)); 259 | process.env.AWS_ACCESS_KEY_ID = 'foo'; 260 | process.env.AWS_SECRET_ACCESS_KEY = 'bar'; 261 | process.env.AWS_SESSION_TOKEN = 'helloworld'; 262 | aws.config.credentials = { 263 | accessKeyId: 'foo', 264 | secretAccessKey: 'bar', 265 | sessionToken: 'helloworld' 266 | }; 267 | 268 | await run(); 269 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(0); 270 | expect(core.exportVariable).toHaveBeenCalledTimes(5); 271 | expect(core.setSecret).toHaveBeenCalledTimes(3); 272 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); 273 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID); 274 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); 275 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY); 276 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', ''); 277 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1'); 278 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1'); 279 | expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); 280 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID); 281 | expect(aws.config.credentials.accessKeyId).toBe(FAKE_ACCESS_KEY_ID); 282 | expect(aws.config.credentials.secretAccessKey).toBe(FAKE_SECRET_ACCESS_KEY); 283 | expect(aws.config.credentials.sessionToken).toBeUndefined(); 284 | }); 285 | 286 | test('validates region name', async () => { 287 | process.env.SHOW_STACK_TRACE = 'false'; 288 | 289 | const mockInputs = {...CREDS_INPUTS, 'aws-region': '$AWS_REGION'}; 290 | core.getInput = jest 291 | .fn() 292 | .mockImplementation(mockGetInput(mockInputs)); 293 | 294 | await run(); 295 | 296 | expect(core.setFailed).toHaveBeenCalledWith('Region is not valid: $AWS_REGION'); 297 | }); 298 | 299 | test('throws error if access key id exists but missing secret access key', async () => { 300 | process.env.SHOW_STACK_TRACE = 'false'; 301 | const inputsWIthoutSecretKey = {...ASSUME_ROLE_INPUTS} 302 | inputsWIthoutSecretKey["aws-secret-access-key"] = undefined 303 | core.getInput = jest 304 | .fn() 305 | .mockImplementation(mockGetInput(inputsWIthoutSecretKey)); 306 | 307 | await run(); 308 | expect(core.setFailed).toHaveBeenCalledWith("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided"); 309 | 310 | }); 311 | 312 | test('can opt out of masking account ID', async () => { 313 | const mockInputs = {...CREDS_INPUTS, 'aws-region': 'us-east-1', 'mask-aws-account-id': 'false'}; 314 | core.getInput = jest 315 | .fn() 316 | .mockImplementation(mockGetInput(mockInputs)); 317 | 318 | await run(); 319 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(0); 320 | expect(core.exportVariable).toHaveBeenCalledTimes(4); 321 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); 322 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID); 323 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); 324 | expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY); 325 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'us-east-1'); 326 | expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'us-east-1'); 327 | expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); 328 | expect(core.setSecret).toHaveBeenCalledTimes(2); 329 | }); 330 | 331 | test('error is caught by core.setFailed and caught', async () => { 332 | process.env.SHOW_STACK_TRACE = 'false'; 333 | 334 | mockStsCallerIdentity.mockReset(); 335 | mockStsCallerIdentity.mockImplementation(() => { 336 | throw new Error(); 337 | }); 338 | 339 | await run(); 340 | 341 | expect(core.setFailed).toBeCalled(); 342 | }); 343 | 344 | test('error is caught by core.setFailed and passed', async () => { 345 | 346 | mockStsCallerIdentity.mockReset(); 347 | mockStsCallerIdentity.mockImplementation(() => { 348 | throw new Error(); 349 | }); 350 | 351 | await assert.rejects(() => run()); 352 | 353 | expect(core.setFailed).toBeCalled(); 354 | }); 355 | 356 | test('basic role assumption exports', async () => { 357 | core.getInput = jest 358 | .fn() 359 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 360 | 361 | await run(); 362 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(1); 363 | expect(core.exportVariable).toHaveBeenCalledTimes(7); 364 | expect(core.setSecret).toHaveBeenCalledTimes(7); 365 | expect(core.setOutput).toHaveBeenCalledTimes(2); 366 | 367 | // first the source credentials are exported and masked 368 | expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCESS_KEY_ID); 369 | expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_SECRET_ACCESS_KEY); 370 | expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_ACCOUNT_ID); 371 | 372 | expect(core.exportVariable).toHaveBeenNthCalledWith(1, 'AWS_DEFAULT_REGION', FAKE_REGION); 373 | expect(core.exportVariable).toHaveBeenNthCalledWith(2, 'AWS_REGION', FAKE_REGION); 374 | expect(core.exportVariable).toHaveBeenNthCalledWith(3, 'AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); 375 | expect(core.exportVariable).toHaveBeenNthCalledWith(4, 'AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); 376 | 377 | expect(core.setOutput).toHaveBeenNthCalledWith(1, 'aws-account-id', FAKE_ACCOUNT_ID); 378 | 379 | // then the role credentials are exported and masked 380 | expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_ACCESS_KEY_ID); 381 | expect(core.setSecret).toHaveBeenNthCalledWith(5, FAKE_STS_SECRET_ACCESS_KEY); 382 | expect(core.setSecret).toHaveBeenNthCalledWith(6, FAKE_STS_SESSION_TOKEN); 383 | expect(core.setSecret).toHaveBeenNthCalledWith(7, FAKE_ROLE_ACCOUNT_ID); 384 | 385 | expect(core.exportVariable).toHaveBeenNthCalledWith(5, 'AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID); 386 | expect(core.exportVariable).toHaveBeenNthCalledWith(6, 'AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY); 387 | expect(core.exportVariable).toHaveBeenNthCalledWith(7, 'AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN); 388 | 389 | expect(core.setOutput).toHaveBeenNthCalledWith(2, 'aws-account-id', FAKE_ROLE_ACCOUNT_ID); 390 | }); 391 | 392 | test('assume role can pull source credentials from self-hosted environment', async () => { 393 | core.getInput = jest 394 | .fn() 395 | .mockImplementation(mockGetInput({'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION})); 396 | 397 | await run(); 398 | expect(mockStsAssumeRole).toHaveBeenCalledTimes(1); 399 | expect(core.exportVariable).toHaveBeenCalledTimes(5); 400 | expect(core.setSecret).toHaveBeenCalledTimes(5); 401 | expect(core.setOutput).toHaveBeenCalledTimes(2); 402 | 403 | // first the source account is exported and masked 404 | expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCOUNT_ID); 405 | expect(core.exportVariable).toHaveBeenNthCalledWith(1, 'AWS_DEFAULT_REGION', FAKE_REGION); 406 | expect(core.exportVariable).toHaveBeenNthCalledWith(2, 'AWS_REGION', FAKE_REGION); 407 | expect(core.setOutput).toHaveBeenNthCalledWith(1, 'aws-account-id', FAKE_ACCOUNT_ID); 408 | 409 | // then the role credentials are exported and masked 410 | expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_ACCESS_KEY_ID); 411 | expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SECRET_ACCESS_KEY); 412 | expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_SESSION_TOKEN); 413 | expect(core.setSecret).toHaveBeenNthCalledWith(5, FAKE_ROLE_ACCOUNT_ID); 414 | 415 | expect(core.exportVariable).toHaveBeenNthCalledWith(3, 'AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID); 416 | expect(core.exportVariable).toHaveBeenNthCalledWith(4, 'AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY); 417 | expect(core.exportVariable).toHaveBeenNthCalledWith(5, 'AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN); 418 | 419 | expect(core.setOutput).toHaveBeenNthCalledWith(2, 'aws-account-id', FAKE_ROLE_ACCOUNT_ID); 420 | }); 421 | 422 | test('role assumption tags', async () => { 423 | core.getInput = jest 424 | .fn() 425 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 426 | 427 | await run(); 428 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 429 | RoleArn: ROLE_ARN, 430 | RoleSessionName: 'GitHubActions', 431 | DurationSeconds: 6 * 3600, 432 | Tags: [ 433 | {Key: 'GitHub', Value: 'Actions'}, 434 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 435 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 436 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 437 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 438 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 439 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 440 | ] 441 | }) 442 | }); 443 | 444 | test('role assumption duration provided', async () => { 445 | core.getInput = jest 446 | .fn() 447 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-duration-seconds': 5})); 448 | 449 | await run(); 450 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 451 | RoleArn: ROLE_ARN, 452 | RoleSessionName: 'GitHubActions', 453 | DurationSeconds: 5, 454 | Tags: [ 455 | {Key: 'GitHub', Value: 'Actions'}, 456 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 457 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 458 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 459 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 460 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 461 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 462 | ] 463 | }) 464 | }); 465 | 466 | test('role assumption session name provided', async () => { 467 | core.getInput = jest 468 | .fn() 469 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-session-name': 'MySessionName'})); 470 | 471 | await run(); 472 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 473 | RoleArn: ROLE_ARN, 474 | RoleSessionName: 'MySessionName', 475 | DurationSeconds: 6 * 3600, 476 | Tags: [ 477 | {Key: 'GitHub', Value: 'Actions'}, 478 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 479 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 480 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 481 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 482 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 483 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 484 | ] 485 | }) 486 | }); 487 | 488 | test('role name provided instead of ARN', async () => { 489 | core.getInput = jest 490 | .fn() 491 | .mockImplementation(mockGetInput({...CREDS_INPUTS, 'role-to-assume': ROLE_NAME, 'aws-region': FAKE_REGION})); 492 | 493 | await run(); 494 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 495 | RoleArn: 'arn:aws:iam::123456789012:role/MY-ROLE', 496 | RoleSessionName: 'GitHubActions', 497 | DurationSeconds: 6 * 3600, 498 | Tags: [ 499 | {Key: 'GitHub', Value: 'Actions'}, 500 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 501 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 502 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 503 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 504 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 505 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 506 | ] 507 | }) 508 | }); 509 | 510 | test('role external ID provided', async () => { 511 | core.getInput = jest 512 | .fn() 513 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-external-id': 'abcdef'})); 514 | 515 | await run(); 516 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 517 | RoleArn: ROLE_ARN, 518 | RoleSessionName: 'GitHubActions', 519 | DurationSeconds: 6 * 3600, 520 | Tags: [ 521 | {Key: 'GitHub', Value: 'Actions'}, 522 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 523 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 524 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 525 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 526 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 527 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 528 | ], 529 | ExternalId: 'abcdef' 530 | }) 531 | }); 532 | 533 | test('workflow name sanitized in role assumption tags', async () => { 534 | core.getInput = jest 535 | .fn() 536 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 537 | 538 | process.env = {...process.env, GITHUB_WORKFLOW: 'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA'}; 539 | 540 | const sanitizedWorkflowName = 'Workflow__________+_ -./:;<=>?@____________1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa' 541 | 542 | await run(); 543 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 544 | RoleArn: ROLE_ARN, 545 | RoleSessionName: 'GitHubActions', 546 | DurationSeconds: 6 * 3600, 547 | Tags: [ 548 | {Key: 'GitHub', Value: 'Actions'}, 549 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 550 | {Key: 'Workflow', Value: sanitizedWorkflowName}, 551 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 552 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 553 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 554 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 555 | ] 556 | }) 557 | }); 558 | 559 | test('skip tagging provided as true', async () => { 560 | core.getInput = jest 561 | .fn() 562 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-skip-session-tagging': true})); 563 | 564 | await run(); 565 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 566 | RoleArn: ROLE_ARN, 567 | RoleSessionName: 'GitHubActions', 568 | DurationSeconds: 21600, 569 | Tags: undefined 570 | }) 571 | }); 572 | 573 | test('skip tagging provided as false', async () => { 574 | core.getInput = jest 575 | .fn() 576 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'role-skip-session-tagging': false})); 577 | 578 | await run(); 579 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 580 | RoleArn: ROLE_ARN, 581 | RoleSessionName: 'GitHubActions', 582 | DurationSeconds: 21600, 583 | Tags: [ 584 | {Key: 'GitHub', Value: 'Actions'}, 585 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 586 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 587 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 588 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 589 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 590 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 591 | ] 592 | }) 593 | }); 594 | 595 | test('skip tagging not provided', async () => { 596 | core.getInput = jest 597 | .fn() 598 | .mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS})); 599 | 600 | await run(); 601 | expect(mockStsAssumeRole).toHaveBeenCalledWith({ 602 | RoleArn: ROLE_ARN, 603 | RoleSessionName: 'GitHubActions', 604 | DurationSeconds: 21600, 605 | Tags: [ 606 | {Key: 'GitHub', Value: 'Actions'}, 607 | {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY}, 608 | {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW}, 609 | {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION}, 610 | {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED}, 611 | {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA}, 612 | {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF}, 613 | ] 614 | }) 615 | }); 616 | 617 | test('masks variables before exporting', async () => { 618 | let maskedValues = []; 619 | const publicFields = ['AWS_REGION', 'AWS_DEFAULT_REGION']; 620 | core.setSecret.mockReset(); 621 | core.setSecret.mockImplementation((secret) => { 622 | maskedValues.push(secret); 623 | }); 624 | 625 | core.exportVariable.mockReset(); 626 | core.exportVariable.mockImplementation((name, value) => { 627 | if (!maskedValues.includes(value) && !publicFields.includes(name)) { 628 | throw new Error(value + " for variable " + name + " is not masked yet!"); 629 | } 630 | }); 631 | 632 | core.getInput = jest 633 | .fn() 634 | .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); 635 | 636 | await run(); 637 | }); 638 | 639 | }); 640 | --------------------------------------------------------------------------------