mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-08-24 04:25:05 +09:00
Compare commits
302 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ffcda53774 | |||
| 39c6a97582 | |||
| 42741432d4 | |||
| 8078ce0ebb | |||
| 70ddaa6bac | |||
| ddbaf5f59a | |||
| d10819b44a | |||
| be8dacf4c9 | |||
| d6cef3c044 | |||
| d73d78f404 | |||
| 5afbaca2ca | |||
| b64a17e6a8 | |||
| 0c14411b23 | |||
| 0dc1a4d7f6 | |||
| 9f5ce9a88b | |||
| 269cbf309a | |||
| 65e7b995bd | |||
| f50cf95a79 | |||
| c4d3b7d849 | |||
| 8847f6fb22 | |||
| 158470e9bf | |||
| 4219453061 | |||
| 54023a9bb9 | |||
| 867cd54918 | |||
| 982ee074df | |||
| 53caebc361 | |||
| 56ab19e885 | |||
| 281eef0dac | |||
| 00177ab388 | |||
| ebd966a3ae | |||
| f4c981cd71 | |||
| 7750261bf0 | |||
| 123342dcd6 | |||
| beb8a76634 | |||
| e39c69b801 | |||
| 77611502ec | |||
| 428bb81241 | |||
| 173f3c3fd6 | |||
| 22bc3c9a3c | |||
| 49981b951b | |||
| aa504e8ee4 | |||
| c7054df179 | |||
| bfcea37f4e | |||
| 56895078ea | |||
| 41a664e882 | |||
| 79531b8a10 | |||
| fad15eaf24 | |||
| 5dec3d3594 | |||
| 25227b9a1e | |||
| 07c4348e4f | |||
| 47337ef769 | |||
| 8d897af323 | |||
| dc74e14b79 | |||
| 375dd25baf | |||
| 230da0e042 | |||
| 1bb4f17501 | |||
| 0c08cbd062 | |||
| cddab27bcc | |||
| 0c8dfd7ac7 | |||
| 67239dbf04 | |||
| e9f0f780ef | |||
| b970b5daca | |||
| 7838721021 | |||
| 11d5373ff3 | |||
| d8d145180a | |||
| 3523ed4748 | |||
| 19114d4d14 | |||
| f8d509b786 | |||
| 234fd6db25 | |||
| 32092f94bf | |||
| 29d92e3c3c | |||
| 7e2225648c | |||
| c4eb366d3f | |||
| e46e6846e2 | |||
| 9ca6b8cb15 | |||
| b512dc6b4d | |||
| f5a1aa74fa | |||
| e14cbfbe41 | |||
| 6239eb860e | |||
| e1c5d41dda | |||
| 8ec073e640 | |||
| e21512a24b | |||
| dfdce0341e | |||
| 6759aac527 | |||
| 2f5db48505 | |||
| 5177388996 | |||
| 8bd33aae60 | |||
| 7ef09d93f2 | |||
| fc003a96b9 | |||
| 971f8e0317 | |||
| 56e74f240c | |||
| e4301e7409 | |||
| 19d30f37ce | |||
| 9f2f301355 | |||
| db0acda0b7 | |||
| f40941831c | |||
| 0299e8b63d | |||
| ab84932bd4 | |||
| e06544f9c1 | |||
| d296a6f9c6 | |||
| 494e828ec2 | |||
| 0d3251cb3d | |||
| de4add5d4b | |||
| f2bbe7ac86 | |||
| 3d63cbfca4 | |||
| a383c284dc | |||
| 4530f64fbe | |||
| f5b07e1980 | |||
| f08ee4b2e6 | |||
| 6bf389cedc | |||
| 1465d9e115 | |||
| ed3e3cea76 | |||
| 6922dfe844 | |||
| 17d9c5808b | |||
| 80a9577c61 | |||
| 93b00b9f38 | |||
| fde601610d | |||
| 064e6ec870 | |||
| aa52599dac | |||
| 5d940a63da | |||
| e6378d2a27 | |||
| 0e2664d7ab | |||
| 62cd6e3dec | |||
| 6bbe648032 | |||
| 055a04228c | |||
| ccc9e92812 | |||
| 71fcc3faee | |||
| 7bbacc5ed6 | |||
| 4a52fc69c7 | |||
| 261fe15aef | |||
| bdd66dd8da | |||
| fcf9d5adaf | |||
| 03cbca5dfa | |||
| 7e97815dc2 | |||
| ddfc625cb3 | |||
| 1ab60e2a5f | |||
| f26a86a6a9 | |||
| 7dbac97349 | |||
| 79be67248b | |||
| 4289e4c080 | |||
| 5e5773df02 | |||
| 437bcdc433 | |||
| 71ea6ac87b | |||
| 09d406fdff | |||
| e0cb2a46c5 | |||
| b244a48622 | |||
| cca0ca44b2 | |||
| 884380eea9 | |||
| af3e5697fc | |||
| b99aa4f17f | |||
| f995bff8ba | |||
| ed3e01c94b | |||
| a370189201 | |||
| 2638671029 | |||
| d40d5c6e33 | |||
| 5e8c1ceefd | |||
| c4719fcb5e | |||
| 4427332c53 | |||
| e04a462c90 | |||
| 887cd2dc54 | |||
| ad3a4f857f | |||
| 2d2d5ba422 | |||
| f24b0909c3 | |||
| 54f30a82cc | |||
| 733b542d71 | |||
| 4042331e3b | |||
| 6f81120cf9 | |||
| 65a09b82c7 | |||
| c5e69c6719 | |||
| f23203556c | |||
| 1bb74cc344 | |||
| d45c3bab4c | |||
| ca5b152543 | |||
| 95f14be4de | |||
| c5aa9e3c63 | |||
| dc5979fde9 | |||
| 531de3628b | |||
| 652e9dc948 | |||
| 1fdc1aba97 | |||
| 1659b53df7 | |||
| c7c26f4db1 | |||
| eed1dbd1b2 | |||
| b279fd518f | |||
| 22801f0b3e | |||
| b32153bf30 | |||
| b35e88274e | |||
| 1da8891971 | |||
| f582de096c | |||
| 9f7522478f | |||
| 6c1d857c8e | |||
| a87d6c80e4 | |||
| 1e2974e965 | |||
| 407f77794e | |||
| cc53b45104 | |||
| 4f27967ba2 | |||
| 7ca811ec04 | |||
| 067391feae | |||
| 7a441ec853 | |||
| 42f3df8c6d | |||
| eef01572f1 | |||
| a20cd9cc10 | |||
| cde0197cb8 | |||
| bf3de7dd3a | |||
| 65b0090178 | |||
| 39f329d57c | |||
| 98b15fd2df | |||
| 6b71242bfc | |||
| 11152182dc | |||
| aa54e61cee | |||
| 01ceb91ccc | |||
| 0c904bae68 | |||
| 26c80239ba | |||
| 2d704ca7de | |||
| 9ec3208b43 | |||
| 7fb675fd75 | |||
| 7f763b8323 | |||
| 91707c0dcf | |||
| 38546e5967 | |||
| 56a1fc0de4 | |||
| 517de21864 | |||
| 11dc2eacea | |||
| 29eeecb757 | |||
| 6f308da2eb | |||
| b850bd494c | |||
| 510f2be2b6 | |||
| 2521a17ac4 | |||
| ef792bc5f4 | |||
| 20e614fd3b | |||
| ca9b7628d1 | |||
| f27dceb0d2 | |||
| 510f5eb309 | |||
| f2a578c7e0 | |||
| e6fc186064 | |||
| 9d755088ad | |||
| 0c4d32edd2 | |||
| 28f5d8aa60 | |||
| c138219391 | |||
| b0d6b4734b | |||
| 8556bd2ba5 | |||
| 927ffbaa01 | |||
| c9af67c14b | |||
| 9573d31cea | |||
| 1d22c37745 | |||
| bc072deb94 | |||
| 2f68b5b77f | |||
| 3e9031ea81 | |||
| e1764007c6 | |||
| 37cf5bc447 | |||
| 476989a346 | |||
| 984e09349b | |||
| 23e0293a93 | |||
| a3809804d4 | |||
| 6194903182 | |||
| 22bbfc93dc | |||
| 66e9528279 | |||
| f4262f41ca | |||
| 0584ad30e8 | |||
| 97ffe1bf59 | |||
| fd6b36600e | |||
| b6753f8cdb | |||
| bb49542c83 | |||
| 7e8479cd78 | |||
| 904314ace9 | |||
| 7617c8446a | |||
| c967f02288 | |||
| da6c56690d | |||
| a3106fc918 | |||
| 2bed79c30a | |||
| fe2f185b53 | |||
| 50bebb45ae | |||
| 79baccafc6 | |||
| e6b3abec18 | |||
| c2187b0f98 | |||
| c5db572eb9 | |||
| 5df9c76314 | |||
| 6cadbafc6e | |||
| 661b774204 | |||
| 2114920f63 | |||
| a2a4e956b7 | |||
| a31179a438 | |||
| 5b76065b77 | |||
| 75ea080d90 | |||
| 5b7c76e806 | |||
| 6ca7d2cf9d | |||
| 4b2f8e7007 | |||
| aa176e0f81 | |||
| be2f672a7e | |||
| 50f9dace0e | |||
| 4009ab8463 | |||
| 4604f0ba35 | |||
| da5798ce57 | |||
| 51211f3751 | |||
| 23214f72d5 | |||
| 230f272080 | |||
| f560fd0ebe | |||
| c967135d79 | |||
| 88285ed915 | |||
| b41429af80 | |||
| 956cf8b7c4 | |||
| f3db7100d8 | |||
| ab24425ffc | |||
| 4be2a92b34 |
@@ -1,19 +0,0 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"inputs": ["./package.json"],
|
||||
"output": "./THIRD-PARTY",
|
||||
"overwrite": true,
|
||||
"ci": true
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/dist/** linguist-generated
|
||||
/package-lock.json linguist-generated
|
||||
*.json text eol=lf
|
||||
*.js text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.tsx text eol=lf
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: "🐛 Bug Report"
|
||||
description: Report a bug
|
||||
title: "short issue description"
|
||||
labels: [bug, needs-triage]
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: What is the problem? A clear and concise description of the bug.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: regression
|
||||
attributes:
|
||||
label: Regression Issue
|
||||
description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report.
|
||||
options:
|
||||
- label: Select this option if this issue appears to be a regression.
|
||||
required: false
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: |
|
||||
What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: |
|
||||
What actually happened?
|
||||
|
||||
Please include full errors, uncaught exceptions, stack traces, and relevant logs.
|
||||
If service responses are relevant, please include wire logs.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: |
|
||||
Provide a self-contained, concise snippet of code that can be used to reproduce the issue.
|
||||
For more complex issues provide a repo with the smallest sample that reproduces the bug.
|
||||
|
||||
Avoid including business logic or unrelated code, it makes diagnosis more difficult.
|
||||
The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: |
|
||||
Suggest a fix/reason for the bug
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Information/Context
|
||||
description: |
|
||||
Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 General Question
|
||||
url: https://github.com/aws-actions/configure-aws-credentials/discussions/categories/q-a
|
||||
about: Please ask and answer questions as a discussion thread
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: "📕 Documentation Issue"
|
||||
description: Report an issue in the README or a suggestion to improve documentation
|
||||
title: "short issue description"
|
||||
labels: [documentation, needs-triage]
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the issue
|
||||
description: A clear and concise description of the issue.
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: 🚀 Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "short issue description"
|
||||
labels: [feature-request, needs-triage]
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of the feature you are proposing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: use-case
|
||||
attributes:
|
||||
label: Use Case
|
||||
description: |
|
||||
Why do you need this feature? For example: "I'm always frustrated when..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: |
|
||||
Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
label: Other Information
|
||||
description: |
|
||||
Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: ack
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I may be able to implement this feature request
|
||||
required: false
|
||||
- label: This feature might incur a breaking change
|
||||
required: false
|
||||
@@ -1,11 +0,0 @@
|
||||
_Issue #, if available:_
|
||||
|
||||
_Description of changes:_
|
||||
|
||||
---
|
||||
|
||||
- [ ] Have you followed the guidelines in our
|
||||
[Contributing guide?](https://github.com/aws-actions/configure-aws-credentials/blob/main/CONTRIBUTING.md)
|
||||
|
||||
By submitting this pull request, I confirm that you can use, modify, copy, and
|
||||
redistribute this contribution, under the terms of your choice.
|
||||
@@ -1,5 +0,0 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
paths-ignore:
|
||||
- dist
|
||||
- node_modules
|
||||
@@ -1,9 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: tuesday
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: 'main'
|
||||
@@ -1,6 +0,0 @@
|
||||
Port 9999
|
||||
Listen 127.0.0.1
|
||||
Timeout 600
|
||||
Allow 127.0.0.1
|
||||
LogFile "/home/runner/work/configure-aws-credentials/configure-aws-credentials/integ_proxy_log.txt"
|
||||
LogLevel Connect
|
||||
@@ -1,32 +0,0 @@
|
||||
on:
|
||||
pull_request_review:
|
||||
types: submitted
|
||||
|
||||
jobs:
|
||||
approved_pr:
|
||||
name: Automerge approved PRs
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
if: ${{ github.event.review.state == 'approved' && github.repository == 'aws-actions/configure-aws-credentials' && (github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'MEMBER' || github.event.review.user.login == 'aws-sdk-osds') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.SECRETS_AWS_PACKAGING_ROLE_TO_ASSUME }}
|
||||
role-duration-seconds: 900
|
||||
role-session-name: SecretsManagerFetch
|
||||
- name: Get bot user token
|
||||
uses: aws-actions/aws-secretsmanager-get-secrets@v2
|
||||
with:
|
||||
parse-json-secrets: true
|
||||
secret-ids: |
|
||||
${{ secrets.OSDS_PACKAGING_ROLE }}
|
||||
- name: Enable PR automerge
|
||||
run: gh pr merge --auto --squash "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GITHUB_TOKEN: ${{ env.OSDS_ACCESS_TOKEN }}
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Test Configure AWS Credential
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cawsc:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: CAWSC
|
||||
uses: aws-actions/configure-aws-credentials@main
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.SECRETS_AWS_PACKAGING_ROLE_TO_ASSUME }}
|
||||
role-duration-seconds: 900
|
||||
role-session-name: TestCAWSC
|
||||
- name: Whoami
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
@@ -1,51 +0,0 @@
|
||||
name: "Close Stale Issues"
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 */4 * * *"
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
name: Stale issue job
|
||||
steps:
|
||||
- uses: aws-actions/stale-issue-cleanup@v6
|
||||
with:
|
||||
# Setting messages to an empty string will cause the automation to skip
|
||||
# that category
|
||||
ancient-issue-message: This issue has not received any attention in 1 year. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.
|
||||
stale-issue-message: This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.
|
||||
stale-pr-message: This PR has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.
|
||||
|
||||
# These labels are required
|
||||
stale-issue-label: closing-soon
|
||||
exempt-issue-labels: no-autoclose
|
||||
stale-pr-label: closing-soon
|
||||
exempt-pr-labels: no-autoclose
|
||||
response-requested-label: response-requested
|
||||
|
||||
# Don't set closed-for-staleness label to skip closing very old issues
|
||||
# regardless of label
|
||||
closed-for-staleness-label: closed-for-staleness
|
||||
|
||||
# Issue timing
|
||||
days-before-stale: 5
|
||||
days-before-close: 2
|
||||
days-before-ancient: 36500
|
||||
|
||||
# If you don't want to mark a issue as being ancient based on a
|
||||
# threshold of "upvotes", you can set this here. An "upvote" is
|
||||
# the total number of +1, heart, hooray, and rocket reactions
|
||||
# on an issue.
|
||||
minimum-upvotes-to-exempt: 5
|
||||
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
loglevel: DEBUG
|
||||
# Set dry-run to true to not perform label or close actions.
|
||||
dry-run: false
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Closed Issue Message
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
jobs:
|
||||
auto_comment:
|
||||
permissions:
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: aws-actions/closed-issue-message@v1
|
||||
with:
|
||||
# These inputs are both required
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
message: |
|
||||
Comments on closed issues are hard for our team to see.
|
||||
If you need more assistance, please either tag a team member or open a new issue that references this one.
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Dependabot auto-approve
|
||||
on:
|
||||
pull_request_target:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'aws-actions/configure-aws-credentials' }}
|
||||
steps:
|
||||
- name: Get Metadata
|
||||
id: dependabot-metadata
|
||||
uses: dependabot/fetch-metadata@v2
|
||||
- uses: actions/checkout@v5
|
||||
name: Clone repo
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.CONFIGUREAWSCREDENTIALSPACKAGEROLEARN }}
|
||||
role-duration-seconds: 900
|
||||
- name: Get bot user token
|
||||
uses: aws-actions/aws-secretsmanager-get-secrets@v2
|
||||
with:
|
||||
parse-json-secrets: true
|
||||
secret-ids: |
|
||||
${{ secrets.OSDS_PACKAGING_ROLE }}
|
||||
- name: Approve PR if not already approved
|
||||
run: |
|
||||
gh pr checkout "$PR_URL"
|
||||
if [ "$(gh pr status --json reviewDecision - q .currentBranch.reviewDecision)" != "APPROVED" ]; then
|
||||
gh pr review "$PR_URL" --approve
|
||||
else echo "PR already approved"
|
||||
fi
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GITHUB_TOKEN: ${{ env.OSDS_ACCESS_TOKEN }}
|
||||
@@ -1,18 +0,0 @@
|
||||
name: HandleStaleDiscussions
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */4 * * *'
|
||||
discussion_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
handle-stale-discussions:
|
||||
name: Handle stale discussions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
discussions: write
|
||||
steps:
|
||||
- name: Stale discussions action
|
||||
uses: aws-github-ops/handle-stale-discussions@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
@@ -1,32 +0,0 @@
|
||||
# Apply potential regression label on issues
|
||||
name: issue-regression-label
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
jobs:
|
||||
add-regression-label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Fetch template body
|
||||
id: check_regression
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TEMPLATE_BODY: ${{ github.event.issue.body }}
|
||||
with:
|
||||
script: |
|
||||
const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i;
|
||||
const template = `${process.env.TEMPLATE_BODY}`
|
||||
const match = regressionPattern.test(template);
|
||||
core.setOutput('is_regression', match);
|
||||
- name: Manage regression label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then
|
||||
gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }}
|
||||
else
|
||||
gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }}
|
||||
fi
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Update dist files on main branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'dist/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
package:
|
||||
name: Package dist files
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref_name }}
|
||||
persist-credentials: false
|
||||
- name: Package
|
||||
run: |
|
||||
npm ci
|
||||
npm test
|
||||
npm run package
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.SECRETS_AWS_PACKAGING_ROLE_TO_ASSUME }}
|
||||
role-duration-seconds: 900
|
||||
role-session-name: SecretsManagerFetch
|
||||
- name: Get bot user token
|
||||
uses: aws-actions/aws-secretsmanager-get-secrets@v2
|
||||
with:
|
||||
parse-json-secrets: true
|
||||
secret-ids: |
|
||||
${{ secrets.OSDS_PACKAGING_ROLE }}
|
||||
- name: Commit
|
||||
run: |
|
||||
echo "::add-mask::${{ env.OSDS_ACCESS_TOKEN }}"
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "github-aws-sdk-osds-automation@amazon.com"
|
||||
git remote set-url origin https://${{ env.OSDS_ACCESS_TOKEN }}@github.com/aws-actions/configure-aws-credentials.git
|
||||
git add dist
|
||||
git add THIRD-PARTY
|
||||
git commit -m "chore: Update dist" || echo "No changes to commit"
|
||||
git push --force origin
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
name: pull-request-lint
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
- edited
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5.5.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |-
|
||||
feat
|
||||
fix
|
||||
chore
|
||||
requireScope: false
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
name: Release Please
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.SECRETS_AWS_PACKAGING_ROLE_TO_ASSUME }}
|
||||
role-duration-seconds: 900
|
||||
role-session-name: ${{ github.run_id }}
|
||||
|
||||
- name: Get git credentials
|
||||
uses: aws-actions/aws-secretsmanager-get-secrets@v2
|
||||
with:
|
||||
parse-json-secrets: true
|
||||
secret-ids: |
|
||||
${{ secrets.OSDS_PACKAGING_ROLE }}
|
||||
|
||||
- name: Run release-please
|
||||
id: release
|
||||
uses: googleapis/release-please-action@v4
|
||||
with:
|
||||
release-type: node
|
||||
token: ${{ env.OSDS_ACCESS_TOKEN }}
|
||||
config-file: release-please-config.json
|
||||
manifest-file: .release-please-manifest.json
|
||||
|
||||
- name: Checkout Again
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Tag Major Version
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "github-aws-sdk-osds-automation@amazon.com"
|
||||
if git rev-parse "v${{ steps.release.outputs.major }}" >/dev/null 2>&1; then
|
||||
git tag -d "v${{ steps.release.outputs.major }}"
|
||||
git push origin ":v${{ steps.release.outputs.major }}"
|
||||
fi
|
||||
git tag -a "v${{ steps.release.outputs.major }}" -m "Release v${{ steps.release.outputs.major }}"
|
||||
git push origin "v${{ steps.release.outputs.major }}"
|
||||
|
||||
- name: Update README version references
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: |
|
||||
sed -i 's|configure-aws-credentials@v[0-9]*\.[0-9]*\.[0-9]*|configure-aws-credentials@${{ steps.release.outputs.tag_name }}|g' README.md
|
||||
if git diff --quiet README.md; then
|
||||
echo "README already up to date"
|
||||
else
|
||||
echo "::add-mask::${{ env.OSDS_ACCESS_TOKEN }}"
|
||||
git remote set-url origin https://${{ env.OSDS_ACCESS_TOKEN }}@github.com/aws-actions/configure-aws-credentials.git
|
||||
git add README.md
|
||||
git commit -m "docs: update README version references to ${{ steps.release.outputs.tag_name }}"
|
||||
git push --force origin
|
||||
fi
|
||||
@@ -0,0 +1,60 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- integ-tests
|
||||
|
||||
name: Integration Test
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Print current build ID
|
||||
run: |
|
||||
echo Integration test run: ConfigureAWSCredentialsDepl-DaaDYhTJsCVq:536d0e31-876d-4508-9147-c9197c1ff49e
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@bab55d3830fe69833c9fecaa51fe2c829a7508f3
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
|
||||
- name: Test environment is configured with AWS credentials
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "user/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
|
||||
- name: Configure AWS credentials from role
|
||||
uses: aws-actions/configure-aws-credentials@bab55d3830fe69833c9fecaa51fe2c829a7508f3
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
role-to-assume: github-actions-configure-aws-credentials-integ-tests
|
||||
role-duration-seconds: 900
|
||||
role-session-name: github-actions-integ-test
|
||||
role-external-id: ${{ secrets.INTEG_TEST_ROLE_EXTERNAL_ID }}
|
||||
|
||||
- name: Test environment is configured with AWS credentials from role
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "role/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
|
||||
- name: Configure AWS credentials from role again
|
||||
uses: aws-actions/configure-aws-credentials@bab55d3830fe69833c9fecaa51fe2c829a7508f3
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
role-to-assume: github-actions-configure-aws-credentials-integ-tests
|
||||
role-duration-seconds: 900
|
||||
role-session-name: github-actions-integ-test
|
||||
role-external-id: ${{ secrets.INTEG_TEST_ROLE_EXTERNAL_ID }}
|
||||
|
||||
- name: Test environment is configured with AWS credentials from role
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "role/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Run post-release integration test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'dist/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
package_and_test:
|
||||
name: Package dist files
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Integation tests
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.CAWSC_integ_role }}
|
||||
role-session-name: IntegTestCAWSC
|
||||
- name: Get caller identity
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
@@ -1,281 +0,0 @@
|
||||
name: Run pre-release integ tests
|
||||
on:
|
||||
pull_request_target:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
oidc:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: OIDC login test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Configure AWS credentials
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.OIDC_integ_role }}
|
||||
- name: Get Caller Identity
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
|
||||
#can cut this test out if it's not necessary
|
||||
static_assumeRole:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
name: Static IAM creds test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: assume creator Role
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.STATIC_USER_CREATION_ROLE }}
|
||||
- name: create and mask access key
|
||||
id: create-key
|
||||
run: |
|
||||
AK_OUTPUT=$(aws iam create-access-key --user-name integ-test-static-user-${{ runner.os }})
|
||||
AK_ID=$(echo $AK_OUTPUT | jq -r '.AccessKey.AccessKeyId')
|
||||
SECRET_AK=$(echo $AK_OUTPUT | jq -r '.AccessKey.SecretAccessKey')
|
||||
echo "::add-mask::$AK_ID"
|
||||
echo "::add-mask::$SECRET_AK"
|
||||
echo "STATIC_ACCESS_KEY_ID=$AK_ID" >> $GITHUB_OUTPUT
|
||||
echo "STATIC_SECRET_ACCESS_KEY=$SECRET_AK" >> $GITHUB_OUTPUT
|
||||
- name: sleep so key can propagate
|
||||
run: sleep 30
|
||||
- name: assume role with static creds
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.STATIC_TEST_TARGET_ROLE }}
|
||||
aws-access-key-id: ${{ steps.create-key.outputs.STATIC_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ steps.create-key.outputs.STATIC_SECRET_ACCESS_KEY }}
|
||||
- name: check account
|
||||
run: aws sts get-caller-identity
|
||||
- name: log back into creator role
|
||||
if: success() || failure()
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.STATIC_USER_CREATION_ROLE }}
|
||||
- name: delete access key
|
||||
if: success() || failure()
|
||||
run: |
|
||||
aws iam delete-access-key --user-name integ-test-static-user-${{ runner.os }} --access-key-id ${{ steps.create-key.outputs.STATIC_ACCESS_KEY_ID }}
|
||||
|
||||
role_chaining:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Existing Creds + Role Chaining test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Configure AWS credentials
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.ROLE_chaining_1 }}
|
||||
- name: Get Caller Identity
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
- name: assume second role
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.ROLE_chaining_2 }}
|
||||
role-chaining: true
|
||||
- name: get caller identity
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
|
||||
inline_policy:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Inline Policy Test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: get creds w scoped down policy
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.INLINE_policy_role }}
|
||||
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:ListAllMyBuckets","Resource":"*"}]}'
|
||||
|
||||
#NOTE: This step should succeed. The role should have permission only to list all buckets.
|
||||
- name: list buckets
|
||||
run: aws s3 ls > /dev/null
|
||||
|
||||
#NOTE: This step should fail. we don't want the role to have permission to see the bucket contents.
|
||||
- name: try to list bucket contents
|
||||
id: bucketContentsStep
|
||||
continue-on-error: true
|
||||
run: aws s3 ls s3://cawsc-integ-tests-bucket > /dev/null
|
||||
|
||||
#But the test fails if we could list the bucket contents.
|
||||
- name: fail if we can list bucket contents
|
||||
if: steps.bucketContentsStep.outcome == 'success'
|
||||
run: exit 1
|
||||
|
||||
http-proxy:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
name: HTTP Proxy Test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: install tinyproxy
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install tinyproxy
|
||||
- name: start tinyproxy
|
||||
run: tinyproxy -c .github/integ_tests/tinyproxy.conf
|
||||
- name: Configure AWS credentials
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.OIDC_integ_role }}
|
||||
http-proxy: http://127.0.0.1:9999
|
||||
retry-max-attempts: 4
|
||||
- name: checkout logs
|
||||
run: cat integ_proxy_log.txt
|
||||
- name: check logs to see if successful call
|
||||
run: grep -q "Request" integ_proxy_log.txt && echo "PROXY_CALL_LOGGED=1" >> $GITHUB_ENV || echo "PROXY_CALL_LOGGED=0" >> $GITHUB_ENV
|
||||
- name: fail job if bad call
|
||||
if: ${{ env.PROXY_CALL_LOGGED != 1 }}
|
||||
run: exit 1
|
||||
|
||||
token-file:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Token File Test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: fetch token and write to file
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
async function getIDTokenAction() {
|
||||
const id_token = await core.getIDToken("sts.amazonaws.com");
|
||||
return id_token;
|
||||
}
|
||||
const idToken = await getIDTokenAction();
|
||||
fs.writeFileSync(".github/integ_tests/integ_token.txt", idToken, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
- name: get creds with that file
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: ${{ secrets.OIDC_integ_role }}
|
||||
web-identity-token-file: .github/integ_tests/integ_token.txt
|
||||
retry-max-attempts: 4
|
||||
- name: check creds
|
||||
run: aws sts get-caller-identity
|
||||
|
||||
profile-oidc:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }}
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: OIDC profile writing test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: make profile 1
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.INTEG_PROFILE_ROLE_1 }}
|
||||
aws-profile: TestProfile1
|
||||
|
||||
- name: make profile 2
|
||||
uses: ./
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: ${{ secrets.INTEG_PROFILE_ROLE_2 }}
|
||||
aws-profile: TestProfile2
|
||||
|
||||
- name: check profile1
|
||||
run: aws sts get-caller-identity --profile TestProfile1
|
||||
|
||||
- name: check profile2
|
||||
run: aws sts get-caller-identity --profile TestProfile2
|
||||
|
||||
- name: Get Caller Identity
|
||||
id: no-creds-sts-step
|
||||
continue-on-error: true
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
|
||||
- name: fail if we got caller id with env creds
|
||||
if: steps.no-creds-sts-step.outcome == 'success'
|
||||
run: exit 1
|
||||
@@ -1,42 +0,0 @@
|
||||
on:
|
||||
[pull_request]
|
||||
|
||||
name: Run unit tests
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
name: Run unit tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@v5
|
||||
- name: "Setup node"
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
- name: "Install dependencies"
|
||||
run: npm ci
|
||||
- name: "Run tests"
|
||||
run: npm run test --if-present
|
||||
lint:
|
||||
name: Ensure code standards
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: "Checkout repostiory"
|
||||
uses: actions/checkout@v5
|
||||
- name: "Setup node"
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: "Install dependencies"
|
||||
uses: bahmutov/npm-install@v1
|
||||
- name: "Lint code"
|
||||
run: npm run lint --if-present
|
||||
@@ -1,7 +0,0 @@
|
||||
.history
|
||||
node_modules
|
||||
coverage
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
build
|
||||
test-reports
|
||||
@@ -1,17 +0,0 @@
|
||||
default: true
|
||||
MD007:
|
||||
indent: 4
|
||||
heading-style:
|
||||
style: atx
|
||||
ul-style:
|
||||
style: dash
|
||||
line-length:
|
||||
stern: true
|
||||
tables: false
|
||||
code_blocks: false
|
||||
no-inline-html:
|
||||
allowed_elements:
|
||||
- details
|
||||
- summary
|
||||
code-block-style:
|
||||
style: fenced
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
".release-please-manifest.json": "4.0.2",
|
||||
"package.json": "6.0.0",
|
||||
".": "6.2.0"
|
||||
}
|
||||
-419
@@ -1,419 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
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.
|
||||
|
||||
## [6.2.0](https://github.com/aws-actions/configure-aws-credentials/compare/v6.1.3...v6.2.0) (2026-06-01)
|
||||
|
||||
### Features
|
||||
|
||||
* add additional session tags by default ([#1775](https://github.com/aws-actions/configure-aws-credentials/issues/1775)) ([e0ba768](https://github.com/aws-actions/configure-aws-credentials/commit/e0ba7685077379a14a82d01fefd511490344ebfc))
|
||||
* add more retry logic and better logging ([#1764](https://github.com/aws-actions/configure-aws-credentials/issues/1764)) ([540d0c1](https://github.com/aws-actions/configure-aws-credentials/commit/540d0c13aedb8d55501d220bd2f0b3cdedfe84e8))
|
||||
* add regex validation to role-session-name ([#1765](https://github.com/aws-actions/configure-aws-credentials/issues/1765)) ([e354499](https://github.com/aws-actions/configure-aws-credentials/commit/e35449909c6ede5083a48ba4b8bbfaaa1cf09ba1))
|
||||
* Allow custom session tags to be passed when assuming a role ([#1759](https://github.com/aws-actions/configure-aws-credentials/issues/1759)) ([61f50f6](https://github.com/aws-actions/configure-aws-credentials/commit/61f50f630f383628add73c1eab3f1935ba07da2b))
|
||||
* expose run id in STS client user-agent ([#1774](https://github.com/aws-actions/configure-aws-credentials/issues/1774)) ([29d1be3](https://github.com/aws-actions/configure-aws-credentials/commit/29d1be30273e7ef371d59fccf6ec54572c64ec89))
|
||||
* support custom STS endpoints ([#1762](https://github.com/aws-actions/configure-aws-credentials/issues/1762)) ([8d52d05](https://github.com/aws-actions/configure-aws-credentials/commit/8d52d05d7a4521fa52b39de50cb6114b12e5c332))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip credential check on output-env-credentials: false ([#1778](https://github.com/aws-actions/configure-aws-credentials/issues/1778)) ([58e7c47](https://github.com/aws-actions/configure-aws-credentials/commit/58e7c47adf77846879008deadfeeef8a6969fe6c))
|
||||
* assumeRole failing from session tag size too large ([#1808](https://github.com/aws-actions/configure-aws-credentials/issues/1808)) ([d6f5dc3](https://github.com/aws-actions/configure-aws-credentials/commit/d6f5dc331b44474b19a52caaf85fa4d637b13c8e))
|
||||
|
||||
## [6.1.3](https://github.com/aws-actions/configure-aws-credentials/compare/v6.1.2...v6.1.3) (2026-05-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: allow kubelet token symlink in [#1805](https://github.com/aws-actions/configure-aws-credentials/issues/1805)
|
||||
|
||||
## [6.1.2](https://github.com/aws-actions/configure-aws-credentials/compare/v6.1.1...v6.1.2) (2026-05-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* additional filesystem checks ([#1799](https://github.com/aws-actions/configure-aws-credentials/issues/1799)) ([c39f282](https://github.com/aws-actions/configure-aws-credentials/commit/c39f282697aca8a78c522ecf1f7da9899a31432c))
|
||||
|
||||
## [6.1.1](https://github.com/aws-actions/configure-aws-credentials/compare/v6.1.0...v6.1.1) (2026-05-05)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* various dependency updates
|
||||
|
||||
## [6.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v6.0.0...v6.1.0) (2026-04-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add skip cleanup option ([#1716](https://github.com/aws-actions/configure-aws-credentials/issues/1716)) ([11b1c58](https://github.com/aws-actions/configure-aws-credentials/commit/11b1c58b24724e66aa52a847862a0c1b0c4b0c7b)), closes [#1545](https://github.com/aws-actions/configure-aws-credentials/issues/1545)
|
||||
* Support usage of AWS Profiles ([#1696](https://github.com/aws-actions/configure-aws-credentials/issues/1696)) ([a7f0c82](https://github.com/aws-actions/configure-aws-credentials/commit/a7f0c828ac76e0d049e34c920172c60f579f9eb3))
|
||||
|
||||
## [6.0.0](https://github.com/aws-actions/configure-aws-credentials/compare/v5.1.1...v6.0.0) (2026-02-04)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* Update action to use node24 ([#1632](https://github.com/aws-actions/configure-aws-credentials/pull/1632)) ([a7a2c11](https://github.com/aws-actions/configure-aws-credentials/commit/a7a2c1125c67f40a1e95768f4e4a7d8f019f87af))
|
||||
|
||||
### Features
|
||||
|
||||
* add support to define transitive tag keys ([#1316](https://github.com/aws-actions/configure-aws-credentials/pull/1316)) ([232435c](https://github.com/aws-actions/configure-aws-credentials/commit/232435c0c05e51137544f0203931b84893d13b74)) ([930ebd9](https://github.com/aws-actions/configure-aws-credentials/pull/1628/changes/930ebd9bcaed959c3ba9e21567e8abbc3cae72c0))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly output `aws-account-id` and `authenticated-arn` when using role-chaining ([#1633](https://github.com/aws-actions/configure-aws-credentials/pull/1633)) ([7ceaf96](https://github.com/aws-actions/configure-aws-credentials/commit/7ceaf96edc86cc1713cef59eba79feeb23f59da1))
|
||||
|
||||
## [5.1.1](https://github.com/aws-actions/configure-aws-credentials/compare/v5.1.0...v5.1.1) (2025-11-24)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 5.1.1 ([56d6a58](https://github.com/aws-actions/configure-aws-credentials/commit/56d6a583f00f6bad6d19d91d53a7bc3b8143d0e9))
|
||||
|
||||
## [5.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v5.0.0...v5.1.0) (2025-10-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add global timeout support ([#1487](https://github.com/aws-actions/configure-aws-credentials/issues/1487)) ([1584b8b](https://github.com/aws-actions/configure-aws-credentials/commit/1584b8b0e2062557287c28fbe9b8920df434e866))
|
||||
* add no-proxy support ([#1482](https://github.com/aws-actions/configure-aws-credentials/issues/1482)) ([dde9b22](https://github.com/aws-actions/configure-aws-credentials/commit/dde9b22a8e889a0821997a21a2c5a38020ee8de3))
|
||||
* Improve debug logging in retry logic ([#1485](https://github.com/aws-actions/configure-aws-credentials/issues/1485)) ([97ef425](https://github.com/aws-actions/configure-aws-credentials/commit/97ef425d73aa532439f54f90d0e83101a186c5a6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly expose getProxyForUrl (introduced in [#1482](https://github.com/aws-actions/configure-aws-credentials/issues/1482)) ([#1486](https://github.com/aws-actions/configure-aws-credentials/issues/1486)) ([cea4298](https://github.com/aws-actions/configure-aws-credentials/commit/cea42985ac88b42678fbc84c18066a7f07f05176))
|
||||
|
||||
## [5.0.0](https://github.com/aws-actions/configure-aws-credentials/compare/v4.3.1...v5.0.0) (2025-09-03)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* Cleanup input handling. Changes invalid boolean input behavior (see #1445)
|
||||
|
||||
### Features
|
||||
|
||||
* add skip OIDC option ([#1458](https://github.com/aws-actions/configure-aws-credentials/issues/1458)) ([8c45f6b](https://github.com/aws-actions/configure-aws-credentials/commit/8c45f6b08196feb86cfdbe431541d5571d9ab2c2))
|
||||
* Cleanup input handling. Changes invalid boolean input behavior (see [#1445](https://github.com/aws-actions/configure-aws-credentials/issues/1445)) ([74b3e27](https://github.com/aws-actions/configure-aws-credentials/commit/74b3e27aa80db064b5bb8c04b22fc607e817acf7))
|
||||
* support account id allowlist ([#1456](https://github.com/aws-actions/configure-aws-credentials/issues/1456)) ([c4be498](https://github.com/aws-actions/configure-aws-credentials/commit/c4be498953fc1da2707a50ce4b761a53af3d02af))
|
||||
|
||||
## [4.3.1](https://github.com/aws-actions/configure-aws-credentials/compare/v4.3.0...v4.3.1) (2025-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update readme to 4.3.1 ([#1424](https://github.com/aws-actions/configure-aws-credentials/issues/1424)) ([be2e7ad](https://github.com/aws-actions/configure-aws-credentials/commit/be2e7ad815e27b890489a89ce2717b0f9e26b56e))
|
||||
|
||||
## [4.3.0](https://github.com/aws-actions/configure-aws-credentials/compare/v4.2.1...v4.3.0) (2025-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* depenency update and feature cleanup ([#1414](https://github.com/aws-actions/configure-aws-credentials/issues/1414)) ([59489ba](https://github.com/aws-actions/configure-aws-credentials/commit/59489ba544930000b7b67412c167f5fe816568cf)), closes [#1062](https://github.com/aws-actions/configure-aws-credentials/issues/1062) [#1191](https://github.com/aws-actions/configure-aws-credentials/issues/1191)
|
||||
* Optional environment variable output ([c3b3ce6](https://github.com/aws-actions/configure-aws-credentials/commit/c3b3ce61b02510937ff02916a4eb153874bc5085))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** readme samples versioning ([5b3c895](https://github.com/aws-actions/configure-aws-credentials/commit/5b3c89504689ea1ea2b6000b23a6a2aac463662a))
|
||||
* the wrong example region for China partition in README ([37fe9a7](https://github.com/aws-actions/configure-aws-credentials/commit/37fe9a740bcb30ee8cccd96feb90666c937311f2))
|
||||
* properly set proxy environment variable ([cbea708](https://github.com/aws-actions/configure-aws-credentials/commit/cbea70821e4ab985ad3be0e5a93390523e257cde))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 4.3.0 ([3f7c218](https://github.com/aws-actions/configure-aws-credentials/commit/3f7c2187213bafaa1ea60a850b27082cbf55dda0))
|
||||
|
||||
## [4.2.1](https://github.com/aws-actions/configure-aws-credentials/compare/v4.2.0...v4.2.1) (2025-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensure explicit inputs take precedence over environment variables ([e56e6c4](https://github.com/aws-actions/configure-aws-credentials/commit/e56e6c4038915cd5a7238a671fe97f44c98a40b0))
|
||||
* prioritize explicit inputs over environment variables ([df9c8fe](https://github.com/aws-actions/configure-aws-credentials/commit/df9c8fed6b364f0d1fb0e6e03a0ec26f1ea4e3fc))
|
||||
|
||||
## [4.2.0](https://github.com/aws-actions/configure-aws-credentials/compare/v4.1.0...v4.2.0) (2025-05-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Expiration field to Outputs ([a4f3267](https://github.com/aws-actions/configure-aws-credentials/commit/a4f326760c1c1bf49ab86051c658d6501816b930))
|
||||
* Document role-duration-seconds range ([5a0cf01](https://github.com/aws-actions/configure-aws-credentials/commit/5a0cf0167f837dfa7af7d951ba6a78a38dc2b79e))
|
||||
* support action inputs as environment variables ([#1338](https://github.com/aws-actions/configure-aws-credentials/issues/1338)) ([2c168ad](https://github.com/aws-actions/configure-aws-credentials/commit/2c168adcae62d67531ba83842723c8f30695116a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure action builds, also fix dependabot autoapprove ([c401b8a](https://github.com/aws-actions/configure-aws-credentials/commit/c401b8a98c5067672f52e0387cdd87d54acfe1fd))
|
||||
* role chaning on mulitple runs ([#1340](https://github.com/aws-actions/configure-aws-credentials/issues/1340)) ([9e38641](https://github.com/aws-actions/configure-aws-credentials/commit/9e386419117a9edd458297e4f1822a5df7506a03))
|
||||
|
||||
## [4.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v4.0.3...v4.1.0) (2025-02-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* idempotent fetch ([#1289](https://github.com/aws-actions/configure-aws-credentials/issues/1289)) ([eb70354](https://github.com/aws-actions/configure-aws-credentials/commit/eb70354fb423a380b6e4ab4b9f15d2ee9ffae911))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build failure due to tests ([#1283](https://github.com/aws-actions/configure-aws-credentials/issues/1283)) ([134d71e](https://github.com/aws-actions/configure-aws-credentials/commit/134d71efe0ecbe9ad6965f2f766c0cae63a7685f))
|
||||
* Dependabot autoapprove ([#1284](https://github.com/aws-actions/configure-aws-credentials/issues/1284)) ([b9ee51d](https://github.com/aws-actions/configure-aws-credentials/commit/b9ee51dc600fe38c892e24f60ca26476e0e0b6de))
|
||||
* Dependabot autoapprove id-token write permission ([#1285](https://github.com/aws-actions/configure-aws-credentials/issues/1285)) ([f0af89b](https://github.com/aws-actions/configure-aws-credentials/commit/f0af89b102390dcf10ce402195d74a98f24861f3))
|
||||
* typo ([#1281](https://github.com/aws-actions/configure-aws-credentials/issues/1281)) ([39fd91c](https://github.com/aws-actions/configure-aws-credentials/commit/39fd91c08ed8bf770034de4e62662503e8007d76))
|
||||
|
||||
## [4.0.3](https://github.com/aws-actions/configure-aws-credentials/compare/v4.0.2...v4.0.3) (2025-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added release-please action config ([0f88004](https://github.com/aws-actions/configure-aws-credentials/commit/0f88004d9c27e0bdbbc254b3f7c8053cb38f04d7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add id-token permission to automerge ([97834a4](https://github.com/aws-actions/configure-aws-credentials/commit/97834a484a5ab3c40fa9e2eb40fcf8041105a573))
|
||||
* cpy syntax on npm package ([#1195](https://github.com/aws-actions/configure-aws-credentials/issues/1195)) ([83b5a56](https://github.com/aws-actions/configure-aws-credentials/commit/83b5a565471214aec459e234bef606339fe07111))
|
||||
* force push packaged files to main ([bfd2185](https://github.com/aws-actions/configure-aws-credentials/commit/bfd218503eb87938c29603a551e19c6b594f5fe5))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 4.0.3 ([ca00fd4](https://github.com/aws-actions/configure-aws-credentials/commit/ca00fd4d3842ad58c3c21ebfe69defa1f0e7bdc4))
|
||||
|
||||
## [4.0.2](https://github.com/aws-actions/configure-aws-credentials/compare/v4.0.1...v4.0.2) (2024-02-09)
|
||||
|
||||
* Revert 4.0.1 to remove warning
|
||||
|
||||
## [4.0.1](https://github.com/aws-actions/configure-aws-credentials/compare/v4.0.0...v4.0.1) (2023-10-03)
|
||||
|
||||
### Documentation
|
||||
* Throw a warning when customers use long-term credentials.
|
||||
|
||||
## [4.0.0](https://github.com/aws-actions/configure-aws-credentials/compare/v3.0.2...v4.0.0) (2023-09-11)
|
||||
|
||||
* Upgraded runtime to `node20` from `node16`
|
||||
|
||||
## [3.0.2](https://github.com/aws-actions/configure-aws-credentials/compare/v3.0.1...v3.0.2) (2023-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
* fixes #817 #819: validation logic throwing unwanted errors [d78f55b](https://github.com/aws-actions/configure-aws-credentials/commit/d78f55b1db65186cb251a8504ae9527af06fc5fd)
|
||||
|
||||
## [3.0.1](https://github.com/aws-actions/configure-aws-credentials/compare/v3.0.0...v3.0.1) (2023-08-24)
|
||||
|
||||
### Features
|
||||
* Can configure `special-characters-workaround` to keep retrying credentials if the returned
|
||||
credentials have special characters (Fixes #599)
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes #792: Action fails when intending to use existing credentials
|
||||
* Minor typo fix from @ubaid-ansari21
|
||||
|
||||
### Changes to existing functionality
|
||||
* Special characters are now allowed in returned credential variables unless you configure the
|
||||
`special-characters-workaround` option
|
||||
|
||||
## [3.0.0](https://github.com/aws-actions/configure-aws-credentials/compare/v2.2.0...v3.0.0) (2023-08-21)
|
||||
|
||||
### Features
|
||||
* Can configure `max-retries` and `disable-retry` to modify retry functionality when the assume role call fails
|
||||
* Set returned credentials as step outputs with `output-credentials`
|
||||
* Clear AWS related environment variables at the start of the action with `unset-current-credentials`
|
||||
* Unique role identifier is now printed in the workflow logs
|
||||
|
||||
### Bug Fixes
|
||||
* Can't use credentials if they contain a special character
|
||||
* Retry functionality added when generating the JWT fails
|
||||
* Can now use `webIdentityTokenFile` option
|
||||
* Branch name validation too strict
|
||||
* JS SDK v2 deprecation warning in workflow logs
|
||||
|
||||
### Changes to existing functionality
|
||||
* Default session duration is now 1 hour in all cases (from 6 hours in some cases)
|
||||
* Account ID will not be masked by default in logs
|
||||
|
||||
## [2.2.0](https://github.com/aws-actions/configure-aws-credentials/compare/v2.1.0...v2.2.0) (2023-05-31)
|
||||
|
||||
### Features
|
||||
* `inline-session-policy` prop enables assuming a role with inline session policies ([d00f6c6](https://github.com/aws-actions/configure-aws-credentials/commit/d00f6c6f41fde02a9fd0d469040be6ed0df69e73))
|
||||
* `managed-session-policies` prop enables assuming a role with managed policy arns ([d00f6c6](https://github.com/aws-actions/configure-aws-credentials/commit/d00f6c6f41fde02a9fd0d469040be6ed0df69e73))
|
||||
|
||||
## [2.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v2.0.0...v2.1.0) (2023-05-31)
|
||||
|
||||
### Features
|
||||
* `role-chaining` prop enables role chaining use case ([6fbd316](https://github.com/aws-actions/configure-aws-credentials/commit/6fbd316fd15f52c3d9f68e7aa06eae4f5699a518))
|
||||
|
||||
## [2.0.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.7.0...v2.0.0) (2023-03-06)
|
||||
|
||||
### Features
|
||||
* Version bump to use Node 16 by default.
|
||||
|
||||
## [1.7.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.6.1...v1.7.0) (2022-08-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow audience to be explicitly specified ([2f8dfd0](https://github.com/aws-actions/configure-aws-credentials/commit/2f8dfd0ed43d880f85b57f0c8727b497af2037de))
|
||||
|
||||
### [1.6.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.6.0...v1.6.1) (2022-01-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OIDC Parallel Requests error ([133757e](https://github.com/aws-actions/configure-aws-credentials/commit/133757e9b829f4ef44c8e99e3f272879b45fc9c5))
|
||||
* Strict Mode Deprecation ([4c5e1c6](https://github.com/aws-actions/configure-aws-credentials/commit/4c5e1c60ccfc95d0e48bf1bc95fc707a94aa2c60))
|
||||
|
||||
## [1.6.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.11...v1.6.0) (2021-11-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add the ability to use a web identity token file ([#240](https://github.com/aws-actions/configure-aws-credentials/issues/240)) ([8053174](https://github.com/aws-actions/configure-aws-credentials/commit/8053174404968575ac1dd102dcb1109d2fe6d9ea))
|
||||
* added OIDC ([#262](https://github.com/aws-actions/configure-aws-credentials/issues/262)) ([b8c74de](https://github.com/aws-actions/configure-aws-credentials/commit/b8c74de753fbcb4868bf2011fb2e15826ce973af)), closes [#267](https://github.com/aws-actions/configure-aws-credentials/issues/267)
|
||||
* upgraded to new GH OIDC API ([#284](https://github.com/aws-actions/configure-aws-credentials/issues/284)) ([036a4a1](https://github.com/aws-actions/configure-aws-credentials/commit/036a4a1ddf2c0e7a782dca6e083c6c53e5d90321))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reverting update to use new API ([#274](https://github.com/aws-actions/configure-aws-credentials/issues/274)) ([a78fcb0](https://github.com/aws-actions/configure-aws-credentials/commit/a78fcb01f76c8c5c3b05ab82718a6f7919fc0269)), closes [#270](https://github.com/aws-actions/configure-aws-credentials/issues/270)
|
||||
* typo "charcters" in README.md ([#241](https://github.com/aws-actions/configure-aws-credentials/issues/241)) ([c48e1b5](https://github.com/aws-actions/configure-aws-credentials/commit/c48e1b578416f3457ccf757c47385df5c054d23f))
|
||||
* Updated token retrieval to use new API ([#270](https://github.com/aws-actions/configure-aws-credentials/issues/270)) ([20ce4e5](https://github.com/aws-actions/configure-aws-credentials/commit/20ce4e5ba1de2e753d034b5415075a8767d64d4d))
|
||||
|
||||
### [1.5.11](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.10...v1.5.11) (2021-07-19)
|
||||
|
||||
### [1.5.10](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.9...v1.5.10) (2021-06-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skips session tagging ([#209](https://github.com/aws-actions/configure-aws-credentials/issues/209)) ([4900858](https://github.com/aws-actions/configure-aws-credentials/commit/4900858c22f8f07170e3032d4105f99c2aafa9e7))
|
||||
|
||||
### [1.5.9](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.8...v1.5.9) (2021-05-10)
|
||||
|
||||
### [1.5.8](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.7...v1.5.8) (2021-03-02)
|
||||
|
||||
### [1.5.7](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.6...v1.5.7) (2021-02-08)
|
||||
|
||||
### [1.5.6](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.5...v1.5.6) (2021-01-26)
|
||||
|
||||
### [1.5.5](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.4...v1.5.5) (2020-11-24)
|
||||
|
||||
### [1.5.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.3...v1.5.4) (2020-10-29)
|
||||
|
||||
### [1.5.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.2...v1.5.3) (2020-10-05)
|
||||
|
||||
### [1.5.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.1...v1.5.2) (2020-08-25)
|
||||
|
||||
### [1.5.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.5.0...v1.5.1) (2020-08-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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)
|
||||
|
||||
## [1.5.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.4...v1.5.0) (2020-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.4.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.3...v1.4.4) (2020-07-17)
|
||||
|
||||
### [1.4.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.2...v1.4.3) (2020-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.4.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.1...v1.4.2) (2020-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.4.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.4.0...v1.4.1) (2020-06-09)
|
||||
|
||||
## [1.4.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.5...v1.4.0) (2020-06-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.3.5](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.4...v1.3.5) (2020-05-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.3.4](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.3...v1.3.4) (2020-05-18)
|
||||
|
||||
### [1.3.3](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.2...v1.3.3) (2020-04-02)
|
||||
|
||||
### [1.3.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.1...v1.3.2) (2020-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.3.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.3.0...v1.3.1) (2020-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
## [1.3.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.2.0...v1.3.0) (2020-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
## [1.2.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.2...v1.2.0) (2020-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
* 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))
|
||||
* 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))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
### [1.1.2](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.1...v1.1.2) (2020-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change sanitization character from '*' to '_' ([55f6a14](https://github.com/aws-actions/configure-aws-credentials/commit/55f6a14016cb47190b15751dcce450441bba35e3))
|
||||
|
||||
### [1.1.1](https://github.com/aws-actions/configure-aws-credentials/compare/v1.1.0...v1.1.1) (2020-02-07)
|
||||
|
||||
## [1.1.0](https://github.com/aws-actions/configure-aws-credentials/compare/v1.0.1...v1.1.0) (2020-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
* Build and integ test scripts for pipeline ([52bc82a](https://github.com/aws-actions/configure-aws-credentials/commit/52bc82a29bb08f2f8f87a92f380149945eb8d6f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* create workflows dir in integ-tests ([3de962e](https://github.com/aws-actions/configure-aws-credentials/commit/3de962edc9fe1169ff6032bef94b934be53211d1))
|
||||
* remove buildspecs ([260c6cc](https://github.com/aws-actions/configure-aws-credentials/commit/260c6cc0ed07cbd8ff2a7a74066ad9f67ecc82f7))
|
||||
* remove release script ([1436a16](https://github.com/aws-actions/configure-aws-credentials/commit/1436a160af84c3a086a812f98daf457a042274cb))
|
||||
* resolve commit ID in integ test script ([77f2df7](https://github.com/aws-actions/configure-aws-credentials/commit/77f2df7a41882637145683bb1acf60bb04fddc24))
|
||||
* 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))
|
||||
* 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))
|
||||
@@ -1,8 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
This project has adopted the
|
||||
[Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For
|
||||
more information see the
|
||||
[Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
|
||||
<opensource-codeofconduct@amazon.com> with any additional questions or
|
||||
comments.
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Thank you for your interest in contributing to our project. Whether it's a bug
|
||||
report, new feature, correction, or additional documentation, we greatly value
|
||||
feedback and contributions from our community.
|
||||
|
||||
Please read through this document before submitting any issues or pull requests
|
||||
to ensure we have all the necessary information to effectively respond to your
|
||||
bug report or contribution.
|
||||
|
||||
## Reporting Bugs/Feature Requests
|
||||
|
||||
We welcome you to use the GitHub issue tracker to report bugs or suggest
|
||||
features.
|
||||
|
||||
When filing an issue, please check [existing open][open-issues],
|
||||
or [recently closed][closed-issues], issues to make sure somebody
|
||||
else hasn't already reported the issue.
|
||||
|
||||
[open-issues]: https://github.com/aws-actions/configure-aws-credentials/issues
|
||||
[closed-issues]: https://github.com/aws-actions/configure-aws-credentials/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20
|
||||
|
||||
Please try to include as much information as you can. Details like
|
||||
these are incredibly useful:
|
||||
|
||||
- A reproducible test case or series of steps
|
||||
- The version of our code being used
|
||||
- Any modifications you've made relevant to the bug
|
||||
- Anything unusual about your environment or deployment
|
||||
|
||||
## Contributing via Pull Requests
|
||||
|
||||
Contributions via pull requests are much appreciated. Before sending us a pull
|
||||
request, please ensure that:
|
||||
|
||||
1. You are working against the latest source on the _main_ branch.
|
||||
2. You check existing open, and recently merged, pull requests to make sure
|
||||
someone else hasn't addressed the problem already.
|
||||
3. You open an issue to discuss any significant work - we would hate for your
|
||||
time to be wasted.
|
||||
|
||||
To send us a pull request, please:
|
||||
|
||||
1. Fork the repository.
|
||||
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.
|
||||
3. Ensure local tests pass.
|
||||
4. Commit to your fork using clear commit messages.
|
||||
5. Send us a pull request, answering any default questions in the pull request
|
||||
interface.
|
||||
6. Pay attention to any automated CI failures reported in the pull request, and
|
||||
stay involved in the conversation.
|
||||
|
||||
GitHub provides additional document on
|
||||
[forking a repository](https://help.github.com/articles/fork-a-repo/) and
|
||||
[creating a pull request][create-pr].
|
||||
|
||||
[create-pr]:
|
||||
https://help.github.com/articles/creating-a-pull-request/
|
||||
|
||||
## Finding contributions to work on
|
||||
|
||||
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.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the
|
||||
[Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For
|
||||
more information see the
|
||||
[Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
|
||||
<opensource-codeofconduct@amazon.com> with any additional questions or
|
||||
comments.
|
||||
|
||||
## Security issue notifications
|
||||
|
||||
If you discover a potential security issue in this project we ask that you
|
||||
notify AWS/Amazon Security via our
|
||||
[vulnerability reporting page][vuln-report].
|
||||
|
||||
[vuln-report]:
|
||||
http://aws.amazon.com/security/vulnerability-reporting/
|
||||
Please do **not** create a public github issue.
|
||||
|
||||
## Automated Tools
|
||||
|
||||
The use of AI tooling for assisted development work is accepted and encouraged
|
||||
in this repository, but due to the volume of submissions we ask that you observe
|
||||
the following rules:
|
||||
|
||||
- All issue and pull request submissions to this repository that are sourced by
|
||||
AI must first be reviewed by a human before submitting to the repository.
|
||||
Items reviewed in this way must include a statement like "generated by AI
|
||||
tools, and reviewed by [person]"
|
||||
- Please ensure that your submissions are actually improvements. While we are
|
||||
grateful for any proposed fixes, even if they are very small, behavior that
|
||||
looks like creating noisy PRs or artificially inflating submission counts
|
||||
is not acceptable.
|
||||
- We may close issues or pull requests, or limit your ability to interact with
|
||||
this repository, for behavior that in our estimation violates these rules or
|
||||
any of the other rules in this repository's
|
||||
[Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Licensing
|
||||
|
||||
See the
|
||||
[LICENSE](https://github.com/aws-actions/configure-aws-credentials/blob/main/LICENSE)
|
||||
file for our project's licensing. We will ask you to confirm the licensing of
|
||||
your contribution.
|
||||
|
||||
We may ask you to sign a
|
||||
[Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement)
|
||||
for larger changes.
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2019-2022 Amazon.com, Inc. or its affiliates
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,840 +0,0 @@
|
||||
# Configure AWS Credentials
|
||||
|
||||
Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
||||
[AWS Secrets Manager][secretsmanager].
|
||||
|
||||
[secretsmanager]: https://github.com/aws-actions/aws-secretsmanager-get-secrets
|
||||
|
||||
## Quick Start (OIDC, recommended)
|
||||
|
||||
1. Create an IAM Identity Provider in your AWS account for GitHub OIDC. (See
|
||||
[OIDC configuration](#oidc-configuration-details) below for details.)
|
||||
2. Create an IAM Role in your AWS account with a trust policy that allows GitHub
|
||||
Actions to assume it. (Expand the sections below) <details>
|
||||
<summary>GitHub OIDC Trust Policy</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
||||
"token.actions.githubusercontent.com:sub": "repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:ref:refs/heads/<GITHUB_BRANCH>"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Note: if you are running in a GitHub environment based workflow, the value
|
||||
for the Sub claim will be different, in the form of
|
||||
`repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:environment:<ENVIRONMENT_NAME>`.
|
||||
Adjust the trust policy accordingly if you are using environment-based
|
||||
workflows.
|
||||
|
||||
3. Attach permissions to the IAM Role that allow it to access the AWS resources
|
||||
you need.
|
||||
4. Add the following to your GitHub Actions workflow: <details>
|
||||
<summary>Example Workflow</summary>
|
||||
|
||||
```yaml
|
||||
# Need ID token write permission to use OIDC
|
||||
permissions:
|
||||
id-token: write
|
||||
jobs:
|
||||
run_job_with_aws:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
role-to-assume: <Role ARN you created in step 2>
|
||||
aws-region: <AWS Region you want to use>
|
||||
- name: Additional steps
|
||||
run: |
|
||||
# Your commands that require AWS credentials
|
||||
aws sts get-caller-identity
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
That's it! Your GitHub Actions workflow can now access AWS resources using the
|
||||
IAM Role you created. Other authentication scenarios are also supported (see
|
||||
below).
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
- Use temporary credentials when possible. OIDC is recommended because it
|
||||
provides temporary credentials and it's easy to set up.
|
||||
- Do not store credentials in your repository's code. Consider using
|
||||
[git-secrets](https://github.com/awslabs/git-secrets) to prevent committing
|
||||
secrets to your repository.
|
||||
- [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege)
|
||||
to your workflows. Grant only those permissions that are necessary for the
|
||||
workflow to run.
|
||||
- [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log)
|
||||
of the credentials used in workflows.
|
||||
- Periodically rotate any long-lived credentials that you use.
|
||||
- Store sensitive information in a secure way, such as using
|
||||
[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) or [GitHub
|
||||
Secrets][gh-secrets].
|
||||
- Be especially careful about running Actions in non-ephemeral environments, or
|
||||
[triggering workflows on `pull_request_target`](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target)
|
||||
events.
|
||||
|
||||
[gh-secrets]:
|
||||
https://docs.github.com/en/actions/security-guides/encrypted-secrets
|
||||
|
||||
## Non-OIDC Authentication Options
|
||||
|
||||
This action supports five different authentication methods that are configured
|
||||
by specifying different inputs.
|
||||
|
||||
1. Use a `core.getIDToken()` call to authenticate via OIDC.
|
||||
2. Re-export existing long-lived IAM credentials (access key ID and secret
|
||||
access key) as environment variables.
|
||||
3. Use static credentials stored in GitHub Secrets to fetch temporary
|
||||
credentials via STS AssumeRole.
|
||||
4. Use a Web Identity Token to fetch temporary credentials via STS
|
||||
AssumeRoleWithWebIdentity.
|
||||
5. Use credentials stored in the Action environment to fetch temporary
|
||||
credentials via STS AssumeRole.
|
||||
|
||||
Because we use the AWS JavaScript SDK, we always will use the [credential
|
||||
resolution flow for Node.js][cred-resolution].
|
||||
|
||||
[cred-resolution]:
|
||||
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
|
||||
|
||||
Depending on your inputs, the action might override parts of this flow.
|
||||
|
||||
<details>
|
||||
<summary>Inputs and their effects on the credential resolution flow</summary>
|
||||
|
||||
| **Identity Used** | `aws-access-key-id` | `role-to-assume` | `web-identity-token-file` | `role-chaining` |
|
||||
| --------------------------------------- | ------------------- | ---------------- | ------------------------- | --------------- |
|
||||
| [✅ Recommended] GitHub OIDC | | ✔ | | |
|
||||
| IAM User (no AssumeRole) | ✔ | | | |
|
||||
| AssumeRole using static IAM credentials | ✔ | ✔ | | |
|
||||
| AssumeWithWebIdentity use a token file | | ✔ | ✔ | |
|
||||
| AssumeRole using existing credentials | | ✔ | | ✔ |
|
||||
|
||||
_Note: `role-chaining` is not always necessary to use existing credentials. If
|
||||
you're getting a "Credentials loaded by the SDK do not match" error, try
|
||||
enabling this option._
|
||||
|
||||
</details>
|
||||
|
||||
Additionally, **`aws-region`** is always required.
|
||||
|
||||
_Note: If you use GitHub Enterprise Server, you may need to adjust examples here
|
||||
to match your environment._
|
||||
|
||||
## Additional Options
|
||||
|
||||
### Options
|
||||
|
||||
The options list can be expanded below. See [action.yml](./action.yml) for more
|
||||
detail.
|
||||
|
||||
<details>
|
||||
<summary>Options list and descriptions</summary>
|
||||
|
||||
| Option | Description | Required |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| aws-region | Which AWS region to use | Yes |
|
||||
| aws-profile | Name of the AWS profile to configure. When provided, credentials are written to `~/.aws/credentials` and `~/.aws/config` files. This enables configuring multiple profiles in a single workflow. Name cannot contain whitespace, square brackets, or slashes. When set, credentials will not be exported as environment variables unless `output-env-credentials` is manually set to true. | No |
|
||||
| overwrite-aws-profile | Overwrite the given AWS profile if it already exists. When set to false or not set, an error will be thrown if the profile already exists. | No |
|
||||
| role-to-assume | Role for which to fetch credentials. Only required for some authentication types. | No |
|
||||
| aws-access-key-id | AWS access key to use. Only required for some authentication types. | No |
|
||||
| aws-secret-access-key | AWS secret key to use. Only required for some authentication types. | No |
|
||||
| aws-session-token | AWS session token to use. Used in uncommon authentication scenarios. | No |
|
||||
| role-chaining | Use existing credentials from the environment to assume a new role. | No |
|
||||
| audience | The JWT audience when using OIDC. Used in non-default AWS partitions, like China regions. | No |
|
||||
| http-proxy | An HTTP proxy to use for API calls. | No |
|
||||
| mask-aws-account-id | AWS account IDs are not considered secret. Setting this will hide account IDs from output anyway. | No |
|
||||
| role-duration-seconds | The assumed role duration in seconds, if assuming a role. Defaults to 1 hour (3600 seconds). Acceptable values range from 15 minutes (900 seconds) to 12 hours (43200 seconds). | No |
|
||||
| role-external-id | The external ID of the role to assume. Only needed if your role requires it. | No |
|
||||
| role-session-name | Defaults to "GitHubActions", but may be changed if required. | No |
|
||||
| role-skip-session-tagging | Skips session tagging if set. | No |
|
||||
| transitive-tag-keys | Define a list of transitive tag keys to pass when assuming a role. | No |
|
||||
| custom-tags | Additional tags to apply to the assumed role session. Must be a JSON object provided as a string. Custom tags are not usable with OIDC or web identity token authentication. | No |
|
||||
| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | No |
|
||||
| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No |
|
||||
| output-credentials | When set, outputs fetched credentials as action step output. (Outputs aws-access-key-id, aws-secret-access-key, aws-session-token, aws-account-id, authenticated-arn, and aws-expiration). Defaults to false. | No |
|
||||
| output-env-credentials | When set, outputs fetched credentials as environment variables (AWS_REGION, AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, and AWS_PROFILE (if profile option is used)). Defaults to true when `aws-profile` is not set, and false when `aws-profile` is set. Set to false to avoid setting env variables. (NOTE: Setting to false will prevent aws-account-id from being exported as a step output). | No |
|
||||
| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No |
|
||||
| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No |
|
||||
| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No |
|
||||
| special-characters-workaround | Uncommonly, some environments cannot tolerate special characters in a secret key. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. | No |
|
||||
| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | No |
|
||||
| allowed-account-ids | A comma-delimited list of expected AWS account IDs. The action will fail if we receive credentials for the wrong account. | No |
|
||||
| force-skip-oidc | When set, the action will skip using GitHub OIDC provider even if the id-token permission is set. | No |
|
||||
| action-timeout-s | Global timeout for the action in seconds. If set to a value greater than 0, the action will fail if it takes longer than this time to complete. | No |
|
||||
| no-proxy | Hosts to skip for the proxy configuration. | No |
|
||||
| sts-endpoint | Custom STS endpoint URL. Use this to point to an STS-compatible API (e.g. MinIO, LocalStack) instead of the default AWS STS endpoint for the region. | No |
|
||||
|
||||
</details>
|
||||
|
||||
#### Adjust the retry mechanism
|
||||
|
||||
You can configure retry settings for if the STS call fails. By default, we retry
|
||||
with exponential backoff `12` times. You can disable this behavior altogether by
|
||||
setting the `disable-retry` input to `true`, or you can configure the number of
|
||||
times it retries with the `retry-max-attempts` input.
|
||||
|
||||
#### Mask account ID
|
||||
|
||||
Your account ID is not masked by default in workflow logs. You can set the
|
||||
`mask-aws-account-id` input to `true` to mask your account ID in workflow logs
|
||||
if desired.
|
||||
|
||||
#### Unset current credentials
|
||||
|
||||
Sometimes, existing credentials in your runner can get in the way of the
|
||||
intended outcome. You can set the `unset-current-credentials` input to `true` to
|
||||
work around this issue.
|
||||
|
||||
#### Configure named AWS profiles
|
||||
|
||||
By default, this action exports credentials as environment variables
|
||||
(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.). However, you can use the
|
||||
`aws-profile` input to configure named AWS profiles. When `aws-profile` is
|
||||
provided, credentials are written to `~/.aws/credentials` and `~/.aws/config`
|
||||
files (which are created if they don't already exist). The default locations of
|
||||
these files will be overridden if the `AWS_SHARED_CREDENTIALS_FILE` and
|
||||
`AWS_CONFIG_FILE` environment variables are present.
|
||||
|
||||
Profile names may not contain whitespace, square brackets, or forward or
|
||||
backslashes.
|
||||
|
||||
Writing to a profile will prevent credentials being written to the environment
|
||||
by default. Use `output-env-credentials: true` if you would like the credentials
|
||||
to also be exported as environment variables.
|
||||
|
||||
By default, the action will not overwrite existing profiles. If you would like
|
||||
to overwrite a profile, set the `overwrite-aws-profile` input to `true`.
|
||||
|
||||
_Note: When writing profiles, the action will preserve existing profile sections
|
||||
in the credentials and config files. However, comments in these files will not
|
||||
be preserved._
|
||||
|
||||
_Caution: Writing to the AWS configuration file means that credentials will
|
||||
persist in the execution environment even after the action cleanup step. Use
|
||||
extreme care to ensure that this is safe in your environment and you do not leak
|
||||
valid credentials unintentionally. Writing to configuration files is intended
|
||||
for unusual authentication scenarios._
|
||||
|
||||
For using profiles with static IAM User Credentials or when using one role to
|
||||
assume another, role chaining is needed:
|
||||
|
||||
<details>
|
||||
|
||||
If using static credentials, it's necessary to set `role-chaining: true` and
|
||||
specify the profile name as an environment variable in the job step:
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-role
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-profile: MyProfile1
|
||||
role-chaining: true
|
||||
env:
|
||||
AWS_PROFILE: MyProfile1
|
||||
```
|
||||
|
||||
If you are using one role to assume another while using profiles, the subsequent
|
||||
steps must set `role-chaining: true` and specify the prior profile's name as
|
||||
step environment variables:
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-first-role
|
||||
aws-profile: firstRoleInChain
|
||||
|
||||
- name: assume second role
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||
role-chaining: true
|
||||
aws-profile: secondRoleInChain
|
||||
env:
|
||||
AWS_PROFILE: firstRoleInChain
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
See the [Examples](#examples) section for more usage examples.
|
||||
|
||||
#### Skip the cleanup
|
||||
|
||||
By default, this action runs a post-job cleanup step that removes credentials
|
||||
from the environment. To skip this step, set the `AWS_SKIP_CLEANUP_STEP`
|
||||
environment variable to `true`:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
AWS_SKIP_CLEANUP_STEP: "true"
|
||||
```
|
||||
|
||||
#### Use an HTTP proxy
|
||||
|
||||
If need use an HTTP proxy you can set it in the action manually. Additionally
|
||||
this action will always consider the `HTTP_PROXY` environment variable.
|
||||
|
||||
<details>
|
||||
<summary>Proxy configuration</summary>
|
||||
|
||||
Manually configured proxy:
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: my-github-actions-role
|
||||
http-proxy: "http://companydomain.com:3128"
|
||||
```
|
||||
|
||||
Proxy configured in the environment variable:
|
||||
|
||||
```bash
|
||||
# Your environment configuration
|
||||
HTTP_PROXY="http://companydomain.com:3128"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Special characters in AWS_SECRET_ACCESS_KEY
|
||||
|
||||
Some edge cases are unable to properly parse an `AWS_SECRET_ACCESS_KEY` if it
|
||||
contains special characters. For more information, please see the [AWS CLI
|
||||
documentation][aws-cli-troubleshooting].
|
||||
|
||||
[aws-cli-troubleshooting]:
|
||||
https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-troubleshooting.html#tshoot-signature-does-not-match
|
||||
|
||||
If you set the `special-characters-workaround` option, this action will
|
||||
continually retry fetching credentials until we get one that does not have
|
||||
special characters. This option overrides the `disable-retry` and
|
||||
`retry-max-attempts` options. We recommend that you do not enable this option
|
||||
unless required, because retrying APIs infinitely until they succeed is not best
|
||||
practice.
|
||||
|
||||
## Session Naming and Policies
|
||||
|
||||
The default session name is "GitHubActions", and you can modify it by specifying
|
||||
the desired name in `role-session-name`.
|
||||
|
||||
_Note: you might find it helpful to set the `role-session-name` to
|
||||
`${{ github.run_id }}` so as to clarify in audit logs which AWS actions were
|
||||
performed by which workflow run._
|
||||
|
||||
The session will be tagged with the following tags: (Refer to [GitHub's
|
||||
documentation for `GITHUB_` environment variable definitions][gh-env-vars])
|
||||
|
||||
[gh-env-vars]:
|
||||
https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
||||
|
||||
**Default tags** are always emitted when session tags are used.
|
||||
|
||||
| Key | Value |
|
||||
| ---------- | ----------------- |
|
||||
| GitHub | "Actions" |
|
||||
| Repository | GITHUB_REPOSITORY |
|
||||
| Workflow | GITHUB_WORKFLOW |
|
||||
| Action | GITHUB_ACTION |
|
||||
| Actor | GITHUB_ACTOR |
|
||||
| Commit | GITHUB_SHA |
|
||||
| Branch | GITHUB_REF |
|
||||
|
||||
**Droppable tags** are automatically added to the set of default session tags.
|
||||
If the session tags exceed the [packed size limit][packed-size-limit], these
|
||||
tags will be dropped, and the AssumeRole call will be retried. If it still
|
||||
fails, the action will error out. (It is difficult to predict the packed size
|
||||
before making the call, as session tags and session policies are compressed into
|
||||
a binary format as part of the call.)
|
||||
|
||||
[packed-size-limit]:
|
||||
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_know
|
||||
|
||||
| Key | Value |
|
||||
| --------------- | ----------------------- |
|
||||
| EventName | GITHUB_EVENT_NAME |
|
||||
| BaseRef | GITHUB_BASE_REF |
|
||||
| HeadRef | GITHUB_HEAD_REF |
|
||||
| RunId | GITHUB_RUN_ID |
|
||||
| Job | GITHUB_JOB |
|
||||
| TriggeringActor | GITHUB_TRIGGERING_ACTOR |
|
||||
|
||||
Tags whose source environment variable is unset are omitted (e.g., `BaseRef` and
|
||||
`HeadRef` are only set on `pull_request` events).
|
||||
|
||||
_Note: all tag values must conform to
|
||||
[the tag requirements][sts-tag-requirements].
|
||||
Values longer than 256 characters will be truncated, and characters outside the
|
||||
allowed set will be replaced with an underscore (`_`)._
|
||||
|
||||
[sts-tag-requirements]:
|
||||
https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html
|
||||
|
||||
The action will use session tagging by default unless you are using OIDC or a
|
||||
Web Identify Token File.
|
||||
|
||||
To [forward session tags to subsequent sessions in a role
|
||||
chain][session-tag-chaining], you can use the `transitive-tag-keys` input to
|
||||
specify the keys of the tags to be passed.
|
||||
|
||||
[session-tag-chaining]:
|
||||
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining
|
||||
|
||||
_Note that all subsequent roles in the chain must have
|
||||
`role-skip-session-tagging` set to `true`_
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
transitive-tag-keys: |
|
||||
Repository
|
||||
Workflow
|
||||
Action
|
||||
Actor
|
||||
```
|
||||
|
||||
### Custom session tags
|
||||
|
||||
You can add custom session tags using the `custom-tags` input, which accepts a
|
||||
JSON object. Custom tags cannot override existing tags. Note that AWS allows a
|
||||
maximum of 50 tags (so you can supply a maximum of 43 custom tags), although it
|
||||
is likely that you will exceed the [packed size limit][packed-size-limit]
|
||||
before you exceed the maximum number of tags.
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
custom-tags: '{"Environment": "Production", "Team": "Platform"}'
|
||||
```
|
||||
|
||||
_Note: custom tags are not supported when using OIDC or web identity token
|
||||
authentication. In those flows, session tags are controlled by the identity
|
||||
provider's token claims._
|
||||
|
||||
### Session policies
|
||||
|
||||
Session policies are not required, but they allow you to limit the scope of the
|
||||
fetched credentials without making changes to IAM roles. You can specify inline
|
||||
session policies right in your workflow file, or refer to an existing managed
|
||||
session policy by its ARN.
|
||||
|
||||
#### Inline session policies
|
||||
|
||||
An IAM policy in stringified JSON format that you want to use as an inline
|
||||
session policy. Depending on preferences, the JSON could be written on a single
|
||||
line.
|
||||
|
||||
<details>
|
||||
<summary>Inline session policy examples</summary>
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
|
||||
```
|
||||
|
||||
Or we can have a nicely formatted JSON as well:
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
inline-session-policy: >-
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid":"Stmt1",
|
||||
"Effect":"Allow",
|
||||
"Action":"s3:List*",
|
||||
"Resource":"*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Managed session policies
|
||||
|
||||
The Amazon Resource Names (ARNs) of the IAM managed policies that you want to
|
||||
use as managed session policies. The policies must exist in the same account as
|
||||
the role.
|
||||
|
||||
<details>
|
||||
<summary>Managed session policy examples</summary>
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||
```
|
||||
|
||||
And we can pass multiple managed policies likes this:
|
||||
|
||||
```yaml
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
managed-session-policies: |
|
||||
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||
arn:aws:iam::aws:policy/AmazonS3OutpostsReadOnlyAccess
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Custom STS endpoint
|
||||
|
||||
Use the `sts-endpoint` input to override the AWS STS endpoint URL. Most users
|
||||
should not set this option and instead let the SDK derive the correct endpoint
|
||||
from the specified region.
|
||||
|
||||
## OIDC Configuration Details
|
||||
|
||||
We recommend using
|
||||
[GitHub's OIDC provider](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services)
|
||||
to get short-lived AWS credentials needed for your actions. When using OIDC, you
|
||||
configure IAM to accept JWTs from GitHub's OIDC endpoint. This action will then
|
||||
create a JWT unique to the workflow run using the OIDC endpoint, and it will use
|
||||
the JWT to assume the specified role with short-term credentials.
|
||||
|
||||
To get this to work
|
||||
|
||||
1. Configure your workflow to use the `id-token: write` permission.
|
||||
2. Configure your audience, if required.
|
||||
3. In your AWS account, configure IAM to trust GitHub's OIDC identity provider.
|
||||
4. Configure an IAM role with appropriate claim limits and permission scope.
|
||||
|
||||
_Note_: Naming your role "GitHubActions" has been reported to not work. See
|
||||
[#953](https://github.com/aws-actions/configure-aws-credentials/issues/953).
|
||||
|
||||
5. Specify that role's ARN when setting up this action.
|
||||
|
||||
### OIDC Audience
|
||||
|
||||
When the JWT is created, an audience needs to be specified. Normally, you would
|
||||
use `sts.amazonaws.com`, and this action uses this by default if you don't
|
||||
specify one. This will work for most cases. Changing the default audience may be
|
||||
necessary when using non-default AWS partitions, such as China regions. You can
|
||||
specify the audience through the `audience` input:
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials for China region audience
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
audience: sts.amazonaws.com.cn
|
||||
aws-region: cn-northwest-1
|
||||
role-to-assume: arn:aws-cn:iam::123456789100:role/my-github-actions-role
|
||||
```
|
||||
|
||||
### Configuring IAM to trust GitHub
|
||||
|
||||
To use GitHub's OIDC provider, you must first set up federation in your AWS
|
||||
account. This involves creating an IAM Identity Provider that trusts GitHub's
|
||||
OIDC endpoint. You can create an IAM Identity Provider in the AWS Management
|
||||
Console by specifying the following details:
|
||||
|
||||
- **Provider Type**: OIDC
|
||||
- **Provider URL**: `https://token.actions.githubusercontent.com`
|
||||
- **Audience**: `sts.amazonaws.com` (or your custom audience if you specified
|
||||
one in the `audience` input)
|
||||
|
||||
Prior versions of this documentation gave instructions for specifying the
|
||||
certificate fingerprint, but this is no longer necessary. The thumbprint, if
|
||||
specified, will be ignored.
|
||||
|
||||
You can also create the IAM Identity Provider using the AWS CLI:
|
||||
|
||||
```bash
|
||||
aws iam create-open-id-connect-provider \
|
||||
--url https://token.actions.githubusercontent.com \
|
||||
--client-id-list sts.amazonaws.com
|
||||
```
|
||||
|
||||
### Claims and scoping permissions
|
||||
|
||||
To align with the Amazon IAM best practice of [granting least
|
||||
privilege][least-privilege], the assume role policy document should contain a
|
||||
[`Condition`](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html)
|
||||
that restricts which workflows can assume the role. Without any condition, any
|
||||
GitHub user or repository could potentially assume the role.
|
||||
|
||||
GitHub provides a number of additional claims in the OIDC token that you can use
|
||||
in your IAM policies to scope down permissions. Early versions of this action
|
||||
only supported the `sub` and `aud` claims, but AWS IAM and GitHub have since
|
||||
added support for `sub` claim customization and a variety of additional
|
||||
claims ([1][gh-blog-oidc], [2][sub-claim-custom]).
|
||||
|
||||
> **Warning:** Avoid `ForAllValues:` in `Allow` statements. These operators
|
||||
> return true when the claim is absent or misspelled, which can lead to
|
||||
> unintended access. Instead, use `StringEquals` or `StringLike` operators to
|
||||
> check for specific claim values.
|
||||
|
||||
[least-privilege]:
|
||||
https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege
|
||||
[gh-blog-oidc]:
|
||||
https://aws.amazon.com/about-aws/whats-new/2026/01/aws-sts-supports-validation-identity-provider-claims/
|
||||
[sub-claim-custom]:
|
||||
https://docs.github.com/en/rest/actions/oidc?apiVersion=2026-03-10
|
||||
|
||||
#### Inspecting the token
|
||||
|
||||
If you aren't sure what claim values your workflow is producing, the
|
||||
[`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger)
|
||||
action will print the decoded JWT payload. Run it in a private repository
|
||||
only — the token itself is short-lived but the claim values may be sensitive.
|
||||
|
||||
See the GitHub [security-hardening guide][gh-oidc-hardening] for further
|
||||
discussion of trust conditions and threat modeling.
|
||||
|
||||
[gh-oidc-hardening]:
|
||||
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
|
||||
|
||||
### Further information about OIDC
|
||||
|
||||
For further information on OIDC and GitHub Actions, please see:
|
||||
|
||||
- [AWS docs: Creating OpenID Connect (OIDC) identity providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html)
|
||||
- [AWS docs: IAM JSON policy elements: Condition](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html)
|
||||
- [GitHub docs: About security hardening with OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
||||
- [GitHub docs: Configuring OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services)
|
||||
- [GitHub changelog: GitHub Actions: Secure cloud deployments with OpenID Connect](https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/)
|
||||
|
||||
## Getting Credentials in AWS Self-Hosted Runners
|
||||
|
||||
If you are running GitHub Actions in a self-hosted runner using an AWS Service
|
||||
(such as Codebuild or EKS) and you have properly configured the service,
|
||||
credentials should be available by default; the AWS CLI will fetch credentials
|
||||
using the AWS_CONTAINER_CREDENTIALS_FULL_URI or
|
||||
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables. However, you may
|
||||
still want to use this action if you need to export those credentials for use
|
||||
with other tools in your workflow. You may also want to use this action in
|
||||
scenarios where you need to use that 'default' role to assume another role.
|
||||
|
||||
To export credentials, simply run the action with `role-to-assume` set to the
|
||||
default role of the container.
|
||||
|
||||
To assume another role from the container's default role, use the
|
||||
`role-chaining: true` flag, so that the action fetches the default credentials
|
||||
from the environment before assuming the other role.
|
||||
|
||||
If you are using EKS Pod Identities and encountering an error related to the
|
||||
packed size of session tags, you must either run the action with
|
||||
`role-skip-session-tagging: true` to disable the tags set by the action, or
|
||||
[disable EKS session tagging][eks-disable-session-tagging] in the EKS settings
|
||||
to disable the tags that are automatically set by the EKS Pod Identity Service.
|
||||
Check the values of the action's session tags and the session tags that are
|
||||
added by EKS so you can keep the set of tags which is more useful to you.
|
||||
|
||||
[eks-disable-session-tagging]:
|
||||
https://docs.aws.amazon.com/eks/latest/userguide/pod-id-abac.html#pod-id-abac-tags
|
||||
|
||||
## Compatibility with non-GitHub Actions environments
|
||||
|
||||
This action has been sucessfully tested with
|
||||
Codeberg/[Forgejo Actions](https://forgejo.org/docs/next/user/actions/overview/)
|
||||
and should be generally compatible with any CI/CD environment that sets the
|
||||
correct `GITHUB_` environment variables. For use with Foregejo, please review
|
||||
the
|
||||
[runner differences with GitHub's action runners][forgejo-gh-differences].
|
||||
|
||||
[forgejo-gh-differences]:
|
||||
https://forgejo.org/docs/next/user/actions/github-actions/#known-list-of-differences
|
||||
The main difference to be aware of is that Forgejo uses the
|
||||
`enable-openid-connect` flag to enable OIDC instad of GitHub's `id-token: write`
|
||||
permission. Forgejo also uses a slightly different syntax for the workflow
|
||||
definition file, omitting some subkeys.
|
||||
|
||||
For OIDC use, the issuer name for the IAM IdP for GitHub Actions is
|
||||
`token.actions.githubusercontent.com`. For Forgejo Actions it is
|
||||
`[foregejo instance url]/api/actions`. As an example, Codeberg would use
|
||||
`codeberg.org/api/actions` as the issuer URL when configuring the IAM Identity
|
||||
Provider. The audience would still be `sts.amazonaws.com` by default.
|
||||
|
||||
## Examples
|
||||
|
||||
### AssumeRoleWithWebIdentity
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||
role-session-name: MySessionName
|
||||
```
|
||||
|
||||
In this example, the Action will load the OIDC token from the GitHub-provided
|
||||
environment variable and use it to assume the role
|
||||
`arn:aws:iam::123456789100:role/my-github-actions-role` with the session name
|
||||
`MySessionName`.
|
||||
|
||||
### AssumeRole with role previously assumed by action in same workflow
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||
role-session-name: MySessionName
|
||||
- name: Configure other AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||
role-session-name: MySessionName
|
||||
role-chaining: true
|
||||
```
|
||||
|
||||
In this two-step example, the first step will use OIDC to assume the role
|
||||
`arn:aws:iam::123456789100:role/my-github-actions-role` just as in the prior
|
||||
example. Following that, a second step will use this role to assume a different
|
||||
role, `arn:aws:iam::987654321000:role/my-second-role`.
|
||||
|
||||
Note that the trust relationship/trust policy of the second role must grant the
|
||||
permissions `sts:AssumeRole` and `sts:TagSession` to the first role. (Or,
|
||||
alternatively, the `TagSession` permission can be omitted if you are using the
|
||||
`role-skip-session-tagging: true` flag for the second step.)
|
||||
|
||||
### AssumeRole with static IAM credentials in repository secrets
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
|
||||
role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }}
|
||||
role-duration-seconds: 1200
|
||||
role-session-name: MySessionName
|
||||
```
|
||||
|
||||
In this example, the secret `AWS_ROLE_TO_ASSUME` contains a string like
|
||||
`arn:aws:iam::123456789100:role/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`.
|
||||
|
||||
### Retrieving credentials from step output
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials 1
|
||||
id: creds
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||
output-credentials: true
|
||||
- name: get caller identity 1
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
- name: Configure AWS Credentials 2
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-2
|
||||
aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }}
|
||||
aws-secret-access-key: ${{ steps.creds.outputs.aws-secret-access-key }}
|
||||
aws-session-token: ${{ steps.creds.outputs.aws-session-token }}
|
||||
role-to-assume: arn:aws:iam::123456789100:role/my-other-github-actions-role
|
||||
- name: get caller identity2
|
||||
run: |
|
||||
aws sts get-caller-identity
|
||||
```
|
||||
|
||||
This example shows that you can reference the fetched credentials as outputs if
|
||||
`output-credentials` is set to true. This example also shows that you can use
|
||||
the `aws-session-token` input in a situation where session tokens are fetched
|
||||
and passed to this action.
|
||||
|
||||
If you only want the credentials available as _step outputs_ and not exported to
|
||||
the environment (for example, on a self-hosted runner where you do not want the
|
||||
assumed-role credentials to shadow an existing EC2 instance profile), pair
|
||||
`output-credentials: true` with `output-env-credentials: false`. In that mode,
|
||||
the action does not run its post-credential SDK-pickup validation step, since
|
||||
the credentials were never written to the environment.
|
||||
|
||||
### Configure multiple AWS profiles in a single workflow
|
||||
|
||||
```yaml
|
||||
- name: Configure AWS Credentials for Dev
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: arn:aws:iam::111111111111:role/dev-role
|
||||
aws-profile: dev
|
||||
|
||||
- name: Configure AWS Credentials for Prod
|
||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
||||
with:
|
||||
aws-region: us-west-2
|
||||
role-to-assume: arn:aws:iam::222222222222:role/prod-role
|
||||
aws-profile: prod
|
||||
|
||||
- name: Use multiple profiles
|
||||
run: |
|
||||
# Check caller identity for dev account
|
||||
aws sts get-caller-identity --profile dev
|
||||
|
||||
# Check caller identity for prod account
|
||||
aws sts get-caller-identity --profile prod
|
||||
|
||||
# Deploy to dev using CDK
|
||||
cdk deploy --profile dev
|
||||
```
|
||||
|
||||
This example shows how to configure multiple named AWS profiles in a single
|
||||
workflow. When using the `aws-profile` input, credentials are written to
|
||||
`~/.aws/credentials` and `~/.aws/config` files, allowing you to reference
|
||||
different profiles using the `--profile` flag with AWS CLI, SDKs, CDK, and other
|
||||
tools.
|
||||
|
||||
Each profile is independent and can authenticate to different AWS accounts or
|
||||
use different roles. This is particularly useful for multi-account deployments
|
||||
or when you need to interact with multiple AWS environments in a single job.
|
||||
|
||||
## Versioning
|
||||
|
||||
Starting with version 5.0.0, this action uses semantic-style release tags and
|
||||
[immutable releases][immutable-releases].
|
||||
|
||||
[immutable-releases]:
|
||||
https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases
|
||||
|
||||
A floating version tag (vN) is also provided for convenience: this tag will move
|
||||
to the latest major version (vN -> vN.2.1, vM -> vM.0.0, etc.).
|
||||
|
||||
## License
|
||||
|
||||
This code is made available under the MIT license.
|
||||
|
||||
## Security Disclosures
|
||||
|
||||
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
|
||||
[on the vulnerability reporting page](https://aws.amazon.com/security/vulnerability-reporting/)
|
||||
or [email AWS security](mailto:aws-security@amazon.com) directly.
|
||||
-2749
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
const { fs } = require('memfs');
|
||||
module.exports = fs;
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
name: '"Configure AWS Credentials" Action for GitHub Actions'
|
||||
description: Configures AWS credentials for use in subsequent steps in a GitHub Action workflow
|
||||
runs:
|
||||
using: node24
|
||||
main: dist/index.js
|
||||
post: dist/cleanup/index.js
|
||||
post-if: env.AWS_SKIP_CLEANUP_STEP != 'true'
|
||||
branding:
|
||||
color: orange
|
||||
icon: cloud
|
||||
inputs:
|
||||
aws-region:
|
||||
description: AWS Region, e.g. us-east-2
|
||||
required: true
|
||||
aws-profile:
|
||||
description: Name of the AWS profile to configure. When provided, credentials are written to ~/.aws/credentials and ~/.aws/config files instead of env variables (unless output-env-credentials is manually set to true). Name cannot contain whitespace, square brackets, or slashes.
|
||||
required: false
|
||||
overwrite-aws-profile:
|
||||
description: Overwrite the given AWS profile if it already exists. Requires aws-profile. When set to false or not set, an error will be thrown if the profile already exists.
|
||||
required: false
|
||||
role-to-assume:
|
||||
description: The Amazon Resource Name (ARN) of the role to assume. Use the provided credentials to assume an IAM role and configure the Actions environment with the assumed role credentials rather than with the provided credentials.
|
||||
required: false
|
||||
aws-access-key-id:
|
||||
description: AWS Access Key ID. Provide this key if you want to assume a role using access keys rather than a web identity token.
|
||||
required: false
|
||||
aws-secret-access-key:
|
||||
description: AWS Secret Access Key. Required if aws-access-key-id is provided.
|
||||
required: false
|
||||
aws-session-token:
|
||||
description: AWS Session Token.
|
||||
required: false
|
||||
web-identity-token-file:
|
||||
description: Use the web identity token file from the provided file system path in order to assume an IAM role using a web identity, e.g. from within an Amazon EKS worker node.
|
||||
required: false
|
||||
role-chaining:
|
||||
description: Use existing credentials from the environment to assume a new role, rather than providing credentials as input.
|
||||
required: false
|
||||
audience:
|
||||
description: The audience to use for the OIDC provider
|
||||
required: false
|
||||
default: sts.amazonaws.com
|
||||
http-proxy:
|
||||
description: Proxy to use for the AWS SDK agent
|
||||
required: false
|
||||
no-proxy:
|
||||
description: Hosts to skip for the proxy configuration
|
||||
required: false
|
||||
mask-aws-account-id:
|
||||
description: Whether to mask the AWS account ID for these credentials as a secret value. By default the account ID will not be masked
|
||||
required: false
|
||||
role-duration-seconds:
|
||||
description: Role duration in seconds. Default is one hour.
|
||||
required: false
|
||||
role-external-id:
|
||||
description: The external ID of the role to assume.
|
||||
required: false
|
||||
role-session-name:
|
||||
description: "Role session name (default: GitHubActions)"
|
||||
required: false
|
||||
role-skip-session-tagging:
|
||||
description: Skip session tagging during role assumption
|
||||
required: false
|
||||
transitive-tag-keys:
|
||||
description: Define a list of transitive tag keys to pass when assuming a role
|
||||
required: false
|
||||
inline-session-policy:
|
||||
description: Define an inline session policy to use when assuming a role
|
||||
required: false
|
||||
managed-session-policies:
|
||||
description: Define a list of managed session policies to use when assuming a role
|
||||
required: false
|
||||
output-credentials:
|
||||
description: Whether to set credentials as step output
|
||||
required: false
|
||||
output-env-credentials:
|
||||
description: Whether to export credentials as environment variables. If you set this to false, you probably want to use output-credentials.
|
||||
required: false
|
||||
unset-current-credentials:
|
||||
description: Whether to unset the existing credentials in your runner. May be useful if you run this action multiple times in the same job
|
||||
required: false
|
||||
disable-retry:
|
||||
description: Whether to disable the retry and backoff mechanism when the assume role call fails. By default the retry mechanism is enabled
|
||||
required: false
|
||||
retry-max-attempts:
|
||||
description: The maximum number of attempts it will attempt to retry the assume role call. By default it will retry 12 times
|
||||
required: false
|
||||
special-characters-workaround:
|
||||
description: Some environments do not support special characters in AWS_SECRET_ACCESS_KEY. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. This option is disabled by default
|
||||
required: false
|
||||
use-existing-credentials:
|
||||
required: false
|
||||
description: When enabled, this option will check if there are already valid credentials in the environment. If there are, new credentials will not be fetched. If there are not, the action will run as normal.
|
||||
allowed-account-ids:
|
||||
required: false
|
||||
description: An option comma-delimited list of expected AWS account IDs. The action will fail if we receive credentials for the wrong account.
|
||||
force-skip-oidc:
|
||||
required: false
|
||||
description: When enabled, this option will skip using GitHub OIDC provider even if the id-token permission is set. This is sometimes useful when using IAM instance credentials.
|
||||
action-timeout-s:
|
||||
required: false
|
||||
description: A global timeout in seconds for the action. When the timeout is reached, the action immediately exits. The default is to run without a timeout.
|
||||
custom-tags:
|
||||
description: Additional tags to apply to the assumed role session. Must be a JSON object provided as a string.
|
||||
required: false
|
||||
sts-endpoint:
|
||||
description: Custom STS endpoint URL. Use this to point to an STS-compatible API (e.g. MinIO, LocalStack) instead of the default AWS STS endpoint for the region.
|
||||
required: false
|
||||
|
||||
outputs:
|
||||
aws-account-id:
|
||||
description: The AWS account ID for the provided credentials
|
||||
aws-access-key-id:
|
||||
description: The AWS access key ID for the provided credentials
|
||||
aws-secret-access-key:
|
||||
description: The AWS secret access key for the provided credentials
|
||||
aws-session-token:
|
||||
description: The AWS session token for the provided credentials
|
||||
aws-expiration:
|
||||
description: The expiration time for the provided credentials
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"lineWidth": 120,
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"performance": {
|
||||
"noDelete": "off"
|
||||
},
|
||||
"correctness": {
|
||||
// Specifying a radix disables interpretation of 0x as a number (needed for backwards compat)
|
||||
"useParseIntRadix": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noExtraBooleanCast": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"trailingCommas": "all",
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteStyle": "single",
|
||||
"bracketSpacing": true,
|
||||
"arrowParentheses": "always"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-19187
File diff suppressed because one or more lines are too long
Vendored
-75890
File diff suppressed because one or more lines are too long
@@ -1,14 +0,0 @@
|
||||
# Examples
|
||||
|
||||
## [federated-setup](./federated-setup/README.md)
|
||||
|
||||
The directory contains templates for setting up the `configure-aws-credentials`
|
||||
federation between your GitHub Organization/repository and your AWS account.
|
||||
|
||||
## [cfn-deploy-example](./cfn-deploy-example/README.md)
|
||||
|
||||
Repository example uses aws-action `configure-aws-credentials` with OIDC
|
||||
federation template
|
||||
[github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml).
|
||||
Example demonstrates a repository that deploys AWS CloudFormation template using
|
||||
cfn-deploy GitHub Action.
|
||||
@@ -1,15 +0,0 @@
|
||||
name: 'compliance'
|
||||
## run ci testing on all push events
|
||||
on: [push]
|
||||
jobs:
|
||||
## Guard rule set
|
||||
sast-guard:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: grolston/guard-action@main
|
||||
with:
|
||||
data_directory: './cloudformation/' ## change to your template directory
|
||||
rule_set: 'FedRAMP-Moderate'
|
||||
show_summary: 'all'
|
||||
output_format: 'single-line-summary'
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
AWS_DEFAULT_OUTPUT: json
|
||||
|
||||
jobs:
|
||||
deploy-cfn:
|
||||
name: deploy
|
||||
runs-on: ubuntu-latest
|
||||
# These permissions are needed to interact with GitHub’s OIDC Token endpoint.
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
## the following creates an ARN based on the values entered into github secrets
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_DEPLOY_ROLE }}
|
||||
role-session-name: myGitHubActions
|
||||
- name: Deploy EC2 Bastion
|
||||
uses: aws-actions/aws-cloudformation-github-deploy@v1.3.0
|
||||
with:
|
||||
name: myEC2bastion
|
||||
## change to path to template in your github repo
|
||||
template: cloudformation/ec2-bastion.yml
|
||||
capabilities: CAPABILITY_IAM, CAPABILITY_NAMED_IAM
|
||||
no-fail-on-empty-changeset: "1"
|
||||
## parameter set in github secrets
|
||||
parameter-overrides: "pVpc=${{ secrets.VPC_ID }},pSubnet=${{ secrets.SUBNET_ID }}"
|
||||
@@ -1,24 +0,0 @@
|
||||
# cfn-deploy example
|
||||
|
||||
Example uses aws-action `configure-aws-credentials` with OIDC federation. Prior
|
||||
to using this example project, the user needs to deploy the
|
||||
[github-actions-oidc-federation-and-role](../federated-setup/github-actions-oidc-federation-and-role.yml)
|
||||
template in the AWS account they want to deploy the CloudFormation template
|
||||
into. Specify the GitHub Organization name, repository name, and the specific
|
||||
branch you want to deploy on.
|
||||
|
||||
Within the [github/workflows](./.github/workflows/) directory there is a
|
||||
[compliance.yml](./.github/workflows/compliance.yml) and a
|
||||
[deploy.yml](./.github/workflows/deploy.yml). The deploy.yml file leverages the
|
||||
aws-action `configure-aws-credentials` and accesses GitHub Action Secrets for
|
||||
some of the variables. The compliance.yml runs static application security
|
||||
testing using cfn-guard.
|
||||
|
||||
To use the example you will need to set the following GitHub Action Secrets:
|
||||
|
||||
| Secret Key | Used With | Description |
|
||||
| --------------- | -------------------------------- | ---------------------------------------- |
|
||||
| AWS_ACCOUNT_ID | configure-aws-credentials | The AWS account ID |
|
||||
| AWS_DEPLOY_ROLE | configure-aws-credentials | The name of the IAM role |
|
||||
| VPC_ID | aws-cloudformation-github-deploy | VPC ID the EC2 Bastion is deployed to |
|
||||
| SUBNET_ID | aws-cloudformation-github-deploy | Subnet ID the EC2 Bastion is deployed to |
|
||||
@@ -1,150 +0,0 @@
|
||||
---
|
||||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
Description: EC2 bastion for latest AWS Linux 2 EC2 deployment
|
||||
Metadata:
|
||||
AWS::CloudFormation::Interface:
|
||||
ParameterGroups:
|
||||
- Label:
|
||||
default: "EC2 Configuration"
|
||||
Parameters:
|
||||
- pTagNameValue
|
||||
- pOperatingSystem
|
||||
- pInstanceType
|
||||
- pVolumeSize
|
||||
- pEbsDeleteOnTermination
|
||||
- Label:
|
||||
default: "Network Configuration"
|
||||
Parameters:
|
||||
- pVpc
|
||||
- pSubnet
|
||||
ParameterLabels:
|
||||
pOperatingSystem:
|
||||
default: "Operating System"
|
||||
pInstanceType:
|
||||
default: "Instance Type"
|
||||
pTagNameValue:
|
||||
default: "EC2 Name"
|
||||
pVolumeSize:
|
||||
default: "Volume Size"
|
||||
pEbsDeleteOnTermination:
|
||||
default: "Delete EBS Volume on Termination"
|
||||
pSubnet:
|
||||
default: "Subnet"
|
||||
pVpc:
|
||||
default: "VPC"
|
||||
Parameters:
|
||||
pSubnet:
|
||||
Description: The subnet to launch the instance in to. It must be part of the VPC chosen above.
|
||||
Type: AWS::EC2::Subnet::Id
|
||||
pVpc:
|
||||
Description: The VPC to launch the EC2 instance in to.
|
||||
Type: AWS::EC2::VPC::Id
|
||||
pOperatingSystem:
|
||||
Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
|
||||
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs"
|
||||
pInstanceType:
|
||||
Description: Desired Instance Size
|
||||
Type: String
|
||||
Default: t3.small
|
||||
AllowedValues:
|
||||
- t3.small
|
||||
- t3.medium
|
||||
- t3.nano
|
||||
pTagNameValue:
|
||||
Description: "Required: Enter the tag name you'd like applied to the instance. Tag Name gives the name to the EC2 instance."
|
||||
Type: String
|
||||
MinLength: 1
|
||||
Default: "myBastion"
|
||||
pVolumeSize:
|
||||
Description:
|
||||
Enter the number of GBs you want your volume to be. The minimum value
|
||||
is 8 GBs
|
||||
Type: Number
|
||||
Default: 50
|
||||
MinValue: 8
|
||||
pEbsDeleteOnTermination:
|
||||
Description: "Specify if the EBS volume should be deleted if EC2 is deleted."
|
||||
Type: String
|
||||
Default: true
|
||||
AllowedValues:
|
||||
- true
|
||||
- false
|
||||
Rules:
|
||||
SubnetInVPC:
|
||||
Assertions:
|
||||
- Assert: !EachMemberIn
|
||||
- !ValueOfAll
|
||||
- AWS::EC2::Subnet::Id
|
||||
- VpcId
|
||||
- !RefAll "AWS::EC2::VPC::Id"
|
||||
AssertDescription: All subnets must in the VPC
|
||||
Resources:
|
||||
rSecurityGroupDefault:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: !Sub "Default SG for SC Product ${pTagNameValue} "
|
||||
VpcId: !Ref pVpc
|
||||
SecurityGroupEgress:
|
||||
- Description: Outbound unrestricted traffic
|
||||
IpProtocol: "-1"
|
||||
CidrIp: 0.0.0.0/0
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Ref pTagNameValue
|
||||
rLinuxEc2:
|
||||
Type: AWS::EC2::Instance
|
||||
Metadata:
|
||||
guard:
|
||||
SuppressedRules:
|
||||
- 'EC2_INSTANCE_DETAILED_MONITORING_ENABLED'
|
||||
Properties:
|
||||
ImageId: !Ref pOperatingSystem
|
||||
IamInstanceProfile: !Ref rec2InstanceProfile
|
||||
Monitoring: false
|
||||
InstanceType: !Ref pInstanceType
|
||||
EbsOptimized: true
|
||||
SourceDestCheck: true
|
||||
SubnetId: !Ref pSubnet
|
||||
SecurityGroupIds:
|
||||
- !Ref rSecurityGroupDefault
|
||||
BlockDeviceMappings:
|
||||
- DeviceName: "/dev/xvda"
|
||||
Ebs:
|
||||
VolumeSize: !Ref pVolumeSize
|
||||
DeleteOnTermination: !Ref pEbsDeleteOnTermination
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Ref pTagNameValue
|
||||
UserData:
|
||||
Fn::Base64:
|
||||
yum update -y
|
||||
## Instance Profiles
|
||||
## EC2 IAM Roles
|
||||
rEc2Role:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: !Sub "ec2-role-${AWS::StackName}"
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: [ec2.amazonaws.com]
|
||||
Action: ['sts:AssumeRole']
|
||||
Path: /
|
||||
ManagedPolicyArns:
|
||||
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
|
||||
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy'
|
||||
rec2InstanceProfile:
|
||||
Type: AWS::IAM::InstanceProfile
|
||||
Properties:
|
||||
InstanceProfileName: !Sub "ec2-profile-${AWS::StackName}"
|
||||
Path: /
|
||||
Roles:
|
||||
- !Ref rEc2Role
|
||||
Outputs:
|
||||
oLinuxEc2InstanceId:
|
||||
Description: Resource ID of the newly created EC2 instance
|
||||
Value: !Ref rLinuxEc2
|
||||
oLinuxEc2PrivateIP:
|
||||
Description: Private IP Address for EC2
|
||||
Value: !GetAtt rLinuxEc2.PrivateIp
|
||||
@@ -1,11 +0,0 @@
|
||||
# federated-setup
|
||||
|
||||
## [github-action-oidc-federation](./github-actions-oidc-federation.yml)
|
||||
|
||||
Setup of the OIDC federation between your GitHub Organization/repository and
|
||||
your AWS account.
|
||||
|
||||
## [github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml)
|
||||
|
||||
Setup of the OIDC federation between your GitHub Organization/repository and
|
||||
your AWS account along with a role that only executes on specific branch.
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
Description: Github Actions configuration - OIDC IAM IdP and associated role CI/CD
|
||||
|
||||
Parameters:
|
||||
|
||||
GitHubOrganization:
|
||||
Type: String
|
||||
Description: This is the root organization or personal account where repos are stored (Case Sensitive)
|
||||
|
||||
RepositoryName:
|
||||
Type: String
|
||||
Description: The repo(s) these roles will have access to. (Use * for all org or personal repos)
|
||||
Default: "*"
|
||||
|
||||
BranchName:
|
||||
Type: String
|
||||
Description: Name of the git branch to to trust. (Use * for all branches)
|
||||
Default: "*"
|
||||
|
||||
RoleName:
|
||||
Type: String
|
||||
Description: Name the Role
|
||||
|
||||
UseExistingProvider:
|
||||
Type: String
|
||||
Description: "Only one GitHub Provider can exists. Choose yes if one is already present in account"
|
||||
Default: "no"
|
||||
AllowedValues:
|
||||
- "yes"
|
||||
- "no"
|
||||
|
||||
Conditions:
|
||||
|
||||
CreateProvider: !Equals ["no", !Ref UseExistingProvider]
|
||||
|
||||
Resources:
|
||||
|
||||
IdpGitHubOidc:
|
||||
Type: AWS::IAM::OIDCProvider
|
||||
Condition: CreateProvider
|
||||
Properties:
|
||||
Url: https://token.actions.githubusercontent.com
|
||||
ClientIdList:
|
||||
- sts.amazonaws.com
|
||||
- !Sub https://github.com/${GitHubOrganization}/${RepositoryName}
|
||||
ThumbprintList:
|
||||
- 6938fd4d98bab03faadb97b34396831e3780aea1
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Sub ${RoleName}-OIDC-Provider
|
||||
|
||||
RoleGithubActions:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: !Ref RoleName
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action: sts:AssumeRoleWithWebIdentity
|
||||
Principal:
|
||||
Federated: !If
|
||||
- CreateProvider
|
||||
- !Ref IdpGitHubOidc
|
||||
- !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com
|
||||
Condition:
|
||||
StringLike:
|
||||
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrganization}/${RepositoryName}:ref:refs/heads/${BranchName}
|
||||
ManagedPolicyArns:
|
||||
## edit the managed policy to give least privileges
|
||||
- !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
|
||||
|
||||
Outputs:
|
||||
|
||||
IdpGitHubOidc:
|
||||
Condition: CreateProvider
|
||||
Description: "ARN of Github OIDC Provider"
|
||||
Value: !GetAtt IdpGitHubOidc.Arn
|
||||
|
||||
RoleGithubActionsARN:
|
||||
Description: "CICD Role for GitHub Actions"
|
||||
Value: !GetAtt RoleGithubActions.Arn
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
Description: Github Actions configuration - OIDC IAM IdP Federation
|
||||
|
||||
Parameters:
|
||||
|
||||
GitHubOrganization:
|
||||
Type: String
|
||||
Description: This is the root organization or personal account where repos are stored (Case Sensitive)
|
||||
Default: ""
|
||||
|
||||
RepositoryName:
|
||||
Type: String
|
||||
Description: The repo(s) these roles will have access to. (Use * for all org or personal repos)
|
||||
Default: "*"
|
||||
|
||||
RoleName:
|
||||
Type: String
|
||||
Description: Name the Role
|
||||
Default: ""
|
||||
|
||||
|
||||
Resources:
|
||||
|
||||
IdpGitHubOidc:
|
||||
Type: AWS::IAM::OIDCProvider
|
||||
Properties:
|
||||
Url: https://token.actions.githubusercontent.com
|
||||
ClientIdList:
|
||||
- sts.amazonaws.com
|
||||
- !Sub https://github.com/${GitHubOrganization}/${RepositoryName}
|
||||
ThumbprintList:
|
||||
- 6938fd4d98bab03faadb97b34396831e3780aea1
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Sub ${RoleName}-OIDC-Provider
|
||||
|
||||
|
||||
Outputs:
|
||||
|
||||
IdpGitHubOidc:
|
||||
Description: "ARN of Github OIDC Provider"
|
||||
Value: !GetAtt IdpGitHubOidc.Arn
|
||||
Generated
-9206
File diff suppressed because it is too large
Load Diff
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"name": "configure-aws-credentials",
|
||||
"description": "A GitHub Action to configure AWS credentials",
|
||||
"version": "6.2.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
||||
"lint:fix": "biome check --write ./src ./test && markdownlint -i node_modules -i CHANGELOG.md -f '**/*.md'",
|
||||
"package": "esbuild src/index.ts --bundle --platform=node --target=node24 --outfile=dist/index.js && esbuild src/cleanup/index.ts --bundle --platform=node --target=node24 --outfile=dist/cleanup/index.js && npm run license",
|
||||
"test": "npm run lint && vitest run && npm run build",
|
||||
"clean": "del-cli coverage test-reports node_modules",
|
||||
"license": "generate-license-file --config ./.generatelicensefile.json"
|
||||
},
|
||||
"author": {
|
||||
"name": "Amazon.com, Inc. or its affiliates",
|
||||
"url": "https://aws.amazon.com",
|
||||
"organization": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.39",
|
||||
"@biomejs/biome": "2.4.15",
|
||||
"@smithy/property-provider": "^4.3.4",
|
||||
"@types/node": "^25.9.1",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"aws-sdk-client-mock": "^4.1.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"generate-license-file": "^4.1.1",
|
||||
"json-schema": "^0.4.0",
|
||||
"markdownlint-cli": "^0.48.0",
|
||||
"memfs": "^4.57.2",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "4.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^3.0.1",
|
||||
"@aws-sdk/client-sts": "^3.1049.0",
|
||||
"@smithy/node-http-handler": "^4.7.3",
|
||||
"proxy-agent": "^8.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
"aws",
|
||||
"github",
|
||||
"github-action"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 16.3.0"
|
||||
},
|
||||
"main": "build/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/aws-actions/configure-aws-credentials",
|
||||
"bugs": {
|
||||
"url": "https://github.com/aws-actions/configure-aws-credentials/issues"
|
||||
},
|
||||
"types": "build/index.d.ts",
|
||||
"private": true
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"packages": {
|
||||
".": {
|
||||
"changelog-path": "CHANGELOG.md",
|
||||
"release-type": "node",
|
||||
"bump-minor-pre-major": false,
|
||||
"bump-patch-for-minor-pre-major": false,
|
||||
"draft": false,
|
||||
"prerelease": false,
|
||||
"exclude-paths": [".github", "dist"]
|
||||
}
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Update the integ test action workflow file with the commit ID to test
|
||||
sed -i "s|aws-actions/configure-aws-credentials@v1|aws-actions/configure-aws-credentials@$GIT_COMMIT_ID|g" test-workflow.yml
|
||||
sed -i "s|BUILD_ID|$CODEBUILD_BUILD_ID|g" test-workflow.yml
|
||||
|
||||
mkdir -p .github/workflows
|
||||
cp test-workflow.yml .github/workflows
|
||||
git add .github/workflows
|
||||
git commit -m "Test commit $GIT_COMMIT_ID"
|
||||
|
||||
# Trigger the action workflow
|
||||
git push origin HEAD:integ-tests
|
||||
|
||||
# Validate that the action workflow succeeds
|
||||
# Exit codes: success = 0; failure = 1; pending = 2; no status = 3
|
||||
while hub ci-status; [ $? -ge 2 ]; do
|
||||
echo "waiting for test workflow to complete..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
hub ci-status
|
||||
@@ -1,112 +0,0 @@
|
||||
import { info } from '@actions/core';
|
||||
import { STSClient } from '@aws-sdk/client-sts';
|
||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
import { ProxyAgent } from 'proxy-agent';
|
||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
||||
import { ProxyResolver } from './ProxyResolver';
|
||||
|
||||
if (!process.env.AWS_EXECUTION_ENV) {
|
||||
process.env.AWS_EXECUTION_ENV = 'GitHubActions';
|
||||
}
|
||||
|
||||
export interface CredentialsClientProps {
|
||||
region?: string;
|
||||
proxyServer?: string;
|
||||
noProxy?: string;
|
||||
stsEndpoint?: string;
|
||||
roleChaining: boolean;
|
||||
}
|
||||
|
||||
export class CredentialsClient {
|
||||
public region?: string;
|
||||
private _stsClient?: STSClient;
|
||||
private readonly requestHandler?: NodeHttpHandler;
|
||||
private readonly stsEndpoint?: string;
|
||||
private roleChaining?: boolean;
|
||||
|
||||
constructor(props: CredentialsClientProps) {
|
||||
if (props.region !== undefined) {
|
||||
this.region = props.region;
|
||||
}
|
||||
if (props.proxyServer) {
|
||||
info('Configuring proxy handler for STS client');
|
||||
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
||||
httpProxy: props.proxyServer,
|
||||
httpsProxy: props.proxyServer,
|
||||
};
|
||||
if (props.noProxy !== undefined) {
|
||||
proxyOptions.noProxy = props.noProxy;
|
||||
}
|
||||
const getProxyForUrl = new ProxyResolver(proxyOptions).getProxyForUrl;
|
||||
const handler = new ProxyAgent({ getProxyForUrl });
|
||||
this.requestHandler = new NodeHttpHandler({
|
||||
httpsAgent: handler,
|
||||
httpAgent: handler,
|
||||
});
|
||||
}
|
||||
if (props.stsEndpoint) {
|
||||
this.stsEndpoint = props.stsEndpoint;
|
||||
}
|
||||
this.roleChaining = props.roleChaining;
|
||||
}
|
||||
|
||||
public get stsClient(): STSClient {
|
||||
if (!this._stsClient || this.roleChaining) {
|
||||
this._stsClient = new STSClient({
|
||||
customUserAgent: buildCustomUserAgent(),
|
||||
...(this.region !== undefined && { region: this.region }),
|
||||
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
|
||||
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
|
||||
});
|
||||
}
|
||||
return this._stsClient;
|
||||
}
|
||||
|
||||
public async validateCredentials(
|
||||
expectedAccessKeyId?: string,
|
||||
roleChaining?: boolean,
|
||||
expectedAccountIds?: string[],
|
||||
) {
|
||||
let credentials: AwsCredentialIdentity;
|
||||
try {
|
||||
credentials = await this.loadCredentials();
|
||||
if (!credentials.accessKeyId) {
|
||||
throw new Error('Access key ID empty after loading credentials');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Credentials could not be loaded, please check your action inputs: ${errorMessage(error)}`);
|
||||
}
|
||||
if (expectedAccountIds && expectedAccountIds.length > 0 && expectedAccountIds[0] !== '') {
|
||||
let callerIdentity: Awaited<ReturnType<typeof getCallerIdentity>>;
|
||||
try {
|
||||
callerIdentity = await getCallerIdentity(this.stsClient);
|
||||
} catch (error) {
|
||||
throw new Error(`Could not validate account ID of credentials: ${errorMessage(error)}`);
|
||||
}
|
||||
if (!callerIdentity.Account || !expectedAccountIds.includes(callerIdentity.Account)) {
|
||||
throw new Error(
|
||||
`The account ID of the provided credentials (${
|
||||
callerIdentity.Account ?? 'unknown'
|
||||
}) does not match any of the expected account IDs: ${expectedAccountIds.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!roleChaining) {
|
||||
const actualAccessKeyId = credentials.accessKeyId;
|
||||
if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) {
|
||||
throw new Error(
|
||||
'Credentials loaded by the SDK do not match the expected access key ID configured by the action',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadCredentials() {
|
||||
const config = {} as { requestHandler?: NodeHttpHandler };
|
||||
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
|
||||
const client = new STSClient(config);
|
||||
return client.config.credentials();
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Based on https://github.com/Rob--W/proxy-from-env/tree/caf8c32301afdac8b5feaf346028bd8240690144
|
||||
// See https://github.com/Rob--W/proxy-from-env/blob/caf8c32301afdac8b5feaf346028bd8240690144/LICENSE
|
||||
import type * as http from 'node:http';
|
||||
|
||||
const DEFAULT_PORTS: Record<string, number> = {
|
||||
http: 80,
|
||||
https: 443,
|
||||
};
|
||||
export interface ProxyOptions {
|
||||
readonly noProxy?: string;
|
||||
readonly httpsProxy?: string;
|
||||
readonly httpProxy?: string;
|
||||
}
|
||||
|
||||
export class ProxyResolver {
|
||||
options: ProxyOptions;
|
||||
constructor(options: ProxyOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
// This method matches the interface expected by 'proxy-agent'. It is an arrow function to bind 'this'.
|
||||
public readonly getProxyForUrl = (url: string, _req: http.ClientRequest): string => {
|
||||
return this.getProxyForUrlOptions(url, this.options);
|
||||
};
|
||||
|
||||
private getProxyForUrlOptions(url: string | URL, options?: ProxyOptions): string {
|
||||
let parsedUrl: URL;
|
||||
try {
|
||||
parsedUrl = typeof url === 'string' ? new URL(url) : url;
|
||||
} catch (_) {
|
||||
return ''; // Don't proxy invalid URLs.
|
||||
}
|
||||
const proto = parsedUrl.protocol.split(':', 1)[0];
|
||||
if (!proto) return ''; // Don't proxy URLs without a protocol.
|
||||
const hostname = parsedUrl.host;
|
||||
const port = parseInt(parsedUrl.port || '') || DEFAULT_PORTS[proto] || 0;
|
||||
|
||||
if (options?.noProxy && !this.shouldProxy(hostname, port, options.noProxy)) return '';
|
||||
if (proto === 'http' && options?.httpProxy) return options.httpProxy;
|
||||
if (proto === 'https' && options?.httpsProxy) return options.httpsProxy;
|
||||
return ''; // No proxy configured for this protocol or unknown protocol
|
||||
}
|
||||
|
||||
private shouldProxy(hostname: string, port: number, noProxy: string): boolean {
|
||||
if (!noProxy) return true;
|
||||
if (noProxy === '*') return false; // Never proxy if wildcard is set.
|
||||
|
||||
return noProxy.split(/[,\s]/).every((proxy) => {
|
||||
if (!proxy) return true; // Skip zero-length hosts.
|
||||
|
||||
const parsedProxy = proxy.match(/^(.+):(\d+)$/);
|
||||
const parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
|
||||
const parsedProxyPort = parsedProxy?.[2] ? parseInt(parsedProxy[2]) : 0;
|
||||
|
||||
if (parsedProxyPort && parsedProxyPort !== port) return true; // Skip if ports don't match.
|
||||
|
||||
if (parsedProxyHostname && !/^[.*]/.test(parsedProxyHostname)) {
|
||||
// No wildcards, so stop proxying if there is an exact match.
|
||||
return hostname !== parsedProxyHostname;
|
||||
}
|
||||
|
||||
let cleanProxyHostname = parsedProxyHostname;
|
||||
if (parsedProxyHostname && parsedProxyHostname.charAt(0) === '*') {
|
||||
// Remove leading wildcard.
|
||||
cleanProxyHostname = parsedProxyHostname.slice(1);
|
||||
}
|
||||
// Stop proxying if the hostname ends with the no_proxy host.
|
||||
return !cleanProxyHostname || !hostname.endsWith(cleanProxyHostname);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import assert from 'node:assert';
|
||||
import path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import type { AssumeRoleCommandInput, STSClient, Tag } from '@aws-sdk/client-sts';
|
||||
import {
|
||||
AssumeRoleCommand,
|
||||
AssumeRoleWithWebIdentityCommand,
|
||||
PackedPolicyTooLargeException,
|
||||
} from '@aws-sdk/client-sts';
|
||||
import type { CredentialsClient } from './CredentialsClient';
|
||||
import { errorMessage, isDefined, readFileUtf8, sanitizeGitHubVariables } from './helpers';
|
||||
|
||||
async function assumeRoleWithOIDC(params: AssumeRoleCommandInput, client: STSClient, webIdentityToken: string) {
|
||||
delete params.Tags;
|
||||
delete params.TransitiveTagKeys;
|
||||
core.info('Assuming role with OIDC');
|
||||
try {
|
||||
const creds = await client.send(
|
||||
new AssumeRoleWithWebIdentityCommand({
|
||||
...params,
|
||||
WebIdentityToken: webIdentityToken,
|
||||
}),
|
||||
);
|
||||
return creds;
|
||||
} catch (error) {
|
||||
throw new Error(`Could not assume role with OIDC: ${errorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assumeRoleWithWebIdentityTokenFile(
|
||||
params: AssumeRoleCommandInput,
|
||||
client: STSClient,
|
||||
webIdentityTokenFile: string,
|
||||
workspace: string,
|
||||
) {
|
||||
core.debug(
|
||||
'webIdentityTokenFile provided. Will call sts:AssumeRoleWithWebIdentity and take session tags from token contents.',
|
||||
);
|
||||
const webIdentityTokenFilePath = path.isAbsolute(webIdentityTokenFile)
|
||||
? webIdentityTokenFile
|
||||
: path.join(workspace, webIdentityTokenFile);
|
||||
const webIdentityToken = readFileUtf8(webIdentityTokenFilePath);
|
||||
if (webIdentityToken === null) {
|
||||
throw new Error(`Web identity token file does not exist: ${webIdentityTokenFilePath}`);
|
||||
}
|
||||
core.info('Assuming role with web identity token file');
|
||||
try {
|
||||
delete params.Tags;
|
||||
delete params.TransitiveTagKeys;
|
||||
const creds = await client.send(
|
||||
new AssumeRoleWithWebIdentityCommand({
|
||||
...params,
|
||||
WebIdentityToken: webIdentityToken,
|
||||
}),
|
||||
);
|
||||
return creds;
|
||||
} catch (error) {
|
||||
throw new Error(`Could not assume role with web identity token file: ${errorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assumeRoleWithCredentials(params: AssumeRoleCommandInput, client: STSClient) {
|
||||
core.info('Assuming role with user credentials');
|
||||
try {
|
||||
const creds = await client.send(new AssumeRoleCommand({ ...params }));
|
||||
return creds;
|
||||
} catch (error) {
|
||||
if (error instanceof PackedPolicyTooLargeException) {
|
||||
core.info('Session tag size is too large; dropping droppable tags and retrying.');
|
||||
const droppableKeys = new Set(DROPPABLE_TAG_SOURCES.map((s) => s.key));
|
||||
params.Tags = params.Tags?.filter((tag) => !droppableKeys.has(tag.Key ?? ''));
|
||||
const creds = await client.send(new AssumeRoleCommand({ ...params }));
|
||||
return creds;
|
||||
}
|
||||
throw new Error(`Could not assume role with user credentials: ${errorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface assumeRoleParams {
|
||||
credentialsClient: CredentialsClient;
|
||||
roleToAssume: string;
|
||||
roleDuration: number;
|
||||
roleSessionName: string;
|
||||
roleSkipSessionTagging?: boolean;
|
||||
transitiveTagKeys?: string[];
|
||||
sourceAccountId?: string;
|
||||
roleExternalId?: string;
|
||||
webIdentityTokenFile?: string;
|
||||
webIdentityToken?: string;
|
||||
inlineSessionPolicy?: string;
|
||||
managedSessionPolicies?: { arn: string }[];
|
||||
customTags?: string;
|
||||
}
|
||||
|
||||
const TAG_KEY_REGEX = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]+$/u;
|
||||
const TAG_VALUE_REGEX = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
|
||||
const MAX_TAG_KEY_LENGTH = 128;
|
||||
const MAX_TAG_VALUE_LENGTH = 256;
|
||||
const MAX_SESSION_TAGS = 50;
|
||||
|
||||
// Identity/audit primitives. Always emitted and cannot be dropped.
|
||||
const NON_DROPPABLE_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||
{ key: 'Repository', envVar: 'GITHUB_REPOSITORY' },
|
||||
{ key: 'Workflow', envVar: 'GITHUB_WORKFLOW' },
|
||||
{ key: 'Action', envVar: 'GITHUB_ACTION' },
|
||||
{ key: 'Actor', envVar: 'GITHUB_ACTOR' },
|
||||
{ key: 'Commit', envVar: 'GITHUB_SHA' },
|
||||
{ key: 'Branch', envVar: 'GITHUB_REF' },
|
||||
];
|
||||
|
||||
// Convenience metadata. If the AssumeRole call fails due to compressed size of
|
||||
// session tags being too large, we will drop these tags and retry once.
|
||||
const DROPPABLE_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||
{ key: 'EventName', envVar: 'GITHUB_EVENT_NAME' },
|
||||
{ key: 'BaseRef', envVar: 'GITHUB_BASE_REF' },
|
||||
{ key: 'HeadRef', envVar: 'GITHUB_HEAD_REF' },
|
||||
{ key: 'RunId', envVar: 'GITHUB_RUN_ID' },
|
||||
{ key: 'Job', envVar: 'GITHUB_JOB' },
|
||||
{ key: 'TriggeringActor', envVar: 'GITHUB_TRIGGERING_ACTOR' },
|
||||
];
|
||||
|
||||
const PROTECTED_TAG_KEYS = new Set<string>([
|
||||
'GitHub',
|
||||
...NON_DROPPABLE_TAG_SOURCES.map((s) => s.key),
|
||||
...DROPPABLE_TAG_SOURCES.map((s) => s.key),
|
||||
]);
|
||||
|
||||
export function parseAndValidateCustomTags(customTags: string, existingTags: Tag[]): Tag[] {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(customTags);
|
||||
} catch {
|
||||
throw new Error('custom-tags: input is not valid JSON');
|
||||
}
|
||||
|
||||
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
||||
throw new Error('custom-tags: input must be a JSON object (not an array or primitive)');
|
||||
}
|
||||
|
||||
const newTags: Tag[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (typeof value === 'object') {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' must be a string, number, or boolean (not an object or array)`,
|
||||
);
|
||||
}
|
||||
|
||||
const stringValue = String(value);
|
||||
|
||||
if (key.length === 0 || key.length > MAX_TAG_KEY_LENGTH) {
|
||||
throw new Error(`custom-tags: key '${key}' must be between 1 and ${MAX_TAG_KEY_LENGTH} characters`);
|
||||
}
|
||||
if (stringValue.length > MAX_TAG_VALUE_LENGTH) {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' exceeds maximum length of ${MAX_TAG_VALUE_LENGTH} characters`,
|
||||
);
|
||||
}
|
||||
if (!TAG_KEY_REGEX.test(key)) {
|
||||
throw new Error(
|
||||
`custom-tags: key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
||||
);
|
||||
}
|
||||
if (stringValue.length > 0 && !TAG_VALUE_REGEX.test(stringValue)) {
|
||||
throw new Error(
|
||||
`custom-tags: value for key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
||||
);
|
||||
}
|
||||
if (PROTECTED_TAG_KEYS.has(key)) {
|
||||
throw new Error(
|
||||
`custom-tags: key '${key}' conflicts with a protected session tag set by this action and cannot be overridden`,
|
||||
);
|
||||
}
|
||||
|
||||
newTags.push({ Key: key, Value: stringValue });
|
||||
}
|
||||
|
||||
if (existingTags.length + newTags.length > MAX_SESSION_TAGS) {
|
||||
throw new Error(
|
||||
`custom-tags: total session tags (${existingTags.length + newTags.length}) would exceed the AWS limit of ${MAX_SESSION_TAGS}`,
|
||||
);
|
||||
}
|
||||
|
||||
return newTags;
|
||||
}
|
||||
|
||||
export async function assumeRole(params: assumeRoleParams) {
|
||||
const {
|
||||
credentialsClient,
|
||||
sourceAccountId,
|
||||
roleToAssume,
|
||||
roleExternalId,
|
||||
roleDuration,
|
||||
roleSessionName,
|
||||
roleSkipSessionTagging,
|
||||
transitiveTagKeys,
|
||||
webIdentityTokenFile,
|
||||
webIdentityToken,
|
||||
inlineSessionPolicy,
|
||||
managedSessionPolicies,
|
||||
customTags,
|
||||
} = { ...params };
|
||||
|
||||
// Load GitHub environment variables
|
||||
const { GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_SHA, GITHUB_WORKSPACE } = process.env;
|
||||
if (!GITHUB_REPOSITORY || !GITHUB_WORKFLOW || !GITHUB_ACTION || !GITHUB_ACTOR || !GITHUB_SHA || !GITHUB_WORKSPACE) {
|
||||
throw new Error('Missing required environment variables. Are you running in GitHub Actions?');
|
||||
}
|
||||
|
||||
// Build session tags. Values are sanitized because the AWS tag value spec is more
|
||||
// restrictive than permissible characters in environment variables.
|
||||
const protectedTags: Tag[] = [{ Key: 'GitHub', Value: 'Actions' }];
|
||||
for (const { key, envVar } of NON_DROPPABLE_TAG_SOURCES) {
|
||||
const value = process.env[envVar];
|
||||
if (value) {
|
||||
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||
}
|
||||
}
|
||||
for (const { key, envVar } of DROPPABLE_TAG_SOURCES) {
|
||||
const value = process.env[envVar];
|
||||
if (value) {
|
||||
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||
}
|
||||
}
|
||||
|
||||
const parsedCustomTags: Tag[] = customTags ? parseAndValidateCustomTags(customTags, protectedTags) : [];
|
||||
|
||||
const tagArray: Tag[] = [...protectedTags, ...parsedCustomTags];
|
||||
|
||||
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
||||
if (!tags) {
|
||||
core.debug('Role session tagging has been skipped.');
|
||||
} else {
|
||||
core.debug(`${tags.length} role session tags are being used:`);
|
||||
core.debug(JSON.stringify(tagArray));
|
||||
}
|
||||
|
||||
//only populate transitiveTagKeys array if user is actually using session tagging
|
||||
const transitiveTagKeysArray = roleSkipSessionTagging
|
||||
? undefined
|
||||
: transitiveTagKeys?.filter((key) => tags?.some((tag) => tag.Key === key));
|
||||
|
||||
// Calculate role ARN from name and account ID (currently only supports `aws` partition)
|
||||
let roleArn = roleToAssume;
|
||||
if (!roleArn.startsWith('arn:aws')) {
|
||||
assert(
|
||||
isDefined(sourceAccountId),
|
||||
'Source Account ID is needed if the Role Name is provided and not the Role Arn.',
|
||||
);
|
||||
roleArn = `arn:aws:iam::${sourceAccountId}:role/${roleArn}`;
|
||||
}
|
||||
|
||||
// Ready common parameters to assume role
|
||||
const commonAssumeRoleParams: AssumeRoleCommandInput = {
|
||||
RoleArn: roleArn,
|
||||
RoleSessionName: roleSessionName,
|
||||
DurationSeconds: roleDuration,
|
||||
Tags: tags ? tags : undefined,
|
||||
TransitiveTagKeys: transitiveTagKeysArray ? transitiveTagKeysArray : undefined,
|
||||
ExternalId: roleExternalId ? roleExternalId : undefined,
|
||||
Policy: inlineSessionPolicy ? inlineSessionPolicy : undefined,
|
||||
PolicyArns: managedSessionPolicies?.length ? managedSessionPolicies : undefined,
|
||||
};
|
||||
const keys = Object.keys(commonAssumeRoleParams) as Array<keyof typeof commonAssumeRoleParams>;
|
||||
keys.forEach((k) => {
|
||||
if (commonAssumeRoleParams[k] === undefined) {
|
||||
delete commonAssumeRoleParams[k];
|
||||
}
|
||||
});
|
||||
|
||||
// Instantiate STS client
|
||||
const stsClient = credentialsClient.stsClient;
|
||||
|
||||
// Assume role using one of three methods
|
||||
if (!!webIdentityToken) {
|
||||
return assumeRoleWithOIDC(commonAssumeRoleParams, stsClient, webIdentityToken);
|
||||
}
|
||||
if (!!webIdentityTokenFile) {
|
||||
return assumeRoleWithWebIdentityTokenFile(
|
||||
commonAssumeRoleParams,
|
||||
stsClient,
|
||||
webIdentityTokenFile,
|
||||
GITHUB_WORKSPACE,
|
||||
);
|
||||
}
|
||||
return assumeRoleWithCredentials(commonAssumeRoleParams, stsClient);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { errorMessage, getBooleanInput } from '../helpers';
|
||||
|
||||
/**
|
||||
* When the GitHub Actions job is done, clean up any environment variables that
|
||||
* may have been set by the configure-aws-credentials steps in the job.
|
||||
*
|
||||
* Environment variables are not intended to be shared across different jobs in
|
||||
* the same GitHub Actions workflow: GitHub Actions documentation states that
|
||||
* each job runs in a fresh instance. However, doing our own cleanup will
|
||||
* give us additional assurance that these environment variables are not shared
|
||||
* with any other jobs.
|
||||
*/
|
||||
|
||||
export function cleanup() {
|
||||
// Only attempt to change environment variables if we changed them in the first place
|
||||
const awsProfile = core.getInput('aws-profile', { required: false });
|
||||
if (getBooleanInput('output-env-credentials', { required: false, default: !awsProfile })) {
|
||||
try {
|
||||
// The GitHub Actions toolkit does not have an option to completely unset
|
||||
// environment variables, so we overwrite the current value with an empty
|
||||
// string. The AWS CLI and AWS SDKs will behave correctly: they treat an
|
||||
// empty string value as if the environment variable does not exist.
|
||||
core.exportVariable('AWS_ACCESS_KEY_ID', '');
|
||||
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
|
||||
core.exportVariable('AWS_SESSION_TOKEN', '');
|
||||
core.exportVariable('AWS_DEFAULT_REGION', '');
|
||||
core.exportVariable('AWS_REGION', '');
|
||||
if (core.getInput('aws-profile')) {
|
||||
core.exportVariable('AWS_PROFILE', '');
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(errorMessage(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* c8 ignore start */
|
||||
if (require.main === module) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
core.setFailed(errorMessage(error));
|
||||
}
|
||||
}
|
||||
-398
@@ -1,398 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||
import type { UserAgent } from '@smithy/types';
|
||||
import type { CredentialsClient } from './CredentialsClient';
|
||||
|
||||
const MAX_TAG_VALUE_LENGTH = 256;
|
||||
const SANITIZATION_CHARACTER = '_';
|
||||
const SPECIAL_CHARS_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/;
|
||||
const USER_AGENT_PREFIX = 'configure-aws-credentials-for-github-actions';
|
||||
const UA_FIELDS: ReadonlyArray<{ env: string; label: string; pattern: RegExp }> = [
|
||||
{ env: 'GITHUB_ACTION', label: 'action', pattern: /^[A-Za-z0-9_-]{1,128}$/ },
|
||||
{ env: 'GITHUB_RUN_ID', label: 'run_id', pattern: /^[0-9]{1,20}$/ },
|
||||
{ env: 'GITHUB_RUN_ATTEMPT', label: 'attempt', pattern: /^[0-9]{1,10}$/ },
|
||||
];
|
||||
|
||||
export function buildCustomUserAgent(): UserAgent {
|
||||
const tokens: UserAgent = [[USER_AGENT_PREFIX]];
|
||||
for (const { env, label, pattern } of UA_FIELDS) {
|
||||
const value = process.env[env];
|
||||
if (value === undefined) continue;
|
||||
if (pattern.test(value)) {
|
||||
tokens.push(['md', `${label}#${value}`]);
|
||||
} else {
|
||||
core.warning(`${env} has unexpected format; omitting from User-Agent`);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export function translateEnvVariables() {
|
||||
const envVars = [
|
||||
'AWS_REGION',
|
||||
'ROLE_TO_ASSUME',
|
||||
'WEB_IDENTITY_TOKEN_FILE',
|
||||
'ROLE_CHAINING',
|
||||
'AUDIENCE',
|
||||
'HTTP_PROXY',
|
||||
'MASK_AWS_ACCOUNT_ID',
|
||||
'ROLE_DURATION_SECONDS',
|
||||
'ROLE_EXTERNAL_ID',
|
||||
'ROLE_SESSION_NAME',
|
||||
'ROLE_SKIP_SESSION_TAGGING',
|
||||
'TRANSITIVE_TAG_KEYS',
|
||||
'INLINE_SESSION_POLICY',
|
||||
'MANAGED_SESSION_POLICIES',
|
||||
'OUTPUT_CREDENTIALS',
|
||||
'UNSET_CURRENT_CREDENTIALS',
|
||||
'DISABLE_RETRY',
|
||||
'RETRY_MAX_ATTEMPTS',
|
||||
'SPECIAL_CHARACTERS_WORKAROUND',
|
||||
'USE_EXISTING_CREDENTIALS',
|
||||
'NO_PROXY',
|
||||
'OVERWRITE_AWS_PROFILE',
|
||||
];
|
||||
// Treat HTTPS_PROXY as HTTP_PROXY. Precedence is HTTPS_PROXY > HTTP_PROXY
|
||||
if (process.env.HTTPS_PROXY) process.env.HTTP_PROXY = process.env.HTTPS_PROXY;
|
||||
|
||||
for (const envVar of envVars) {
|
||||
if (process.env[envVar]) {
|
||||
const inputKey = `INPUT_${envVar.replace(/_/g, '-')}`;
|
||||
process.env[inputKey] = process.env[inputKey] || process.env[envVar];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
|
||||
// Setting the credentials as secrets masks them in Github Actions logs
|
||||
export function exportCredentials(
|
||||
creds?: Partial<Credentials>,
|
||||
outputCredentials?: boolean,
|
||||
outputEnvCredentials?: boolean,
|
||||
) {
|
||||
if (creds?.AccessKeyId) {
|
||||
core.setSecret(creds.AccessKeyId);
|
||||
}
|
||||
|
||||
if (creds?.SecretAccessKey) {
|
||||
core.setSecret(creds.SecretAccessKey);
|
||||
}
|
||||
|
||||
if (creds?.SessionToken) {
|
||||
core.setSecret(creds.SessionToken);
|
||||
}
|
||||
|
||||
if (outputEnvCredentials) {
|
||||
if (creds?.AccessKeyId) {
|
||||
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
|
||||
}
|
||||
|
||||
if (creds?.SecretAccessKey) {
|
||||
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
|
||||
}
|
||||
|
||||
if (creds?.SessionToken) {
|
||||
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
|
||||
} else if (process.env.AWS_SESSION_TOKEN) {
|
||||
// clear session token from previous credentials action
|
||||
core.exportVariable('AWS_SESSION_TOKEN', '');
|
||||
}
|
||||
}
|
||||
|
||||
if (outputCredentials) {
|
||||
if (creds?.AccessKeyId) {
|
||||
core.setOutput('aws-access-key-id', creds.AccessKeyId);
|
||||
}
|
||||
if (creds?.SecretAccessKey) {
|
||||
core.setOutput('aws-secret-access-key', creds.SecretAccessKey);
|
||||
}
|
||||
if (creds?.SessionToken) {
|
||||
core.setOutput('aws-session-token', creds.SessionToken);
|
||||
}
|
||||
if (creds?.Expiration) {
|
||||
core.setOutput('aws-expiration', creds.Expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function unsetCredentials(outputEnvCredentials?: boolean) {
|
||||
if (outputEnvCredentials) {
|
||||
core.exportVariable('AWS_ACCESS_KEY_ID', '');
|
||||
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
|
||||
core.exportVariable('AWS_SESSION_TOKEN', '');
|
||||
core.exportVariable('AWS_REGION', '');
|
||||
core.exportVariable('AWS_DEFAULT_REGION', '');
|
||||
}
|
||||
}
|
||||
|
||||
export function exportRegion(region: string, outputEnvCredentials?: boolean) {
|
||||
if (outputEnvCredentials) {
|
||||
core.exportVariable('AWS_DEFAULT_REGION', region);
|
||||
core.exportVariable('AWS_REGION', region);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCallerIdentity(client: STSClient): Promise<{ Account: string; Arn: string; UserId?: string }> {
|
||||
const identity = await client.send(new GetCallerIdentityCommand({}));
|
||||
if (!identity.Account || !identity.Arn) {
|
||||
throw new Error('Could not get Account ID or ARN from STS. Did you set credentials?');
|
||||
}
|
||||
const result: { Account: string; Arn: string; UserId?: string } = {
|
||||
Account: identity.Account,
|
||||
Arn: identity.Arn,
|
||||
};
|
||||
if (identity.UserId !== undefined) {
|
||||
result.UserId = identity.UserId;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Obtains account ID from STS Client and sets it as output
|
||||
export async function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean) {
|
||||
const identity = await getCallerIdentity(credentialsClient.stsClient);
|
||||
const accountId = identity.Account;
|
||||
const arn = identity.Arn;
|
||||
if (maskAccountId) {
|
||||
core.setSecret(accountId);
|
||||
core.setSecret(arn);
|
||||
}
|
||||
core.setOutput('aws-account-id', accountId);
|
||||
core.setOutput('authenticated-arn', arn);
|
||||
return accountId;
|
||||
}
|
||||
|
||||
// Tags have a more restrictive set of acceptable characters than GitHub environment variables can.
|
||||
// This replaces anything not conforming to the tag restrictions by inverting the regular expression.
|
||||
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
|
||||
export function sanitizeGitHubVariables(name: string) {
|
||||
const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_.:/=+\-@]/gu, SANITIZATION_CHARACTER);
|
||||
const nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH);
|
||||
return nameTruncated;
|
||||
}
|
||||
|
||||
export async function defaultSleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
let sleep = defaultSleep;
|
||||
|
||||
export function withsleep(s: typeof sleep) {
|
||||
sleep = s;
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
sleep = defaultSleep;
|
||||
}
|
||||
|
||||
export function verifyKeys(creds: Partial<Credentials> | undefined) {
|
||||
if (!creds) {
|
||||
return false;
|
||||
}
|
||||
if (creds.AccessKeyId) {
|
||||
if (SPECIAL_CHARS_REGEX.test(creds.AccessKeyId)) {
|
||||
core.debug('AccessKeyId contains special characters.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (creds.SecretAccessKey) {
|
||||
if (SPECIAL_CHARS_REGEX.test(creds.SecretAccessKey)) {
|
||||
core.debug('SecretAccessKey contains special characters.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Retries the promise with exponential backoff if the error isRetryable up to maxRetries time.
|
||||
export async function retryAndBackoff<T>(
|
||||
fn: () => Promise<T>,
|
||||
isRetryable: boolean,
|
||||
maxRetries = 12,
|
||||
retries = 0,
|
||||
base = 50,
|
||||
label?: string,
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
if (!isRetryable) {
|
||||
core.debug(`retryAndBackoff: error is not retryable: ${errorMessage(err)}`);
|
||||
throw err;
|
||||
}
|
||||
// It's retryable, so sleep and retry.
|
||||
const delay = Math.random() * (2 ** retries * base);
|
||||
const nextRetry = retries + 1;
|
||||
const opName = label ? ` ${label}` : '';
|
||||
|
||||
core.info(
|
||||
`Retry${opName}: attempt ${nextRetry} of ${maxRetries} failed: ${errorMessage(err)}. ` +
|
||||
`Retrying after ${Math.floor(delay)}ms.`,
|
||||
);
|
||||
|
||||
await sleep(delay);
|
||||
|
||||
if (nextRetry >= maxRetries) {
|
||||
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
||||
}
|
||||
}
|
||||
|
||||
/* c8 ignore start */
|
||||
export function errorMessage(error: unknown) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export function isDefined<T>(i: T | undefined | null): i is T {
|
||||
return i !== undefined && i !== null;
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
|
||||
export async function areCredentialsValid(credentialsClient: CredentialsClient) {
|
||||
const client = credentialsClient.stsClient;
|
||||
try {
|
||||
const identity = await client.send(new GetCallerIdentityCommand({}));
|
||||
if (identity.Account) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like core.getBooleanInput, but respects the required option.
|
||||
*
|
||||
* From https://github.com/actions/toolkit/blob/6876e2a664ec02908178087905b9155e9892a437/packages/core/src/core.ts
|
||||
*
|
||||
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
|
||||
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
|
||||
* The return value is also in boolean type.
|
||||
* ref: https://yaml.org/spec/1.2/spec.html#id2804923
|
||||
*
|
||||
* @param name name of the input to get
|
||||
* @param options optional. See core.InputOptions. Also supports optional 'default' if the input is not set
|
||||
* @returns boolean
|
||||
*/
|
||||
export function getBooleanInput(name: string, options?: core.InputOptions & { default?: boolean }): boolean {
|
||||
const trueValue = ['true', 'True', 'TRUE'];
|
||||
const falseValue = ['false', 'False', 'FALSE'];
|
||||
const optionsWithoutDefault = { ...options };
|
||||
delete optionsWithoutDefault.default;
|
||||
const val = core.getInput(name, optionsWithoutDefault);
|
||||
if (trueValue.includes(val)) return true;
|
||||
if (falseValue.includes(val)) return false;
|
||||
if (val === '') return options?.default ?? false;
|
||||
throw new TypeError(
|
||||
`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
|
||||
`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``,
|
||||
);
|
||||
}
|
||||
|
||||
// O_NOFOLLOW is undefined on Windows. This sets it to 0 if it's not defined.
|
||||
const O_NOFOLLOW: number = (fs.constants as { O_NOFOLLOW?: number }).O_NOFOLLOW ?? 0;
|
||||
|
||||
export function isAllowListed(filePath: string): boolean {
|
||||
// Kubelet projects service-account tokens through a symlink chain
|
||||
// (token -> ..data/token, ..data -> ..<timestamp>/). The containing path is
|
||||
// kubelet-controlled, so we allow symlink-following reads of this fixed
|
||||
// location only.
|
||||
const KUBERNETES_TOKEN_PATH_REGEX = /^\/var\/run\/secrets\/[^/]+\/serviceaccount\/token$/;
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
// No Kubernetes token paths on Windows
|
||||
return KUBERNETES_TOKEN_PATH_REGEX.test(path.posix.normalize(filePath));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isSymlink(filePath: string): boolean {
|
||||
try {
|
||||
return fs.lstatSync(filePath).isSymbolicLink();
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Refuses if filePath or its parent directory is a symbolic link.
|
||||
function refuseSymlinkOnPath(filePath: string): void {
|
||||
const parent = path.dirname(filePath);
|
||||
if (parent !== filePath && isSymlink(parent)) {
|
||||
throw new Error(`Refusing ${filePath} (parent directory is a symbolic link)`);
|
||||
}
|
||||
if (isSymlink(filePath)) {
|
||||
throw new Error(`Refusing ${filePath} (path is a symbolic link)`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertRegularFile(fd: number, filePath: string): void {
|
||||
const stats = fs.fstatSync(fd);
|
||||
if (!stats.isFile()) {
|
||||
throw new Error(`${filePath} (path is not a regular file)`);
|
||||
}
|
||||
}
|
||||
|
||||
// ENOENT: file does not exist
|
||||
// ELOOP: too many symbolic links (from NOFOLLOW)
|
||||
|
||||
export function readFileUtf8(filePath: string): string | null {
|
||||
const allowSymlink = isAllowListed(filePath);
|
||||
if (!allowSymlink) {
|
||||
refuseSymlinkOnPath(filePath);
|
||||
}
|
||||
const openFlags = fs.constants.O_RDONLY | (allowSymlink ? 0 : O_NOFOLLOW);
|
||||
let fd: number;
|
||||
try {
|
||||
fd = fs.openSync(filePath, openFlags);
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException).code;
|
||||
if (code === 'ENOENT') return null;
|
||||
if (code === 'ELOOP') {
|
||||
throw new Error(`Refusing ${filePath} (path is a symbolic link)`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
assertRegularFile(fd, filePath);
|
||||
return fs.readFileSync(fd, 'utf-8');
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
export function writeFileUtf8(filePath: string, content: string, mode = 0o600): void {
|
||||
refuseSymlinkOnPath(filePath);
|
||||
let fd: number;
|
||||
try {
|
||||
fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | O_NOFOLLOW, mode);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === 'ELOOP') {
|
||||
throw new Error(`Refusing ${filePath} (path is a symbolic link)`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
assertRegularFile(fd, filePath);
|
||||
// openSync only applies mode on creation.
|
||||
// If the file already exists, we need to ensure the mode is correct.
|
||||
if (process.platform !== 'win32') {
|
||||
fs.fchmodSync(fd, mode);
|
||||
}
|
||||
fs.writeFileSync(fd, content);
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
export function mkdir(dir: string, mode = 0o700): void {
|
||||
fs.mkdirSync(dir, { recursive: true, mode });
|
||||
if (isSymlink(dir)) {
|
||||
throw new Error(`Refusing ${dir} (path is a symbolic link)`);
|
||||
}
|
||||
}
|
||||
-332
@@ -1,332 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import type { AssumeRoleCommandOutput } from '@aws-sdk/client-sts';
|
||||
import { assumeRole } from './assumeRole';
|
||||
import { CredentialsClient } from './CredentialsClient';
|
||||
import {
|
||||
areCredentialsValid,
|
||||
errorMessage,
|
||||
exportAccountId,
|
||||
exportCredentials,
|
||||
exportRegion,
|
||||
getBooleanInput,
|
||||
retryAndBackoff,
|
||||
translateEnvVariables,
|
||||
unsetCredentials,
|
||||
verifyKeys,
|
||||
} from './helpers';
|
||||
import { writeProfileFiles } from './profileManager';
|
||||
|
||||
const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds)
|
||||
const ROLE_SESSION_NAME = 'GitHubActions';
|
||||
const REGION_REGEX = /^[a-z0-9-]+$/g;
|
||||
const ROLE_SESSION_NAME_REGEX = /^[\w+=,.@-]*$/;
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
translateEnvVariables();
|
||||
// Get inputs
|
||||
// Undefined inputs are empty strings ( or empty arrays)
|
||||
const AccessKeyId = core.getInput('aws-access-key-id', { required: false });
|
||||
const SecretAccessKey = core.getInput('aws-secret-access-key', { required: false });
|
||||
const sessionTokenInput = core.getInput('aws-session-token', { required: false });
|
||||
const SessionToken = sessionTokenInput === '' ? undefined : sessionTokenInput;
|
||||
const region = core.getInput('aws-region', { required: true });
|
||||
const awsProfile = core.getInput('aws-profile', { required: false });
|
||||
const overwriteAwsProfile = getBooleanInput('overwrite-aws-profile', { required: false });
|
||||
const roleToAssume = core.getInput('role-to-assume', { required: false });
|
||||
const audience = core.getInput('audience', { required: false });
|
||||
const maskAccountId = getBooleanInput('mask-aws-account-id', { required: false });
|
||||
const roleExternalId = core.getInput('role-external-id', { required: false });
|
||||
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
|
||||
const roleDuration =
|
||||
Number.parseInt(core.getInput('role-duration-seconds', { required: false })) || DEFAULT_ROLE_DURATION;
|
||||
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
|
||||
const roleSkipSessionTagging = getBooleanInput('role-skip-session-tagging', { required: false });
|
||||
const transitiveTagKeys = core.getMultilineInput('transitive-tag-keys', { required: false });
|
||||
const proxyServer = core.getInput('http-proxy', { required: false }) || process.env.HTTP_PROXY;
|
||||
const customTags = core.getInput('custom-tags', { required: false });
|
||||
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
|
||||
const managedSessionPolicies = core.getMultilineInput('managed-session-policies', { required: false }).map((p) => {
|
||||
return { arn: p };
|
||||
});
|
||||
const roleChaining = getBooleanInput('role-chaining', { required: false });
|
||||
const outputCredentials = getBooleanInput('output-credentials', { required: false });
|
||||
// Default to always outputting environment credentials unless profile is specified. If profile is specified, default to
|
||||
// no environment credentials (but still output them if the user specifically requests it).
|
||||
const outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: !awsProfile });
|
||||
const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false });
|
||||
let disableRetry = getBooleanInput('disable-retry', { required: false });
|
||||
const specialCharacterWorkaround = getBooleanInput('special-characters-workaround', { required: false });
|
||||
const useExistingCredentials = core.getInput('use-existing-credentials', { required: false });
|
||||
let maxRetries = Number.parseInt(core.getInput('retry-max-attempts', { required: false })) || 12;
|
||||
const expectedAccountIds = core
|
||||
.getInput('allowed-account-ids', { required: false })
|
||||
.split(',')
|
||||
.map((s) => s.trim());
|
||||
const forceSkipOidc = getBooleanInput('force-skip-oidc', { required: false });
|
||||
const noProxy = core.getInput('no-proxy', { required: false });
|
||||
const stsEndpoint = core.getInput('sts-endpoint', { required: false });
|
||||
const globalTimeout = Number.parseInt(core.getInput('action-timeout-s', { required: false })) || 0;
|
||||
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
if (globalTimeout > 0) {
|
||||
core.info(`Setting a global timeout of ${globalTimeout} seconds for the action`);
|
||||
timeoutId = setTimeout(() => {
|
||||
core.setFailed(`Action timed out after ${globalTimeout} seconds`);
|
||||
process.exit(1);
|
||||
}, globalTimeout * 1000);
|
||||
}
|
||||
|
||||
if (forceSkipOidc && roleToAssume && !AccessKeyId && !webIdentityTokenFile) {
|
||||
throw new Error(
|
||||
"If 'force-skip-oidc' is true and 'role-to-assume' is set, 'aws-access-key-id' or 'web-identity-token-file' must be set",
|
||||
);
|
||||
}
|
||||
|
||||
if (specialCharacterWorkaround) {
|
||||
// 😳
|
||||
disableRetry = false;
|
||||
maxRetries = 12;
|
||||
} else if (maxRetries < 1) {
|
||||
maxRetries = 1;
|
||||
}
|
||||
|
||||
const withRetry = <T>(fn: () => Promise<T>, label: string): Promise<T> =>
|
||||
retryAndBackoff(fn, !disableRetry, maxRetries, 0, 50, label);
|
||||
|
||||
// Logic to decide whether to attempt to use OIDC or not
|
||||
const useGitHubOIDCProvider = () => {
|
||||
if (forceSkipOidc) return false;
|
||||
// The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted.
|
||||
// This is necessary to authenticate with OIDC, but not strictly set just for OIDC. If it is not set and all other
|
||||
// checks pass, it is likely but not guaranteed that the user needs but lacks this permission in their workflow.
|
||||
// So, we will log a warning when it is the only piece absent
|
||||
if (
|
||||
!!roleToAssume &&
|
||||
!webIdentityTokenFile &&
|
||||
!AccessKeyId &&
|
||||
!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN &&
|
||||
!roleChaining
|
||||
) {
|
||||
core.info(
|
||||
'It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission? ' +
|
||||
'If you are not trying to authenticate with OIDC and the action is working successfully, you can ignore this message.',
|
||||
);
|
||||
}
|
||||
return (
|
||||
!!roleToAssume &&
|
||||
!!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN &&
|
||||
!AccessKeyId &&
|
||||
!webIdentityTokenFile &&
|
||||
!roleChaining
|
||||
);
|
||||
};
|
||||
|
||||
if (unsetCurrentCredentials) {
|
||||
unsetCredentials(outputEnvCredentials);
|
||||
}
|
||||
|
||||
if (!region.match(REGION_REGEX)) {
|
||||
throw new Error(`Region is not valid: ${region}`);
|
||||
}
|
||||
|
||||
if (roleSessionName.length < 2 || roleSessionName.length > 64) {
|
||||
throw new Error(
|
||||
`Role session name must be between 2 and 64 characters, got ${roleSessionName.length}: '${roleSessionName}'`,
|
||||
);
|
||||
}
|
||||
if (!roleSessionName.match(ROLE_SESSION_NAME_REGEX)) {
|
||||
throw new Error(
|
||||
`Role session name is not valid: '${roleSessionName}'. Must satisfy regular expression pattern: [\\w+=,.@-]*`,
|
||||
);
|
||||
}
|
||||
|
||||
exportRegion(region, outputEnvCredentials);
|
||||
|
||||
// Instantiate credentials client
|
||||
const clientProps: {
|
||||
region: string;
|
||||
proxyServer?: string;
|
||||
noProxy?: string;
|
||||
stsEndpoint?: string;
|
||||
roleChaining: boolean;
|
||||
} = {
|
||||
region,
|
||||
roleChaining,
|
||||
};
|
||||
if (proxyServer) clientProps.proxyServer = proxyServer;
|
||||
if (noProxy) clientProps.noProxy = noProxy;
|
||||
if (stsEndpoint) clientProps.stsEndpoint = stsEndpoint;
|
||||
const credentialsClient = new CredentialsClient(clientProps);
|
||||
let sourceAccountId: string;
|
||||
let webIdentityToken: string;
|
||||
|
||||
//if the user wants to attempt to use existing credentials, check if we have some already
|
||||
if (useExistingCredentials) {
|
||||
const validCredentials = await areCredentialsValid(credentialsClient);
|
||||
if (validCredentials) {
|
||||
core.notice('Pre-existing credentials are valid. No need to generate new ones.');
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
return;
|
||||
}
|
||||
core.notice('No valid credentials exist. Running as normal.');
|
||||
}
|
||||
|
||||
// If OIDC is being used, generate token
|
||||
// Else, export credentials provided as input
|
||||
if (useGitHubOIDCProvider()) {
|
||||
try {
|
||||
webIdentityToken = await withRetry(async () => {
|
||||
return core.getIDToken(audience);
|
||||
}, 'getIDToken');
|
||||
} catch (error) {
|
||||
throw new Error(`getIDToken call failed: ${errorMessage(error)}`);
|
||||
}
|
||||
} else if (AccessKeyId) {
|
||||
if (!SecretAccessKey) {
|
||||
throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided");
|
||||
}
|
||||
// The STS client for calling AssumeRole pulls creds from the environment.
|
||||
// Plus, in the assume role case, if the AssumeRole call fails, we want
|
||||
// the source credentials to already be masked as secrets
|
||||
// in any error messages.
|
||||
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }, outputCredentials, outputEnvCredentials);
|
||||
|
||||
// If using IAM User Credentials, write to profile now so that the assumeRole call can succeed (and also for
|
||||
// credential validation before role assumption).
|
||||
if (awsProfile) {
|
||||
writeProfileFiles(awsProfile, { AccessKeyId, SecretAccessKey, SessionToken }, region, overwriteAwsProfile);
|
||||
}
|
||||
} else if (!webIdentityTokenFile && !roleChaining) {
|
||||
// Proceed only if credentials can be picked up
|
||||
await withRetry(
|
||||
() => credentialsClient.validateCredentials(undefined, roleChaining, expectedAccountIds),
|
||||
'validateCredentials',
|
||||
);
|
||||
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
}
|
||||
|
||||
if (AccessKeyId || roleChaining) {
|
||||
// Validate that the SDK can actually pick up credentials.
|
||||
// This validates cases where this action is using existing environment credentials,
|
||||
// and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
|
||||
// Skip when output-env-credentials is false: input IAM keys were not written to env, so
|
||||
// the default chain would resolve to ambient runner credentials and the access-key check
|
||||
// would spuriously fail (see #1554).
|
||||
if (outputEnvCredentials) {
|
||||
await withRetry(
|
||||
() => credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds),
|
||||
'validateCredentials',
|
||||
);
|
||||
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
}
|
||||
}
|
||||
if (customTags && (useGitHubOIDCProvider() || webIdentityTokenFile)) {
|
||||
core.warning(
|
||||
"'custom-tags' is set but will be ignored because session tags cannot be applied when using OIDC or web identity token authentication. " +
|
||||
'Tags are controlled by the identity provider token claims in these authentication flows.',
|
||||
);
|
||||
}
|
||||
|
||||
// Get role credentials if configured to do so
|
||||
if (roleToAssume) {
|
||||
let roleCredentials: AssumeRoleCommandOutput;
|
||||
do {
|
||||
roleCredentials = await withRetry(async () => {
|
||||
return assumeRole({
|
||||
credentialsClient,
|
||||
sourceAccountId,
|
||||
roleToAssume,
|
||||
roleExternalId,
|
||||
roleDuration,
|
||||
roleSessionName,
|
||||
roleSkipSessionTagging,
|
||||
transitiveTagKeys,
|
||||
webIdentityTokenFile,
|
||||
webIdentityToken,
|
||||
inlineSessionPolicy,
|
||||
managedSessionPolicies,
|
||||
customTags,
|
||||
});
|
||||
}, 'AssumeRole');
|
||||
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
|
||||
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
|
||||
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
|
||||
// Validate that the SDK can pick up the assumed-role credentials from the environment.
|
||||
// Skip when output-env-credentials is false: the credentials were never written to env,
|
||||
// so the default credential provider chain would resolve to ambient runner credentials
|
||||
// (e.g. an EC2 instance profile) and the access-key-id check would spuriously fail.
|
||||
// Skip when using a profile: validation runs after the profile file is written below.
|
||||
if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile && outputEnvCredentials) {
|
||||
await withRetry(
|
||||
() =>
|
||||
credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials?.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
),
|
||||
'validateCredentials',
|
||||
);
|
||||
}
|
||||
if (outputEnvCredentials) {
|
||||
await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||
}
|
||||
|
||||
// Write profile files if profile mode is enabled
|
||||
if (awsProfile) {
|
||||
if (!roleCredentials.Credentials) {
|
||||
throw new Error('AssumeRole call succeeded but returned no credentials');
|
||||
}
|
||||
// If user provided IAM User Credentials and then we assumed a role, overwrite the profile file to add
|
||||
// the session token. (this only overwrites the profile within a single run of the action).
|
||||
// We then validate the credentials to make sure they work.
|
||||
if (AccessKeyId || !process.env.GITHUB_ACTIONS) {
|
||||
writeProfileFiles(awsProfile, roleCredentials.Credentials, region, true);
|
||||
await withRetry(
|
||||
() =>
|
||||
credentialsClient.validateCredentials(
|
||||
roleCredentials.Credentials?.AccessKeyId,
|
||||
roleChaining,
|
||||
expectedAccountIds,
|
||||
),
|
||||
'validateCredentials',
|
||||
);
|
||||
} else {
|
||||
writeProfileFiles(awsProfile, roleCredentials.Credentials, region, overwriteAwsProfile);
|
||||
}
|
||||
|
||||
if (outputEnvCredentials) {
|
||||
core.exportVariable('AWS_PROFILE', awsProfile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core.info('Proceeding with IAM user credentials');
|
||||
|
||||
if (awsProfile) {
|
||||
if (outputEnvCredentials) {
|
||||
core.exportVariable('AWS_PROFILE', awsProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear timeout on successful completion
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
} catch (error) {
|
||||
core.setFailed(errorMessage(error));
|
||||
|
||||
const showStackTrace = process.env.SHOW_STACK_TRACE;
|
||||
if (showStackTrace === 'true') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* c8 ignore start */
|
||||
/* istanbul ignore next */
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await run();
|
||||
})().catch((error) => {
|
||||
core.setFailed(errorMessage(error));
|
||||
});
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import type { Credentials } from '@aws-sdk/client-sts';
|
||||
import { mkdir, readFileUtf8, writeFileUtf8 } from './helpers';
|
||||
|
||||
/**
|
||||
* Parse an INI-format string into a nested object.
|
||||
* Preserves literal section names (e.g. "profile dev" stays as-is).
|
||||
*/
|
||||
export function parseIni(iniData: string): Record<string, Record<string, string>> {
|
||||
const result: Record<string, Record<string, string>> = {};
|
||||
let currentSection: string | undefined;
|
||||
|
||||
for (const line of iniData.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sectionMatch = trimmed.match(/^\[([^\]]*)\]$/);
|
||||
if (sectionMatch) {
|
||||
currentSection = sectionMatch[1] as string;
|
||||
if (currentSection === '__proto__') {
|
||||
currentSection = undefined;
|
||||
continue;
|
||||
}
|
||||
result[currentSection] = result[currentSection] || {};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentSection) {
|
||||
const eqIndex = trimmed.indexOf('=');
|
||||
if (eqIndex > 0) {
|
||||
const key = trimmed.substring(0, eqIndex).trim();
|
||||
const value = trimmed.substring(eqIndex + 1).trim();
|
||||
if (key !== '__proto__') {
|
||||
const section = result[currentSection];
|
||||
if (section) {
|
||||
section[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a nested object into INI-format string.
|
||||
*/
|
||||
export function stringifyIni(data: Record<string, Record<string, string>>): string {
|
||||
const sections: string[] = [];
|
||||
for (const [sectionName, sectionData] of Object.entries(data)) {
|
||||
const lines: string[] = [`[${sectionName}]`];
|
||||
for (const [key, value] of Object.entries(sectionData)) {
|
||||
lines.push(`${key} = ${value}`);
|
||||
}
|
||||
sections.push(lines.join('\n'));
|
||||
}
|
||||
return `${sections.join('\n\n')}\n`;
|
||||
}
|
||||
|
||||
interface ProfileFilePaths {
|
||||
credentials: string;
|
||||
config: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file paths for AWS credentials and config files
|
||||
* Respects AWS_SHARED_CREDENTIALS_FILE and AWS_CONFIG_FILE environment variables
|
||||
*/
|
||||
export function getProfileFilePaths(): ProfileFilePaths {
|
||||
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || path.join(os.homedir(), '.aws', 'credentials');
|
||||
const configPath = process.env.AWS_CONFIG_FILE || path.join(os.homedir(), '.aws', 'config');
|
||||
|
||||
return {
|
||||
credentials: credentialsPath,
|
||||
config: configPath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the AWS directory exists with secure permissions
|
||||
* Creates the directory with 700 permissions (rwx for owner only)
|
||||
*/
|
||||
export function ensureAwsDirectoryExists(filePath: string): void {
|
||||
const dir = path.dirname(filePath);
|
||||
core.debug(`Ensuring directory exists: ${dir}`);
|
||||
mkdir(dir, 0o700);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate profile name format
|
||||
* Profile names must be non-empty, contain no whitespace, brackets, or path separators
|
||||
*/
|
||||
export function validateProfileName(profileName: string): void {
|
||||
if (!profileName || profileName.trim() === '') {
|
||||
throw new Error('aws-profile must not be empty');
|
||||
}
|
||||
|
||||
if (/\s/.test(profileName)) {
|
||||
throw new Error('aws-profile must not contain whitespace');
|
||||
}
|
||||
|
||||
// INI section names can't contain brackets
|
||||
if (/[[\]]/.test(profileName)) {
|
||||
throw new Error('aws-profile must not contain brackets');
|
||||
}
|
||||
|
||||
// Prevent path traversal
|
||||
if (profileName.includes('/') || profileName.includes('\\')) {
|
||||
throw new Error('aws-profile must not contain path separators');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a profile section into an INI file
|
||||
* Reads existing file, updates the specified section, and writes back
|
||||
*/
|
||||
export function mergeProfileSection(
|
||||
filePath: string,
|
||||
sectionName: string,
|
||||
data: Record<string, string>,
|
||||
overwriteAwsProfile: boolean,
|
||||
): void {
|
||||
const fileContent = readFileUtf8(filePath);
|
||||
const existingContent: Record<string, Record<string, string>> = fileContent === null ? {} : parseIni(fileContent);
|
||||
|
||||
if (existingContent[sectionName] && !overwriteAwsProfile) {
|
||||
throw new Error(
|
||||
`Profile with name "${sectionName}" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`,
|
||||
);
|
||||
}
|
||||
// Merge: update existing profile or add new one
|
||||
existingContent[sectionName] = data;
|
||||
|
||||
const content = stringifyIni(existingContent);
|
||||
|
||||
core.debug(`Writing profile to ${filePath}`);
|
||||
writeFileUtf8(filePath, content, 0o600);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write AWS profile files with credentials and configuration
|
||||
* This is the main entry point for profile file operations
|
||||
*
|
||||
* @param profileName - Name of the AWS profile to configure
|
||||
* @param credentials - AWS credentials (access key, secret key, session token)
|
||||
* @param region - AWS region
|
||||
*/
|
||||
export function writeProfileFiles(
|
||||
profileName: string,
|
||||
credentials: Partial<Credentials>,
|
||||
region: string,
|
||||
overwriteAwsProfile: boolean,
|
||||
): void {
|
||||
try {
|
||||
// Validate profile name
|
||||
validateProfileName(profileName);
|
||||
|
||||
const paths = getProfileFilePaths();
|
||||
|
||||
// Ensure .aws directory exists
|
||||
ensureAwsDirectoryExists(paths.credentials);
|
||||
ensureAwsDirectoryExists(paths.config);
|
||||
|
||||
// Prepare credentials data
|
||||
const credentialsData: Record<string, string> = {};
|
||||
if (credentials.AccessKeyId) {
|
||||
credentialsData.aws_access_key_id = credentials.AccessKeyId;
|
||||
}
|
||||
if (credentials.SecretAccessKey) {
|
||||
credentialsData.aws_secret_access_key = credentials.SecretAccessKey;
|
||||
}
|
||||
if (credentials.SessionToken) {
|
||||
credentialsData.aws_session_token = credentials.SessionToken;
|
||||
}
|
||||
|
||||
// Credentials file uses [profileName] syntax
|
||||
const credsSectionName = profileName;
|
||||
|
||||
// Config file uses [profile profileName] syntax, except for 'default'
|
||||
const configSectionName = profileName === 'default' ? 'default' : `profile ${profileName}`;
|
||||
|
||||
// Prepare config data
|
||||
const configData: Record<string, string> = {
|
||||
region: region,
|
||||
};
|
||||
|
||||
// Write to credentials file
|
||||
core.info(`Writing credentials to profile: ${profileName}`);
|
||||
mergeProfileSection(paths.credentials, credsSectionName, credentialsData, overwriteAwsProfile);
|
||||
|
||||
// Write to config file
|
||||
core.info(`Writing config to profile: ${profileName}`);
|
||||
mergeProfileSection(paths.config, configSectionName, configData, overwriteAwsProfile);
|
||||
|
||||
core.info(`✓ Successfully configured AWS profile: ${profileName}`);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to write AWS profile '${profileName}': ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- integ-tests
|
||||
|
||||
name: Integration Test
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Print current build ID
|
||||
run: |
|
||||
echo Integration test run: BUILD_ID
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
|
||||
- name: Test environment is configured with AWS credentials
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "user/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
|
||||
- name: Configure AWS credentials from role
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
role-to-assume: github-actions-configure-aws-credentials-integ-tests
|
||||
role-duration-seconds: 900
|
||||
role-session-name: github-actions-integ-test
|
||||
role-external-id: ${{ secrets.INTEG_TEST_ROLE_EXTERNAL_ID }}
|
||||
|
||||
- name: Test environment is configured with AWS credentials from role
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "role/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
|
||||
- name: Configure AWS credentials from role again
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-2
|
||||
role-to-assume: github-actions-configure-aws-credentials-integ-tests
|
||||
role-duration-seconds: 900
|
||||
role-session-name: github-actions-integ-test
|
||||
role-external-id: ${{ secrets.INTEG_TEST_ROLE_EXTERNAL_ID }}
|
||||
|
||||
- name: Test environment is configured with AWS credentials from role
|
||||
run: |
|
||||
aws sts get-caller-identity --query Arn | grep "role/github-actions-configure-aws-credentials-integ-tests" > /dev/null
|
||||
@@ -1,100 +0,0 @@
|
||||
import type * as http from 'node:http';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { type ProxyOptions, ProxyResolver } from '../src/ProxyResolver';
|
||||
|
||||
describe('ProxyResolver', () => {
|
||||
const mockReq = {} as http.ClientRequest;
|
||||
|
||||
test('returns http proxy for http URLs', () => {
|
||||
const options: ProxyOptions = { httpProxy: 'http://proxy:8080' };
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('http://proxy:8080');
|
||||
});
|
||||
|
||||
test('returns https proxy for https URLs', () => {
|
||||
const options: ProxyOptions = { httpsProxy: 'https://proxy:8080' };
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('https://example.com', mockReq)).toBe('https://proxy:8080');
|
||||
});
|
||||
|
||||
test('returns empty string when no proxy configured', () => {
|
||||
const resolver = new ProxyResolver({});
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
});
|
||||
|
||||
test('respects noProxy setting', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: 'example.com',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://other.com', mockReq)).toBe('http://proxy:8080');
|
||||
});
|
||||
|
||||
test('handles invalid URLs', () => {
|
||||
const resolver = new ProxyResolver({ httpProxy: 'http://proxy:8080' });
|
||||
|
||||
expect(resolver.getProxyForUrl('invalid-url', mockReq)).toBe('');
|
||||
});
|
||||
|
||||
test('handles wildcard noProxy', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: '*',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
});
|
||||
|
||||
test('handles comma-separated noProxy list', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: 'example.com,test.com',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://test.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://other.com', mockReq)).toBe('http://proxy:8080');
|
||||
});
|
||||
|
||||
test('handles port-specific noProxy', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: 'example.com:80',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://example.com:8080', mockReq)).toBe('http://proxy:8080');
|
||||
});
|
||||
|
||||
test('handles wildcard domain noProxy', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: '*.example.com',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://sub.example.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('http://proxy:8080');
|
||||
expect(resolver.getProxyForUrl('http://other.com', mockReq)).toBe('http://proxy:8080');
|
||||
});
|
||||
|
||||
test('handles empty noProxy entries', () => {
|
||||
const options: ProxyOptions = {
|
||||
httpProxy: 'http://proxy:8080',
|
||||
noProxy: 'example.com, ,test.com',
|
||||
};
|
||||
const resolver = new ProxyResolver(options);
|
||||
|
||||
expect(resolver.getProxyForUrl('http://example.com', mockReq)).toBe('');
|
||||
expect(resolver.getProxyForUrl('http://test.com', mockReq)).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { AssumeRoleWithWebIdentityCommand, GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
|
||||
import { mockClient } from 'aws-sdk-client-mock';
|
||||
import { fs, vol } from 'memfs';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import * as helpers from '../src/helpers';
|
||||
import { run } from '../src/index';
|
||||
import mocks from './mockinputs.test';
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('@actions/core');
|
||||
|
||||
const mockedSTSClient = mockClient(STSClient);
|
||||
|
||||
describe('assumeRoleWithWebIdentityTokenFile', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockedSTSClient.reset();
|
||||
vol.reset();
|
||||
helpers.withsleep(() => Promise.resolve());
|
||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.WEBIDENTITY_TOKEN_FILE_INPUTS));
|
||||
vi.mocked(core.getMultilineInput).mockReturnValue([]);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env = { ...mocks.envs };
|
||||
fs.mkdirSync('/home/github', { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
helpers.reset();
|
||||
});
|
||||
|
||||
it('refuses when the token file is a symlink and never calls STS', async () => {
|
||||
fs.mkdirSync('/etc', { recursive: true });
|
||||
fs.writeFileSync('/etc/passwd', 'root:x:0:0::/root:/bin/sh');
|
||||
fs.symlinkSync('/etc/passwd', '/home/github/file.txt');
|
||||
|
||||
await run();
|
||||
|
||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringMatching(/Refusing .* \(.* symbolic link\)/));
|
||||
expect(mockedSTSClient.commandCalls(AssumeRoleWithWebIdentityCommand)).toHaveLength(0);
|
||||
expect(fs.readFileSync('/etc/passwd', 'utf-8')).toBe('root:x:0:0::/root:/bin/sh');
|
||||
});
|
||||
|
||||
it('preserves the existing missing-file error when the token file does not exist', async () => {
|
||||
await run();
|
||||
|
||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Web identity token file does not exist'));
|
||||
expect(mockedSTSClient.commandCalls(AssumeRoleWithWebIdentityCommand)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('passes token contents to STS when the file is regular', async () => {
|
||||
fs.writeFileSync('/home/github/file.txt', 'real-token');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
|
||||
await run();
|
||||
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
const calls = mockedSTSClient.commandCalls(AssumeRoleWithWebIdentityCommand);
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]?.args[0].input.WebIdentityToken).toBe('real-token');
|
||||
});
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { STSClient } from '@aws-sdk/client-sts';
|
||||
import { mockClient } from 'aws-sdk-client-mock';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup } from '../src/cleanup';
|
||||
import mocks from './mockinputs.test';
|
||||
|
||||
vi.mock('@actions/core');
|
||||
|
||||
const mockedSTSClient = mockClient(STSClient);
|
||||
|
||||
describe('Configure AWS Credentials cleanup', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockedSTSClient.reset();
|
||||
vi.mocked(core.getInput).mockReturnValue('');
|
||||
process.env = {
|
||||
...mocks.envs,
|
||||
AWS_ACCESS_KEY_ID: 'CLEANUPTEST',
|
||||
AWS_SECRET_ACCESS_KEY: 'CLEANUPTEST',
|
||||
AWS_SESSION_TOKEN: 'CLEANUPTEST',
|
||||
AWS_REGION: 'CLEANUPTEST',
|
||||
AWS_DEFAULT_REGION: 'CLEANUPTEST',
|
||||
};
|
||||
});
|
||||
it('replaces AWS credential and region environment variables with empty strings', {}, () => {
|
||||
cleanup();
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(5);
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', '');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', '');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', '');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', '');
|
||||
});
|
||||
it('also clears AWS_PROFILE when aws-profile was set', {}, () => {
|
||||
vi.mocked(core.getInput).mockImplementation((name: string) => {
|
||||
if (name === 'aws-profile') return 'my-profile';
|
||||
if (name === 'output-env-credentials') return 'true';
|
||||
return '';
|
||||
});
|
||||
cleanup();
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(6);
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_PROFILE', '');
|
||||
});
|
||||
it('skips env cleanup when aws-profile is set without output-env-credentials', {}, () => {
|
||||
vi.mocked(core.getInput).mockImplementation((name: string) => {
|
||||
if (name === 'aws-profile') return 'my-profile';
|
||||
return '';
|
||||
});
|
||||
cleanup();
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
it('handles errors', {}, () => {
|
||||
vi.mocked(core.exportVariable).mockImplementationOnce(() => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
cleanup();
|
||||
expect(core.setFailed).toHaveBeenCalled();
|
||||
});
|
||||
it(`doesn't export credentials as empty env variables if asked not to`, {}, () => {
|
||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.NO_ENV_CREDS_INPUTS));
|
||||
cleanup();
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
@@ -1,267 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { fs, vol } from 'memfs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import * as helpers from '../src/helpers';
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('@actions/core');
|
||||
|
||||
describe('Configure AWS Credentials helpers', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
vol.reset();
|
||||
});
|
||||
it('removes brackets from GitHub Actor', {}, () => {
|
||||
const actor = 'actor[bot]';
|
||||
expect(helpers.sanitizeGitHubVariables(actor)).toBe('actor_bot_');
|
||||
});
|
||||
it('can sleep', {}, async () => {
|
||||
const sleep = helpers.defaultSleep(10);
|
||||
await expect(Promise.race([sleep, new Promise((_, reject) => setTimeout(reject, 20))])).resolves.toBe(undefined);
|
||||
});
|
||||
it('removes special characters from workflow names', {}, () => {
|
||||
expect(helpers.sanitizeGitHubVariables('sdf234@#$%$^&*()_+{}|:"<>?')).toEqual('sdf234@__________+___:____');
|
||||
});
|
||||
it("doesn't retry non-retryable errors", {}, async () => {
|
||||
const fn = vi.fn().mockRejectedValue('i am not retryable');
|
||||
await expect(helpers.retryAndBackoff(fn, false)).rejects.toMatch('i am not retryable');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('retries and logs with label at info level', {}, async () => {
|
||||
helpers.withsleep(() => Promise.resolve());
|
||||
const fn = vi.fn().mockRejectedValueOnce(new Error('transient')).mockResolvedValueOnce('success');
|
||||
const result = await helpers.retryAndBackoff(fn, true, 3, 0, 50, 'TestOp');
|
||||
expect(result).toBe('success');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Retry TestOp: attempt 1 of 3 failed'));
|
||||
helpers.reset();
|
||||
});
|
||||
it('logs max retries reached with label', {}, async () => {
|
||||
helpers.withsleep(() => Promise.resolve());
|
||||
const fn = vi.fn().mockRejectedValue(new Error('persistent'));
|
||||
await expect(helpers.retryAndBackoff(fn, true, 2, 0, 50, 'TestOp')).rejects.toThrow('persistent');
|
||||
expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Retry TestOp: reached max retries (2)'));
|
||||
helpers.reset();
|
||||
});
|
||||
it('retries without a label (backward compat)', {}, async () => {
|
||||
helpers.withsleep(() => Promise.resolve());
|
||||
const fn = vi.fn().mockRejectedValueOnce(new Error('transient')).mockResolvedValueOnce('ok');
|
||||
await helpers.retryAndBackoff(fn, true, 3);
|
||||
expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Retry: attempt 1 of 3 failed'));
|
||||
helpers.reset();
|
||||
});
|
||||
it('can output creds when told to', {}, () => {
|
||||
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
|
||||
helpers.exportCredentials(
|
||||
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
||||
true,
|
||||
true,
|
||||
);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(4);
|
||||
expect(core.setSecret).toHaveBeenCalledTimes(3);
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
it('can unset credentials', {}, () => {
|
||||
const env = process.env;
|
||||
helpers.unsetCredentials();
|
||||
expect(process.env.AWS_ACCESS_KEY_ID).toBeUndefined;
|
||||
expect(process.env.AWS_SECRET_ACCESS_KEY).toBeUndefined;
|
||||
expect(process.env.AWS_SESSION_TOKEN).toBeUndefined;
|
||||
expect(process.env.AWS_REGION).toBeUndefined;
|
||||
expect(process.env.AWS_DEFAULT_REGION).toBeUndefined;
|
||||
process.env = env;
|
||||
});
|
||||
it(`won't output credentials to env if told not to`, {}, () => {
|
||||
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
|
||||
helpers.exportCredentials(
|
||||
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
||||
true,
|
||||
false,
|
||||
);
|
||||
helpers.unsetCredentials(false);
|
||||
helpers.exportRegion('fake-test-region', false);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(4);
|
||||
expect(core.setSecret).toHaveBeenCalledTimes(3);
|
||||
expect(core.exportVariable).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('verifies credentials without special characters', {}, () => {
|
||||
expect(helpers.verifyKeys({ AccessKeyId: 'AKIATEST', SecretAccessKey: 'secretkey' })).toBe(true);
|
||||
expect(helpers.verifyKeys({ AccessKeyId: 'AKIA!@#$', SecretAccessKey: 'secret' })).toBe(false);
|
||||
expect(helpers.verifyKeys(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('translates environment variables', {}, () => {
|
||||
process.env.AWS_REGION = 'us-east-1';
|
||||
process.env.HTTPS_PROXY = 'https://proxy:8080';
|
||||
helpers.translateEnvVariables();
|
||||
expect(process.env['INPUT_AWS-REGION']).toBe('us-east-1');
|
||||
expect(process.env.HTTP_PROXY).toBe('https://proxy:8080');
|
||||
});
|
||||
|
||||
it('handles getBooleanInput correctly', {}, () => {
|
||||
vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
expect(helpers.getBooleanInput('test')).toBe(true);
|
||||
|
||||
vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
expect(helpers.getBooleanInput('test')).toBe(false);
|
||||
|
||||
vi.spyOn(core, 'getInput').mockReturnValue('');
|
||||
expect(helpers.getBooleanInput('test', { default: true })).toBe(true);
|
||||
|
||||
vi.spyOn(core, 'getInput').mockReturnValue('invalid');
|
||||
expect(() => helpers.getBooleanInput('test')).toThrow();
|
||||
});
|
||||
|
||||
it('clears session token when not provided', {}, () => {
|
||||
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
|
||||
process.env.AWS_SESSION_TOKEN = 'old-token';
|
||||
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test' }, false, true);
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
||||
});
|
||||
|
||||
describe('filesystem helpers', {}, () => {
|
||||
describe('isSymlink', {}, () => {
|
||||
it('returns true for a symlink', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/target', 'data');
|
||||
fs.symlinkSync('/dir/target', '/dir/link');
|
||||
expect(helpers.isSymlink('/dir/link')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a regular file', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/file', 'data');
|
||||
expect(helpers.isSymlink('/dir/file')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for a missing path', {}, () => {
|
||||
expect(helpers.isSymlink('/nonexistent')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFileUtf8', {}, () => {
|
||||
it('returns content for a regular file', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/file', 'hello');
|
||||
expect(helpers.readFileUtf8('/dir/file')).toBe('hello');
|
||||
});
|
||||
|
||||
it('returns null when the file does not exist', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
expect(helpers.readFileUtf8('/dir/missing')).toBe(null);
|
||||
});
|
||||
|
||||
it('refuses to read through a symlink at the target', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/secret', 'sensitive');
|
||||
fs.symlinkSync('/dir/secret', '/dir/link');
|
||||
expect(() => helpers.readFileUtf8('/dir/link')).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
});
|
||||
|
||||
it('refuses to read when the parent directory is a symlink', {}, () => {
|
||||
fs.mkdirSync('/real/.aws', { recursive: true });
|
||||
fs.writeFileSync('/real/.aws/credentials', 'data');
|
||||
fs.mkdirSync('/home', { recursive: true });
|
||||
fs.symlinkSync('/real/.aws', '/home/.aws');
|
||||
expect(() => helpers.readFileUtf8('/home/.aws/credentials')).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
});
|
||||
|
||||
it('refuses to read when the path is a directory', {}, () => {
|
||||
fs.mkdirSync('/dir/subdir', { recursive: true });
|
||||
expect(() => helpers.readFileUtf8('/dir/subdir')).toThrow(/not a regular file/);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'follows the kubelet projected-token symlink chain at /var/run/secrets/*/serviceaccount/token',
|
||||
() => {
|
||||
fs.mkdirSync('/var/run/secrets/eks.amazonaws.com/serviceaccount/..2026_05_28_00_00_00.123', {
|
||||
recursive: true,
|
||||
});
|
||||
fs.writeFileSync(
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/..2026_05_28_00_00_00.123/token',
|
||||
'jwt-token',
|
||||
);
|
||||
fs.symlinkSync('..2026_05_28_00_00_00.123', '/var/run/secrets/eks.amazonaws.com/serviceaccount/..data');
|
||||
fs.symlinkSync('..data/token', '/var/run/secrets/eks.amazonaws.com/serviceaccount/token');
|
||||
expect(helpers.readFileUtf8('/var/run/secrets/eks.amazonaws.com/serviceaccount/token')).toBe('jwt-token');
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === 'win32')('still refuses symlinks at lookalike paths outside the allowlist', () => {
|
||||
fs.mkdirSync('/var/run/secrets/eks.amazonaws.com/serviceaccount', { recursive: true });
|
||||
fs.writeFileSync('/var/run/secrets/eks.amazonaws.com/serviceaccount/secret', 'jwt-token');
|
||||
fs.symlinkSync(
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/secret',
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/token2',
|
||||
);
|
||||
expect(() => helpers.readFileUtf8('/var/run/secrets/eks.amazonaws.com/serviceaccount/token2')).toThrow(
|
||||
/Refusing .* \(.* symbolic link\)/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAllowListed', {}, () => {
|
||||
it.skipIf(process.platform === 'win32')('matches the canonical kubelet projected-token path', () => {
|
||||
expect(helpers.isAllowListed('/var/run/secrets/eks.amazonaws.com/serviceaccount/token')).toBe(true);
|
||||
expect(helpers.isAllowListed('/var/run/secrets/kubernetes.io/serviceaccount/token')).toBe(true);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('rejects nested or unrelated paths', () => {
|
||||
expect(helpers.isAllowListed('/var/run/secrets/serviceaccount/token')).toBe(false);
|
||||
expect(helpers.isAllowListed('/var/run/secrets/a/b/serviceaccount/token')).toBe(false);
|
||||
expect(helpers.isAllowListed('/var/run/secrets/eks.amazonaws.com/serviceaccount/token2')).toBe(false);
|
||||
expect(helpers.isAllowListed('/etc/var/run/secrets/foo/serviceaccount/token')).toBe(false);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('normalizes path traversal attempts', () => {
|
||||
expect(helpers.isAllowListed('/var/run/secrets/foo/serviceaccount/../../../../etc/passwd')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeFileUtf8', {}, () => {
|
||||
it('writes content with the specified mode', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
helpers.writeFileUtf8('/dir/file', 'payload', 0o600);
|
||||
expect(fs.readFileSync('/dir/file', 'utf-8')).toBe('payload');
|
||||
expect(fs.statSync('/dir/file').mode & 0o777).toBe(0o600);
|
||||
});
|
||||
|
||||
it('refuses to follow a symlink at the target and leaves the target file untouched', {}, () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/target', 'original');
|
||||
fs.symlinkSync('/dir/target', '/dir/link');
|
||||
expect(() => helpers.writeFileUtf8('/dir/link', 'attacker', 0o600)).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
expect(fs.readFileSync('/dir/target', 'utf-8')).toBe('original');
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('tightens mode on existing files', () => {
|
||||
fs.mkdirSync('/dir', { recursive: true });
|
||||
fs.writeFileSync('/dir/file', 'old', { mode: 0o644 });
|
||||
helpers.writeFileUtf8('/dir/file', 'new', 0o600);
|
||||
expect(fs.statSync('/dir/file').mode & 0o777).toBe(0o600);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mkdir', {}, () => {
|
||||
it('is idempotent on a regular directory', {}, () => {
|
||||
helpers.mkdir('/some/nested/dir', 0o700);
|
||||
helpers.mkdir('/some/nested/dir', 0o700);
|
||||
expect(fs.statSync('/some/nested/dir').isDirectory()).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses when the target directory is a symlink', {}, () => {
|
||||
fs.mkdirSync('/real', { recursive: true });
|
||||
fs.mkdirSync('/home', { recursive: true });
|
||||
fs.symlinkSync('/real', '/home/.aws');
|
||||
expect(() => helpers.mkdir('/home/.aws', 0o700)).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
-1529
File diff suppressed because it is too large
Load Diff
@@ -1,169 +0,0 @@
|
||||
import type * as core from '@actions/core';
|
||||
|
||||
const inputs = {
|
||||
GH_OIDC_INPUTS: {
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'special-characters-workaround': 'true',
|
||||
},
|
||||
CUSTOM_TAGS_INVALID_JSON_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'retry-max-attempts': '1',
|
||||
'custom-tags': 'not a json',
|
||||
},
|
||||
CUSTOM_TAGS_ARRAY_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'retry-max-attempts': '1',
|
||||
'custom-tags': '[1, 2, 3]',
|
||||
},
|
||||
CUSTOM_TAGS_RESERVED_KEY_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'retry-max-attempts': '1',
|
||||
'custom-tags': JSON.stringify({ Repository: 'evil-repo' }),
|
||||
},
|
||||
CUSTOM_TAGS_INVALID_KEY_CHARS_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'retry-max-attempts': '1',
|
||||
'custom-tags': JSON.stringify({ 'invalid{key}': 'value' }),
|
||||
},
|
||||
CUSTOM_TAGS_OBJECT_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'retry-max-attempts': '1',
|
||||
'custom-tags': JSON.stringify({ Environment: 'Production', Team: 'DevOps' }),
|
||||
},
|
||||
IAM_USER_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'aws-region': 'fake-region-1',
|
||||
},
|
||||
IAM_ASSUMEROLE_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
},
|
||||
WEBIDENTITY_TOKEN_FILE_INPUTS: {
|
||||
'web-identity-token-file': 'file.txt',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
},
|
||||
EXISTING_ROLE_INPUTS: {
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'role-chaining': 'true',
|
||||
'aws-region': 'fake-region-1',
|
||||
},
|
||||
USE_EXISTING_CREDENTIALS_INPUTS: {
|
||||
'aws-region': 'fake-region-1',
|
||||
'use-existing-credentials': 'true',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
},
|
||||
NO_ENV_CREDS_INPUTS: {
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'output-env-credentials': 'false',
|
||||
},
|
||||
STEP_BUT_NO_ENV_INPUTS: {
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'output-env-credentials': 'false',
|
||||
'output-credentials': 'true',
|
||||
},
|
||||
IAM_ASSUMEROLE_NO_ENV_INPUTS: {
|
||||
'aws-access-key-id': 'MYAWSACCESSKEYID',
|
||||
'aws-secret-access-key': 'MYAWSSECRETACCESSKEY',
|
||||
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
'aws-region': 'fake-region-1',
|
||||
'output-env-credentials': 'false',
|
||||
'output-credentials': 'true',
|
||||
},
|
||||
};
|
||||
|
||||
const envs = {
|
||||
GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
|
||||
GITHUB_WORKFLOW: 'MY-WORKFLOW-ID',
|
||||
GITHUB_ACTION: 'MY-ACTION-NAME',
|
||||
GITHUB_ACTOR: 'MY-USERNAME[bot]',
|
||||
GITHUB_SHA: 'MY-COMMIT-ID',
|
||||
GITHUB_WORKSPACE: '/home/github',
|
||||
GITHUB_ACTIONS: 'true',
|
||||
GITHUB_REF: 'refs/pull/42/merge',
|
||||
GITHUB_EVENT_NAME: 'pull_request',
|
||||
GITHUB_RUN_ID: '16412345678',
|
||||
GITHUB_JOB: 'build',
|
||||
GITHUB_BASE_REF: 'main',
|
||||
GITHUB_HEAD_REF: 'feature-branch',
|
||||
GITHUB_TRIGGERING_ACTOR: 'MY-USERNAME[bot]',
|
||||
};
|
||||
|
||||
const outputs = {
|
||||
STS_CREDENTIALS: {
|
||||
Credentials: {
|
||||
AccessKeyId: 'STSAWSACCESSKEYID',
|
||||
SecretAccessKey: 'STSAWSSECRETACCESSKEY',
|
||||
SessionToken: 'STSAWSSESSIONTOKEN',
|
||||
Expiration: new Date(8640000000000000),
|
||||
},
|
||||
AssumedRoleUser: {
|
||||
Arn: 'arn:aws:sts::111111111111:assumed-role/MY-ROLE/',
|
||||
AssumedRoleId: 'AROAFAKEASSUMEDROLEID',
|
||||
},
|
||||
},
|
||||
GET_CALLER_IDENTITY: {
|
||||
Account: '111111111111',
|
||||
Arn: 'arn:aws:iam::111111111111:role/MY-ROLE',
|
||||
},
|
||||
FAKE_STS_ACCESS_KEY_ID: 'STSAWSACCESSKEYID',
|
||||
FAKE_STS_SECRET_ACCESS_KEY: 'STSAWSSECRETACCESSKEY',
|
||||
FAKE_STS_SESSION_TOKEN: 'STSAWSSESSIONTOKEN',
|
||||
ODD_CHARACTER_CREDENTIALS: {
|
||||
Credentials: {
|
||||
AccessKeyId: 'STSA#$%^&',
|
||||
SecretAccessKey: 'STSA#$%^&Key',
|
||||
SessionToken: 'STSA#$%^',
|
||||
Expiration: new Date(8640000000000000),
|
||||
},
|
||||
AssumedRoleUser: {
|
||||
Arn: 'arn:aws:sts::111111111111:assumed-role/MY-ROLE/',
|
||||
AssumedRoleId: 'AROAFAKEASSUMEDROLEID',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
getInput: (fakeEnv: Record<string, string>) => {
|
||||
return (name: string, options?: core.InputOptions): string => {
|
||||
if (!fakeEnv[name]) {
|
||||
if (options?.required) throw new Error(`Input ${name} not found`);
|
||||
return '';
|
||||
}
|
||||
return fakeEnv[name];
|
||||
};
|
||||
},
|
||||
getMultilineInput: (fakeEnv: Record<string, string[]>) => {
|
||||
return (name: string, options?: core.InputOptions): string[] => {
|
||||
if (!fakeEnv[name]) {
|
||||
if (options?.required) throw new Error(`Input ${name} not found`);
|
||||
return [];
|
||||
}
|
||||
return fakeEnv[name];
|
||||
};
|
||||
},
|
||||
...inputs,
|
||||
outputs,
|
||||
envs,
|
||||
} as const;
|
||||
@@ -1,804 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { fs, vol } from 'memfs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ensureAwsDirectoryExists,
|
||||
getProfileFilePaths,
|
||||
mergeProfileSection,
|
||||
parseIni,
|
||||
stringifyIni,
|
||||
validateProfileName,
|
||||
writeProfileFiles,
|
||||
} from '../src/profileManager';
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('@actions/core');
|
||||
|
||||
describe('Profile Manager', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe('parseIni', {}, () => {
|
||||
it('parses a single section', {}, () => {
|
||||
const result = parseIni('[dev]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n');
|
||||
expect(result.dev).toEqual({ aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' });
|
||||
});
|
||||
|
||||
it('parses multiple sections', {}, () => {
|
||||
const result = parseIni('[dev]\nkey=dev_val\n\n[prod]\nkey=prod_val\n');
|
||||
expect(result.dev.key).toBe('dev_val');
|
||||
expect(result.prod.key).toBe('prod_val');
|
||||
});
|
||||
|
||||
it('skips comments and empty lines', {}, () => {
|
||||
const result = parseIni('# comment\n; another comment\n\n[dev]\nkey=val\n');
|
||||
expect(result.dev).toEqual({ key: 'val' });
|
||||
});
|
||||
|
||||
it('trims whitespace around keys and values', {}, () => {
|
||||
const result = parseIni('[dev]\n key = val \n');
|
||||
expect(result.dev.key).toBe('val');
|
||||
});
|
||||
|
||||
it('preserves section names with spaces (e.g. profile prefix)', {}, () => {
|
||||
const result = parseIni('[profile dev]\nregion=us-east-1\n');
|
||||
expect(result['profile dev']).toEqual({ region: 'us-east-1' });
|
||||
});
|
||||
|
||||
it('guards against __proto__ section pollution', {}, () => {
|
||||
const result = parseIni('[__proto__]\npolluted=true\n[safe]\nkey=val\n');
|
||||
expect(result.__proto__).not.toHaveProperty('polluted');
|
||||
expect(result.safe).toEqual({ key: 'val' });
|
||||
});
|
||||
|
||||
it('guards against __proto__ key pollution', {}, () => {
|
||||
const result = parseIni('[dev]\n__proto__=evil\naws_access_key_id=AKIA\n');
|
||||
expect(result.dev).toEqual({ aws_access_key_id: 'AKIA' });
|
||||
expect(result.dev).not.toHaveProperty('__proto__', 'evil');
|
||||
});
|
||||
|
||||
it('handles values containing equals signs', {}, () => {
|
||||
const result = parseIni('[dev]\naws_session_token=FwoGZXIvYXdzEBYa/base64==\n');
|
||||
expect(result.dev.aws_session_token).toBe('FwoGZXIvYXdzEBYa/base64==');
|
||||
});
|
||||
|
||||
it('handles empty values', {}, () => {
|
||||
const result = parseIni('[dev]\ncli_pager=\n');
|
||||
expect(result.dev.cli_pager).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty object for empty input', {}, () => {
|
||||
expect(parseIni('')).toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty object for whitespace-only input', {}, () => {
|
||||
expect(parseIni(' \n\n \n')).toEqual({});
|
||||
});
|
||||
|
||||
it('handles Windows line endings (CRLF)', {}, () => {
|
||||
const result = parseIni('[dev]\r\naws_access_key_id=AKIA\r\naws_secret_access_key=secret\r\n');
|
||||
expect(result.dev).toEqual({ aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringifyIni', {}, () => {
|
||||
it('serializes a single section', {}, () => {
|
||||
const result = stringifyIni({ dev: { key: 'val' } });
|
||||
expect(result).toBe('[dev]\nkey = val\n');
|
||||
});
|
||||
|
||||
it('serializes multiple sections with blank line separator', {}, () => {
|
||||
const result = stringifyIni({ dev: { a: '1' }, prod: { b: '2' } });
|
||||
expect(result).toBe('[dev]\na = 1\n\n[prod]\nb = 2\n');
|
||||
});
|
||||
|
||||
it('round-trips through parseIni', {}, () => {
|
||||
const data = {
|
||||
dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' },
|
||||
'profile prod': { region: 'us-west-2' },
|
||||
};
|
||||
const roundTripped = parseIni(stringifyIni(data));
|
||||
expect(roundTripped).toEqual(data);
|
||||
});
|
||||
|
||||
it('handles empty data object', {}, () => {
|
||||
const result = stringifyIni({});
|
||||
expect(result).toBe('\n');
|
||||
expect(parseIni(result)).toEqual({});
|
||||
});
|
||||
|
||||
it('handles section with no keys', {}, () => {
|
||||
const result = stringifyIni({ dev: {} });
|
||||
expect(result).toBe('[dev]\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateProfileName', {}, () => {
|
||||
it('accepts valid profile names', {}, () => {
|
||||
expect(() => validateProfileName('dev')).not.toThrow();
|
||||
expect(() => validateProfileName('production')).not.toThrow();
|
||||
expect(() => validateProfileName('my-profile-123')).not.toThrow();
|
||||
expect(() => validateProfileName('default')).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty profile names', {}, () => {
|
||||
expect(() => validateProfileName('')).toThrow('aws-profile must not be empty');
|
||||
expect(() => validateProfileName(' ')).toThrow('aws-profile must not be empty');
|
||||
});
|
||||
|
||||
it('rejects profile names with whitespace', {}, () => {
|
||||
expect(() => validateProfileName('my profile')).toThrow('aws-profile must not contain whitespace');
|
||||
expect(() => validateProfileName('dev\ntest')).toThrow('aws-profile must not contain whitespace');
|
||||
expect(() => validateProfileName('prod\tenv')).toThrow('aws-profile must not contain whitespace');
|
||||
});
|
||||
|
||||
it('rejects profile names with brackets', {}, () => {
|
||||
expect(() => validateProfileName('dev[test]')).toThrow('aws-profile must not contain brackets');
|
||||
expect(() => validateProfileName('[profile]')).toThrow('aws-profile must not contain brackets');
|
||||
});
|
||||
|
||||
it('rejects profile names with path separators', {}, () => {
|
||||
expect(() => validateProfileName('dev/test')).toThrow('aws-profile must not contain path separators');
|
||||
expect(() => validateProfileName('dev\\test')).toThrow('aws-profile must not contain path separators');
|
||||
expect(() => validateProfileName('../etc/passwd')).toThrow('aws-profile must not contain path separators');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProfileFilePaths', {}, () => {
|
||||
it('returns default paths when env vars not set', {}, () => {
|
||||
delete process.env.AWS_SHARED_CREDENTIALS_FILE;
|
||||
delete process.env.AWS_CONFIG_FILE;
|
||||
|
||||
const paths = getProfileFilePaths();
|
||||
|
||||
expect(paths.credentials).toMatch(/\.aws[/\\]credentials$/);
|
||||
expect(paths.config).toMatch(/\.aws[/\\]config$/);
|
||||
});
|
||||
|
||||
it('respects AWS_SHARED_CREDENTIALS_FILE env var', {}, () => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/path/credentials';
|
||||
process.env.AWS_CONFIG_FILE = '/custom/path/config';
|
||||
|
||||
const paths = getProfileFilePaths();
|
||||
|
||||
expect(paths.credentials).toBe('/custom/path/credentials');
|
||||
expect(paths.config).toBe('/custom/path/config');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureAwsDirectoryExists', {}, () => {
|
||||
it('creates directory if it does not exist', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
|
||||
ensureAwsDirectoryExists(filePath);
|
||||
|
||||
expect(fs.existsSync('/home/runner/.aws')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not error if directory already exists', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
expect(() => ensureAwsDirectoryExists(filePath)).not.toThrow();
|
||||
});
|
||||
|
||||
it('creates nested directories', {}, () => {
|
||||
const filePath = '/home/runner/custom/path/.aws/credentials';
|
||||
|
||||
ensureAwsDirectoryExists(filePath);
|
||||
|
||||
expect(fs.existsSync('/home/runner/custom/path/.aws')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeProfileSection', {}, () => {
|
||||
it('creates new file with profile section', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
|
||||
aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev).toBeDefined();
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
});
|
||||
|
||||
it('merges with existing profiles', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create initial profile
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
|
||||
aws_secret_access_key: 'devSecretKey',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// Add second profile
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'prod',
|
||||
{
|
||||
aws_access_key_id: 'AKIAPRODEXAMPLE',
|
||||
aws_secret_access_key: 'prodSecretKey',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev).toBeDefined();
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(parsed.prod).toBeDefined();
|
||||
expect(parsed.prod.aws_access_key_id).toBe('AKIAPRODEXAMPLE');
|
||||
});
|
||||
|
||||
it('overwrites existing profile with same name', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create initial profile
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'OLD_KEY',
|
||||
aws_secret_access_key: 'oldSecretKey',
|
||||
aws_session_token: 'oldSessionToken',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// Overwrite with new credentials
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'NEW_KEY',
|
||||
aws_secret_access_key: 'newSecretKey',
|
||||
aws_session_token: 'newSessionToken',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev.aws_access_key_id).toBe('NEW_KEY');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('newSecretKey');
|
||||
expect(parsed.dev.aws_session_token).toBe('newSessionToken');
|
||||
});
|
||||
|
||||
it('overwriting a profile removes stale keys', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create profile with session token
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'AKIA',
|
||||
aws_secret_access_key: 'secret',
|
||||
aws_session_token: 'old-token',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// Overwrite without session token
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'AKIA2',
|
||||
aws_secret_access_key: 'secret2',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIA2');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('secret2');
|
||||
expect(parsed.dev.aws_session_token).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles empty existing file', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
fs.writeFileSync(filePath, '', { mode: 0o600 });
|
||||
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'AKIA',
|
||||
aws_secret_access_key: 'secret',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIA');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('secret');
|
||||
});
|
||||
|
||||
it('errors if profile name already exists but overwrite flag is false', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create initial profile
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'OLD_KEY',
|
||||
aws_secret_access_key: 'oldSecretKey',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// Overwrite with new credentials
|
||||
expect(() =>
|
||||
mergeProfileSection(
|
||||
filePath,
|
||||
'dev',
|
||||
{
|
||||
aws_access_key_id: 'NEW_KEY',
|
||||
aws_secret_access_key: 'newSecretKey',
|
||||
aws_session_token: 'sessionToken',
|
||||
},
|
||||
false,
|
||||
),
|
||||
).toThrow(
|
||||
`Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`,
|
||||
);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.dev.aws_access_key_id).toBe('OLD_KEY');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('oldSecretKey');
|
||||
expect(parsed.dev.aws_session_token).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeProfileFiles', {}, () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.AWS_SHARED_CREDENTIALS_FILE;
|
||||
delete process.env.AWS_CONFIG_FILE;
|
||||
});
|
||||
|
||||
it('writes credentials and config for new profile', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
// Check credentials file
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = parseIni(credContent);
|
||||
|
||||
expect(credParsed.dev).toBeDefined();
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
expect(credParsed.dev.aws_session_token).toBe('FwoGZXIvYXdzEBYaDEXAMPLE');
|
||||
|
||||
// Check config file
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = parseIni(configContent);
|
||||
|
||||
expect(configParsed['profile dev']).toBeDefined();
|
||||
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
||||
});
|
||||
|
||||
it('uses correct section naming for default profile', {}, () => {
|
||||
writeProfileFiles(
|
||||
'default',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
},
|
||||
'us-west-2',
|
||||
false,
|
||||
);
|
||||
|
||||
// Check credentials file uses [default]
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = parseIni(credContent);
|
||||
|
||||
expect(credParsed.default).toBeDefined();
|
||||
expect(credParsed['profile default']).toBeUndefined();
|
||||
|
||||
// Check config file uses [default] (not [profile default])
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = parseIni(configContent);
|
||||
|
||||
expect(configParsed.default).toBeDefined();
|
||||
expect(configParsed['profile default']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('supports multiple profiles', {}, () => {
|
||||
// Write first profile
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIADEV',
|
||||
SecretAccessKey: 'devSecret',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
// Write second profile
|
||||
writeProfileFiles(
|
||||
'prod',
|
||||
{
|
||||
AccessKeyId: 'AKIAPROD',
|
||||
SecretAccessKey: 'prodSecret',
|
||||
SessionToken: 'prodToken',
|
||||
},
|
||||
'us-west-2',
|
||||
false,
|
||||
);
|
||||
|
||||
// Verify both profiles exist
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = parseIni(credContent);
|
||||
|
||||
expect(credParsed.dev).toBeDefined();
|
||||
expect(credParsed.prod).toBeDefined();
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIADEV');
|
||||
expect(credParsed.prod.aws_access_key_id).toBe('AKIAPROD');
|
||||
});
|
||||
|
||||
it('handles credentials without session token', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = parseIni(credContent);
|
||||
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
expect(credParsed.dev.aws_session_token).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws error for invalid profile name', {}, () => {
|
||||
expect(() =>
|
||||
writeProfileFiles(
|
||||
'invalid profile',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
),
|
||||
).toThrow('Failed to write AWS profile');
|
||||
expect(() =>
|
||||
writeProfileFiles(
|
||||
'invalid profile',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
),
|
||||
).toThrow('whitespace');
|
||||
});
|
||||
|
||||
it('respects custom file paths from env vars', {}, () => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/credentials';
|
||||
process.env.AWS_CONFIG_FILE = '/custom/config';
|
||||
|
||||
fs.mkdirSync('/custom', { recursive: true });
|
||||
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
expect(fs.existsSync('/custom/credentials')).toBe(true);
|
||||
expect(fs.existsSync('/custom/config')).toBe(true);
|
||||
});
|
||||
|
||||
it('logs info messages', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev');
|
||||
});
|
||||
|
||||
it('preserves pre-existing unrelated profiles in credentials file', {}, () => {
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
credsPath,
|
||||
'[personal]\naws_access_key_id=AKIAPERSONAL\naws_secret_access_key=personalSecret\naws_session_token=personalToken\n',
|
||||
);
|
||||
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
|
||||
|
||||
const content = fs.readFileSync(credsPath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
// Pre-existing profile must be fully intact
|
||||
expect(parsed.personal).toEqual({
|
||||
aws_access_key_id: 'AKIAPERSONAL',
|
||||
aws_secret_access_key: 'personalSecret',
|
||||
aws_session_token: 'personalToken',
|
||||
});
|
||||
// New profile also present
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIADEV');
|
||||
});
|
||||
|
||||
it('preserves pre-existing config with extra keys', {}, () => {
|
||||
const configPath = getProfileFilePaths().config;
|
||||
fs.mkdirSync(require('node:path').dirname(configPath), { recursive: true });
|
||||
fs.writeFileSync(configPath, '[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n');
|
||||
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false);
|
||||
|
||||
const content = fs.readFileSync(configPath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed['profile personal']).toEqual({
|
||||
region: 'eu-west-1',
|
||||
output: 'json',
|
||||
cli_pager: '',
|
||||
});
|
||||
expect(parsed['profile dev'].region).toBe('us-east-1');
|
||||
});
|
||||
|
||||
it('preserves pre-existing default profile when writing a named profile', {}, () => {
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true });
|
||||
fs.writeFileSync(credsPath, '[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n');
|
||||
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-west-2', false);
|
||||
|
||||
const content = fs.readFileSync(credsPath, 'utf-8');
|
||||
const parsed = parseIni(content);
|
||||
|
||||
expect(parsed.default).toEqual({
|
||||
aws_access_key_id: 'AKIADEFAULT',
|
||||
aws_secret_access_key: 'defaultSecret',
|
||||
});
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIADEV');
|
||||
});
|
||||
|
||||
it('comments in pre-existing files are stripped on round-trip', {}, () => {
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
credsPath,
|
||||
'# My important comment\n[personal]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n',
|
||||
);
|
||||
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
|
||||
|
||||
const content = fs.readFileSync(credsPath, 'utf-8') as string;
|
||||
|
||||
// Comment is lost (known trade-off), but profile data is preserved
|
||||
expect(content).not.toContain('# My important comment');
|
||||
const parsed = parseIni(content);
|
||||
expect(parsed.personal.aws_access_key_id).toBe('AKIA');
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIADEV');
|
||||
});
|
||||
|
||||
it('writes empty section when credentials object has no keys', {}, () => {
|
||||
writeProfileFiles('dev', {}, 'us-east-1', false);
|
||||
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = parseIni(credContent);
|
||||
|
||||
// Section exists but has no credential keys
|
||||
expect(credParsed.dev).toEqual({});
|
||||
|
||||
// Config still gets region
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = parseIni(configContent);
|
||||
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
||||
});
|
||||
|
||||
it('resolves credentials and config paths independently from env vars', {}, () => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom-creds/credentials';
|
||||
// AWS_CONFIG_FILE is NOT set — should use default path
|
||||
|
||||
fs.mkdirSync('/custom-creds', { recursive: true });
|
||||
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false);
|
||||
|
||||
expect(fs.existsSync('/custom-creds/credentials')).toBe(true);
|
||||
// Config file should be at the default path (under homedir)
|
||||
const defaultConfigPath = require('node:path').join(require('node:os').homedir(), '.aws', 'config');
|
||||
expect(fs.existsSync(defaultConfigPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('produces AWS CLI-compatible INI output (golden file)', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE',
|
||||
},
|
||||
'us-east-1',
|
||||
false,
|
||||
);
|
||||
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
|
||||
// Verify exact byte-for-byte format matching AWS CLI style:
|
||||
// - [section] header on its own line
|
||||
// - key = value with spaces around =
|
||||
// - LF line endings, trailing newline
|
||||
expect(credContent).toBe(
|
||||
'[dev]\n' +
|
||||
'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' +
|
||||
'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' +
|
||||
'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n',
|
||||
);
|
||||
expect(configContent).toBe('[profile dev]\n' + 'region = us-east-1\n');
|
||||
});
|
||||
|
||||
it('golden file for multi-profile output', {}, () => {
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
|
||||
writeProfileFiles(
|
||||
'prod',
|
||||
{ AccessKeyId: 'AKIAPROD', SecretAccessKey: 'prodSecret', SessionToken: 'prodToken' },
|
||||
'us-west-2',
|
||||
false,
|
||||
);
|
||||
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
|
||||
expect(credContent).toBe(
|
||||
'[dev]\n' +
|
||||
'aws_access_key_id = AKIADEV\n' +
|
||||
'aws_secret_access_key = devSecret\n' +
|
||||
'\n' +
|
||||
'[prod]\n' +
|
||||
'aws_access_key_id = AKIAPROD\n' +
|
||||
'aws_secret_access_key = prodSecret\n' +
|
||||
'aws_session_token = prodToken\n',
|
||||
);
|
||||
expect(configContent).toBe(
|
||||
'[profile dev]\n' + 'region = us-east-1\n' + '\n' + '[profile prod]\n' + 'region = us-west-2\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('symlink hardening', {}, () => {
|
||||
const credsPath = '/home/user/.aws/credentials';
|
||||
const configPath = '/home/user/.aws/config';
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = credsPath;
|
||||
process.env.AWS_CONFIG_FILE = configPath;
|
||||
});
|
||||
|
||||
it('mergeProfileSection refuses when the credentials path is a symlink and leaves the target unchanged', {}, () => {
|
||||
fs.mkdirSync('/home/user/.aws', { recursive: true });
|
||||
fs.mkdirSync('/etc', { recursive: true });
|
||||
fs.writeFileSync('/etc/passwd', 'root:x:0:0::/root:/bin/sh');
|
||||
fs.symlinkSync('/etc/passwd', credsPath);
|
||||
|
||||
expect(() => mergeProfileSection(credsPath, 'dev', { aws_access_key_id: 'AKIA' }, true)).toThrow(
|
||||
/Refusing .* \(.* symbolic link\)/,
|
||||
);
|
||||
expect(fs.readFileSync('/etc/passwd', 'utf-8')).toBe('root:x:0:0::/root:/bin/sh');
|
||||
});
|
||||
|
||||
it('mergeProfileSection refuses when the config path is a symlink', {}, () => {
|
||||
fs.mkdirSync('/home/user/.aws', { recursive: true });
|
||||
fs.mkdirSync('/etc', { recursive: true });
|
||||
fs.writeFileSync('/etc/sensitive', 'do not overwrite');
|
||||
fs.symlinkSync('/etc/sensitive', configPath);
|
||||
|
||||
expect(() => mergeProfileSection(configPath, 'profile dev', { region: 'us-east-1' }, true)).toThrow(
|
||||
/Refusing .* \(.* symbolic link\)/,
|
||||
);
|
||||
expect(fs.readFileSync('/etc/sensitive', 'utf-8')).toBe('do not overwrite');
|
||||
});
|
||||
|
||||
it('ensureAwsDirectoryExists refuses when ~/.aws is a symlink', {}, () => {
|
||||
fs.mkdirSync('/real-target', { recursive: true });
|
||||
fs.mkdirSync('/home/user', { recursive: true });
|
||||
fs.symlinkSync('/real-target', '/home/user/.aws');
|
||||
|
||||
expect(() => ensureAwsDirectoryExists(credsPath)).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
});
|
||||
|
||||
it('writeProfileFiles refuses to overwrite a pre-existing symlink at the credentials path', {}, () => {
|
||||
fs.mkdirSync('/home/user/.aws', { recursive: true });
|
||||
fs.mkdirSync('/etc', { recursive: true });
|
||||
fs.writeFileSync('/etc/passwd', 'root:x:0:0::/root:/bin/sh');
|
||||
fs.symlinkSync('/etc/passwd', credsPath);
|
||||
|
||||
expect(() =>
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', true),
|
||||
).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
|
||||
expect(fs.lstatSync(credsPath).isSymbolicLink()).toBe(true);
|
||||
expect(fs.readFileSync('/etc/passwd', 'utf-8')).toBe('root:x:0:0::/root:/bin/sh');
|
||||
});
|
||||
|
||||
it('happy path still writes both files with mode 0o600 when no symlinks are present', {}, () => {
|
||||
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false);
|
||||
|
||||
expect(fs.statSync(credsPath).mode & 0o777).toBe(0o600);
|
||||
expect(fs.statSync(configPath).mode & 0o777).toBe(0o600);
|
||||
expect(fs.lstatSync(credsPath).isSymbolicLink()).toBe(false);
|
||||
expect(fs.lstatSync(configPath).isSymbolicLink()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"strict": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedParameters": true,
|
||||
"module": "CommonJS",
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "build",
|
||||
"declaration": false,
|
||||
"newLine": "lf",
|
||||
"noEmitOnError": true,
|
||||
"sourceMap": true,
|
||||
"disableSizeLimit": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["ES2020"],
|
||||
"target": "ES2020",
|
||||
"noErrorTruncation": true,
|
||||
"esModuleInterop": true,
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["test/**/*.ts"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["test/**/*.ts", "src/**/*.ts"],
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
passWithNoTests: true,
|
||||
include: ['test/**/*.test.ts'],
|
||||
coverage: { enabled: true },
|
||||
typecheck: { tsconfig: './tsconfig.test.json' },
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user