mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-02 05:55:10 +09:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1829d0ba1 | |||
| 4b8b5e37b4 | |||
| a05dfa82bd | |||
| 378a941623 | |||
| 82408b69eb | |||
| 1f2d3ed486 | |||
| ed5da29eb9 | |||
| 7fdbbb8968 | |||
| 3852440c21 | |||
| c16f89bdf4 | |||
| 9c362eeba7 | |||
| d5f8da8822 | |||
| 2db24970cf | |||
| 58c3b6e457 | |||
| 2630a7c30d | |||
| 9cb796df96 | |||
| 9def7f603e | |||
| f5c3a50aab | |||
| 64cf66c882 | |||
| af68a47c78 | |||
| bcd4089080 | |||
| 721b15f0ee | |||
| 0e771c8069 | |||
| 6e36582a90 | |||
| 7a26c6cbd2 | |||
| 83c855008c | |||
| e2c213d5f6 | |||
| fd3f78a69a | |||
| ab089d1087 | |||
| 0ea2ab0d82 | |||
| 12d377b18d | |||
| 3f6acccbef | |||
| 9f178b3b31 | |||
| 4c029bab3d | |||
| 15313a702c | |||
| c52a2eac0d | |||
| 87f905da14 | |||
| ba7d3485af | |||
| 0802eece3e | |||
| b04158f834 | |||
| bc56d889a4 | |||
| a5fc44c8bc | |||
| e6bb36664c | |||
| c39789ae94 | |||
| 533ad3f138 | |||
| 5a98413048 | |||
| 6d65716bae | |||
| 1d168a55fe | |||
| 848d062284 | |||
| d3194f48b9 | |||
| 61c52b4114 | |||
| 91d3095422 | |||
| 43a31ecb43 | |||
| 13db8f061e | |||
| 7b65d1d5f3 | |||
| 49a3467caa | |||
| f0a75f4173 | |||
| 7d3d30aa06 | |||
| 544de3bdd1 | |||
| 63142d578f | |||
| 33103b67c5 | |||
| ebff9ed752 | |||
| 247bed75ed | |||
| e6de054238 | |||
| ab3b2ba025 | |||
| fa8d6a57bb | |||
| 42e118a656 | |||
| d86ddfcecc | |||
| 874aaac21e | |||
| d4341b65ac | |||
| fe51823c97 | |||
| a8be382115 | |||
| e000376c2c | |||
| c2828f8b08 | |||
| 20e62f617c | |||
| 66f02a3e6c | |||
| 82a0ed2cab | |||
| 9d2a1c17c0 | |||
| e977f476bf | |||
| ab0d61f6a0 | |||
| ccf493e3b1 | |||
| 05ed620dc8 | |||
| 62f98f6930 | |||
| 5a780d06ff | |||
| d3cb30b08b | |||
| eb48debd38 | |||
| e69af16b6c | |||
| 7fb20f0656 | |||
| 4e7ae70130 | |||
| 9ae780b171 |
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Stale issue job
|
name: Stale issue job
|
||||||
steps:
|
steps:
|
||||||
- uses: aws-actions/stale-issue-cleanup@v6
|
- uses: aws-actions/stale-issue-cleanup@v7
|
||||||
with:
|
with:
|
||||||
# Setting messages to an empty string will cause the automation to skip
|
# Setting messages to an empty string will cause the automation to skip
|
||||||
# that category
|
# that category
|
||||||
|
|||||||
@@ -55,6 +55,8 @@
|
|||||||
run: |
|
run: |
|
||||||
git config user.name "GitHub Actions"
|
git config user.name "GitHub Actions"
|
||||||
git config user.email "github-aws-sdk-osds-automation@amazon.com"
|
git config user.email "github-aws-sdk-osds-automation@amazon.com"
|
||||||
|
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
|
||||||
if git rev-parse "v${{ steps.release.outputs.major }}" >/dev/null 2>&1; then
|
if git rev-parse "v${{ steps.release.outputs.major }}" >/dev/null 2>&1; then
|
||||||
git tag -d "v${{ steps.release.outputs.major }}"
|
git tag -d "v${{ steps.release.outputs.major }}"
|
||||||
git push origin ":v${{ steps.release.outputs.major }}"
|
git push origin ":v${{ steps.release.outputs.major }}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
".release-please-manifest.json": "4.0.2",
|
".release-please-manifest.json": "4.0.2",
|
||||||
"package.json": "6.0.0",
|
"package.json": "6.0.0",
|
||||||
".": "6.2.2"
|
".": "6.2.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
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.
|
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.3](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.2...v6.2.3) (2026-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* attach git credentials before Tag Major Version push ([#1877](https://github.com/aws-actions/configure-aws-credentials/issues/1877)) ([9ae780b](https://github.com/aws-actions/configure-aws-credentials/commit/9ae780b171afa8c5a3a6a2d154a765b709492482))
|
||||||
|
* PackedPolicyTooLarge detection in STS tags ([#1899](https://github.com/aws-actions/configure-aws-credentials/issues/1899)) ([fa8d6a5](https://github.com/aws-actions/configure-aws-credentials/commit/fa8d6a57bbf44b34439fb080bbdadc7c92c285eb))
|
||||||
|
|
||||||
## [6.2.2](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.1...v6.2.2) (2026-07-07)
|
## [6.2.2](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.1...v6.2.2) (2026-07-07)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ these are incredibly useful:
|
|||||||
- Any modifications you've made relevant to the bug
|
- Any modifications you've made relevant to the bug
|
||||||
- Anything unusual about your environment or deployment
|
- Anything unusual about your environment or deployment
|
||||||
|
|
||||||
|
We also ask that you refrain from opening issues via the `gh` CLI or GitHub
|
||||||
|
API. These methods bypass our issue templates and therefore don't apply the
|
||||||
|
proper labels or workflows that we use. Note that AI agents typically do not
|
||||||
|
properly use issue templates. Issues that don't have the proper labels
|
||||||
|
applied may be closed without comment.
|
||||||
|
|
||||||
## Contributing via Pull Requests
|
## Contributing via Pull Requests
|
||||||
|
|
||||||
Contributions via pull requests are much appreciated. Before sending us a pull
|
Contributions via pull requests are much appreciated. Before sending us a pull
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
|||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
||||||
"token.actions.githubusercontent.com:sub": "repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:ref:refs/heads/<GITHUB_BRANCH>"
|
"token.actions.githubusercontent.com:sub": "repo:<GITHUB_ORG>@<ORG_ID>/<GITHUB_REPOSITORY>@<REPO_ID>:ref:refs/heads/<GITHUB_BRANCH>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,11 +36,16 @@ Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
Note: if you are running in a GitHub environment based workflow, the value
|
Note: The value of the `sub` claim may be different depending on the workflow
|
||||||
for the Sub claim will be different, in the form of
|
and the environment in which it's running. Workflows in repositories created
|
||||||
`repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:environment:<ENVIRONMENT_NAME>`.
|
prior to [15 July 2026][immutable-sub] will omit the `@<ORG_ID>` and
|
||||||
Adjust the trust policy accordingly if you are using environment-based
|
`@<REPO_ID>` suffixes unless opted in. Workflows running in GitHub
|
||||||
workflows.
|
environments will include an`environment:<ENVIRONMENT_NAME>` stanza. See
|
||||||
|
[Claims and scoping permissions](#claims-and-scoping-permissions) for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
[immutable-sub]:
|
||||||
|
https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/
|
||||||
|
|
||||||
3. Attach permissions to the IAM Role that allow it to access the AWS resources
|
3. Attach permissions to the IAM Role that allow it to access the AWS resources
|
||||||
you need.
|
you need.
|
||||||
@@ -56,7 +61,7 @@ Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
role-to-assume: <Role ARN you created in step 2>
|
role-to-assume: <Role ARN you created in step 2>
|
||||||
aws-region: <AWS Region you want to use>
|
aws-region: <AWS Region you want to use>
|
||||||
@@ -245,7 +250,7 @@ specify the profile name as an environment variable in the job step:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-role
|
||||||
@@ -263,14 +268,14 @@ step environment variables:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-first-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-first-role
|
||||||
aws-profile: firstRoleInChain
|
aws-profile: firstRoleInChain
|
||||||
|
|
||||||
- name: assume second role
|
- name: assume second role
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||||
@@ -306,7 +311,7 @@ this action will always consider the `HTTP_PROXY` environment variable.
|
|||||||
Manually configured proxy:
|
Manually configured proxy:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: my-github-actions-role
|
role-to-assume: my-github-actions-role
|
||||||
@@ -453,7 +458,7 @@ line.
|
|||||||
<summary>Inline session policy examples</summary>
|
<summary>Inline session policy examples</summary>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
|
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
|
||||||
```
|
```
|
||||||
@@ -461,7 +466,7 @@ with:
|
|||||||
Or we can have a nicely formatted JSON as well:
|
Or we can have a nicely formatted JSON as well:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
inline-session-policy: >-
|
inline-session-policy: >-
|
||||||
{
|
{
|
||||||
@@ -489,7 +494,7 @@ the role.
|
|||||||
<summary>Managed session policy examples</summary>
|
<summary>Managed session policy examples</summary>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||||
```
|
```
|
||||||
@@ -497,7 +502,7 @@ with:
|
|||||||
And we can pass multiple managed policies likes this:
|
And we can pass multiple managed policies likes this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
managed-session-policies: |
|
managed-session-policies: |
|
||||||
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||||
@@ -543,7 +548,7 @@ specify the audience through the `audience` input:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials for China region audience
|
- name: Configure AWS Credentials for China region audience
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
audience: sts.amazonaws.com.cn
|
audience: sts.amazonaws.com.cn
|
||||||
aws-region: cn-northwest-1
|
aws-region: cn-northwest-1
|
||||||
@@ -593,6 +598,30 @@ claims ([1][gh-blog-oidc], [2][sub-claim-custom]).
|
|||||||
> unintended access. Instead, use `StringEquals` or `StringLike` operators to
|
> unintended access. Instead, use `StringEquals` or `StringLike` operators to
|
||||||
> check for specific claim values.
|
> check for specific claim values.
|
||||||
|
|
||||||
|
#### Immutable subject claims
|
||||||
|
|
||||||
|
Repositories created on github.com on or after 15 July 2026, older
|
||||||
|
repositories that have opted in, and older repositories that have been renamed
|
||||||
|
since 15 July 2026, emit an [immutable `sub` claim][immutable-sub].
|
||||||
|
This claim appends the permanent numeric ID of the organization and of the
|
||||||
|
repository after each name, separated by `@`, so that a recycled org or
|
||||||
|
repository name cannot be used to mint tokens matching a stale trust policy.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Legacy (mutable) sub claim
|
||||||
|
repo:octo-org/octo-repo:ref:refs/heads/main
|
||||||
|
|
||||||
|
# Immutable sub claim
|
||||||
|
repo:octo-org@123456/octo-repo@789012:ref:refs/heads/main
|
||||||
|
```
|
||||||
|
|
||||||
|
If your trust policy matches the legacy name-only form and your repository emits
|
||||||
|
the immutable claim, `AssumeRoleWithWebIdentity` fails with `Not authorized to
|
||||||
|
perform sts:AssumeRoleWithWebIdentity`. To fix this, update the `sub` condition
|
||||||
|
to the immutable form. You can find your repository's prefix in the Settings,
|
||||||
|
or by following the token inspection steps below.
|
||||||
|
|
||||||
[least-privilege]:
|
[least-privilege]:
|
||||||
https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege
|
||||||
[gh-blog-oidc]:
|
[gh-blog-oidc]:
|
||||||
@@ -605,7 +634,7 @@ claims ([1][gh-blog-oidc], [2][sub-claim-custom]).
|
|||||||
If you aren't sure what claim values your workflow is producing, the
|
If you aren't sure what claim values your workflow is producing, the
|
||||||
[`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger)
|
[`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger)
|
||||||
action will print the decoded JWT payload. Run it in a private repository
|
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.
|
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
|
See the GitHub [security-hardening guide][gh-oidc-hardening] for further
|
||||||
discussion of trust conditions and threat modeling.
|
discussion of trust conditions and threat modeling.
|
||||||
@@ -680,7 +709,7 @@ Provider. The audience would still be `sts.amazonaws.com` by default.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
@@ -696,13 +725,13 @@ environment variable and use it to assume the role
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
role-session-name: MySessionName
|
role-session-name: MySessionName
|
||||||
- name: Configure other AWS Credentials
|
- name: Configure other AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||||
@@ -724,7 +753,7 @@ alternatively, the `TagSession` permission can be omitted if you are using the
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
@@ -745,7 +774,7 @@ like `role-to-assume: my-github-actions-role`.
|
|||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials 1
|
- name: Configure AWS Credentials 1
|
||||||
id: creds
|
id: creds
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
@@ -754,7 +783,7 @@ like `role-to-assume: my-github-actions-role`.
|
|||||||
run: |
|
run: |
|
||||||
aws sts get-caller-identity
|
aws sts get-caller-identity
|
||||||
- name: Configure AWS Credentials 2
|
- name: Configure AWS Credentials 2
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }}
|
aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }}
|
||||||
@@ -785,14 +814,14 @@ provided.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials for Dev
|
- name: Configure AWS Credentials for Dev
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::111111111111:role/dev-role
|
role-to-assume: arn:aws:iam::111111111111:role/dev-role
|
||||||
aws-profile: dev
|
aws-profile: dev
|
||||||
|
|
||||||
- name: Configure AWS Credentials for Prod
|
- name: Configure AWS Credentials for Prod
|
||||||
uses: aws-actions/configure-aws-credentials@v6.1.0
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-west-2
|
aws-region: us-west-2
|
||||||
role-to-assume: arn:aws:iam::222222222222:role/prod-role
|
role-to-assume: arn:aws:iam::222222222222:role/prod-role
|
||||||
|
|||||||
+23
-23
@@ -222,7 +222,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/client-sts@3.1080.0
|
- @aws-sdk/client-sts@3.1116.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -432,9 +432,9 @@ Apache License
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/signature-v4-multi-region@3.996.38
|
- @aws-sdk/signature-v4-multi-region@3.996.46
|
||||||
- @smithy/core@3.29.1
|
- @smithy/core@3.33.3
|
||||||
- @smithy/types@4.15.1
|
- @smithy/types@4.17.2
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -832,7 +832,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/core@3.974.28
|
- @aws-sdk/core@3.977.9
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1042,16 +1042,16 @@ Apache License
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/credential-provider-env@3.972.54
|
- @aws-sdk/credential-provider-env@3.972.70
|
||||||
- @aws-sdk/credential-provider-ini@3.972.61
|
- @aws-sdk/credential-provider-ini@3.973.15
|
||||||
- @aws-sdk/credential-provider-node@3.972.63
|
- @aws-sdk/credential-provider-node@3.972.81
|
||||||
- @aws-sdk/token-providers@3.1080.0
|
- @aws-sdk/token-providers@3.1116.0
|
||||||
- @aws-sdk/types@3.973.15
|
- @aws-sdk/types@3.974.5
|
||||||
- @aws-sdk/xml-builder@3.972.33
|
- @aws-sdk/xml-builder@3.972.40
|
||||||
- @smithy/credential-provider-imds@4.4.6
|
- @smithy/credential-provider-imds@4.5.2
|
||||||
- @smithy/fetch-http-handler@5.6.3
|
- @smithy/fetch-http-handler@5.7.2
|
||||||
- @smithy/node-http-handler@4.9.3
|
- @smithy/node-http-handler@4.11.3
|
||||||
- @smithy/signature-v4@5.6.2
|
- @smithy/signature-v4@5.6.12
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1261,9 +1261,9 @@ Apache License
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/credential-provider-process@3.972.54
|
- @aws-sdk/credential-provider-process@3.972.70
|
||||||
- @aws-sdk/credential-provider-sso@3.972.60
|
- @aws-sdk/credential-provider-sso@3.973.14
|
||||||
- @aws-sdk/credential-provider-web-identity@3.972.60
|
- @aws-sdk/credential-provider-web-identity@3.972.76
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1473,9 +1473,9 @@ Apache License
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/credential-provider-http@3.972.56
|
- @aws-sdk/credential-provider-http@3.972.72
|
||||||
- @aws-sdk/credential-provider-login@3.972.60
|
- @aws-sdk/credential-provider-login@3.972.77
|
||||||
- @aws-sdk/nested-clients@3.997.28
|
- @aws-sdk/nested-clients@3.997.44
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1485,7 +1485,7 @@ Apache-2.0
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- ip-address@10.2.0
|
- ip-address@10.4.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1740,7 +1740,7 @@ SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- undici@6.27.0
|
- undici@6.28.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|||||||
+88
-6
@@ -1929,7 +1929,11 @@ var require_request = __commonJS({
|
|||||||
} else if (typeof val[i] === "object") {
|
} else if (typeof val[i] === "object") {
|
||||||
throw new InvalidArgumentError(`invalid ${key} header`);
|
throw new InvalidArgumentError(`invalid ${key} header`);
|
||||||
} else {
|
} else {
|
||||||
arr.push(`${val[i]}`);
|
const str = `${val[i]}`;
|
||||||
|
if (!isValidHeaderValue(str)) {
|
||||||
|
throw new InvalidArgumentError(`invalid ${key} header`);
|
||||||
|
}
|
||||||
|
arr.push(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val = arr;
|
val = arr;
|
||||||
@@ -1941,6 +1945,9 @@ var require_request = __commonJS({
|
|||||||
val = "";
|
val = "";
|
||||||
} else {
|
} else {
|
||||||
val = `${val}`;
|
val = `${val}`;
|
||||||
|
if (!isValidHeaderValue(val)) {
|
||||||
|
throw new InvalidArgumentError(`invalid ${key} header`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (headerName === "host") {
|
if (headerName === "host") {
|
||||||
if (request.host !== null) {
|
if (request.host !== null) {
|
||||||
@@ -5671,6 +5678,7 @@ var require_client_h1 = __commonJS({
|
|||||||
RequestContentLengthMismatchError,
|
RequestContentLengthMismatchError,
|
||||||
ResponseContentLengthMismatchError,
|
ResponseContentLengthMismatchError,
|
||||||
RequestAbortedError,
|
RequestAbortedError,
|
||||||
|
InvalidArgumentError,
|
||||||
HeadersTimeoutError,
|
HeadersTimeoutError,
|
||||||
HeadersOverflowError,
|
HeadersOverflowError,
|
||||||
SocketError,
|
SocketError,
|
||||||
@@ -6397,8 +6405,16 @@ var require_client_h1 = __commonJS({
|
|||||||
}
|
}
|
||||||
body = bodyStream.stream;
|
body = bodyStream.stream;
|
||||||
contentLength = bodyStream.length;
|
contentLength = bodyStream.length;
|
||||||
} else if (util.isBlobLike(body) && request.contentType == null && body.type) {
|
} else if (util.isBlobLike(body) && request.contentType == null) {
|
||||||
headers.push("content-type", body.type);
|
const contentType = body.type;
|
||||||
|
if (contentType) {
|
||||||
|
const contentTypeValue = `${contentType}`;
|
||||||
|
if (!util.isValidHeaderValue(contentTypeValue)) {
|
||||||
|
util.errorRequest(client, request, new InvalidArgumentError("invalid content-type header"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
headers.push("content-type", contentTypeValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (body && typeof body.read === "function") {
|
if (body && typeof body.read === "function") {
|
||||||
body.read(0);
|
body.read(0);
|
||||||
@@ -8950,6 +8966,24 @@ var require_retry_handler = __commonJS({
|
|||||||
const current = Date.now();
|
const current = Date.now();
|
||||||
return new Date(retryAfter).getTime() - current;
|
return new Date(retryAfter).getTime() - current;
|
||||||
}
|
}
|
||||||
|
function validatePartialResponseContentLength(headers, range, statusCode, retryCount) {
|
||||||
|
const contentLength = headers["content-length"];
|
||||||
|
if (contentLength == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const length = Number(contentLength);
|
||||||
|
const expectedLength = range.end - range.start + 1;
|
||||||
|
if (!Number.isFinite(length) || length !== expectedLength) {
|
||||||
|
return new RequestRetryError("Content-Length mismatch", statusCode, {
|
||||||
|
headers,
|
||||||
|
data: { count: retryCount }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var RetryHandler = class _RetryHandler {
|
var RetryHandler = class _RetryHandler {
|
||||||
constructor(opts, handlers) {
|
constructor(opts, handlers) {
|
||||||
const { retryOptions, ...dispatchOpts } = opts;
|
const { retryOptions, ...dispatchOpts } = opts;
|
||||||
@@ -9122,6 +9156,11 @@ var require_retry_handler = __commonJS({
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount);
|
||||||
|
if (contentLengthError != null) {
|
||||||
|
this.abort(contentLengthError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const { start, size, end = size - 1 } = contentRange;
|
const { start, size, end = size - 1 } = contentRange;
|
||||||
assert(this.start === start, "content-range mismatch");
|
assert(this.start === start, "content-range mismatch");
|
||||||
assert(this.end == null || this.end === end, "content-range mismatch");
|
assert(this.end == null || this.end === end, "content-range mismatch");
|
||||||
@@ -9139,6 +9178,11 @@ var require_retry_handler = __commonJS({
|
|||||||
statusMessage
|
statusMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount);
|
||||||
|
if (contentLengthError != null) {
|
||||||
|
this.abort(contentLengthError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const { start, size, end = size - 1 } = range;
|
const { start, size, end = size - 1 } = range;
|
||||||
assert(
|
assert(
|
||||||
start != null && Number.isFinite(start),
|
start != null && Number.isFinite(start),
|
||||||
@@ -15984,14 +16028,48 @@ var require_util6 = __commonJS({
|
|||||||
for (let i = 0; i < path.length; ++i) {
|
for (let i = 0; i < path.length; ++i) {
|
||||||
const code = path.charCodeAt(i);
|
const code = path.charCodeAt(i);
|
||||||
if (code < 32 || // exclude CTLs (0-31)
|
if (code < 32 || // exclude CTLs (0-31)
|
||||||
code === 127 || // DEL
|
code > 126 || // exclude DEL and non-ascii
|
||||||
code === 59) {
|
code === 59) {
|
||||||
throw new Error("Invalid cookie path");
|
throw new Error("Invalid cookie path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function isLetterOrDigit(code) {
|
||||||
|
return code >= 48 && code <= 57 || // 0-9
|
||||||
|
code >= 65 && code <= 90 || // A-Z
|
||||||
|
code >= 97 && code <= 122;
|
||||||
|
}
|
||||||
function validateCookieDomain(domain) {
|
function validateCookieDomain(domain) {
|
||||||
if (domain.startsWith("-") || domain.endsWith(".") || domain.endsWith("-")) {
|
if (domain === " ") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (domain.length > 255) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
let labelLength = 0;
|
||||||
|
for (let i = 0; i < domain.length; ++i) {
|
||||||
|
const code = domain.charCodeAt(i);
|
||||||
|
if (code === 46) {
|
||||||
|
if (labelLength === 0) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
if (domain.charCodeAt(i - 1) === 45) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
labelLength = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (labelLength === 0 && !isLetterOrDigit(code)) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
if (!isLetterOrDigit(code) && code !== 45) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
if (++labelLength > 63) {
|
||||||
|
throw new Error("Invalid cookie domain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 45) {
|
||||||
throw new Error("Invalid cookie domain");
|
throw new Error("Invalid cookie domain");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16074,7 +16152,11 @@ var require_util6 = __commonJS({
|
|||||||
throw new Error("Invalid unparsed");
|
throw new Error("Invalid unparsed");
|
||||||
}
|
}
|
||||||
const [key, ...value] = part.split("=");
|
const [key, ...value] = part.split("=");
|
||||||
out.push(`${key.trim()}=${value.join("=")}`);
|
const trimmedKey = key.trim();
|
||||||
|
const joinedValue = value.join("=");
|
||||||
|
validateCookieName(trimmedKey);
|
||||||
|
validateCookieValue(joinedValue);
|
||||||
|
out.push(`${trimmedKey}=${joinedValue}`);
|
||||||
}
|
}
|
||||||
return out.join("; ");
|
return out.join("; ");
|
||||||
}
|
}
|
||||||
|
|||||||
+5702
-4040
File diff suppressed because it is too large
Load Diff
@@ -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
+815
-432
File diff suppressed because it is too large
Load Diff
+12
-11
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "configure-aws-credentials",
|
"name": "configure-aws-credentials",
|
||||||
"description": "A GitHub Action to configure AWS credentials",
|
"description": "A GitHub Action to configure AWS credentials",
|
||||||
"version": "6.2.2",
|
"version": "6.2.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
||||||
@@ -17,25 +17,26 @@
|
|||||||
"organization": true
|
"organization": true
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/credential-provider-env": "^3.972.49",
|
"@aws-sdk/credential-provider-env": "^3.972.70",
|
||||||
"@biomejs/biome": "2.5.2",
|
"@biomejs/biome": "2.5.10",
|
||||||
"@smithy/property-provider": "^4.4.6",
|
"@smithy/property-provider": "^4.5.2",
|
||||||
"@types/node": "^26.1.0",
|
"@types/node": "^26.2.0",
|
||||||
"@vitest/coverage-v8": "4.1.10",
|
"@vitest/coverage-v8": "4.1.10",
|
||||||
"aws-sdk-client-mock": "^4.1.0",
|
"aws-sdk-client-mock": "^4.1.0",
|
||||||
"esbuild": "^0.28.1",
|
"esbuild": "^0.28.2",
|
||||||
"generate-license-file": "^4.2.1",
|
"generate-license-file": "^4.2.1",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"markdownlint-cli": "^0.49.0",
|
"markdownlint-cli": "^0.49.1",
|
||||||
"memfs": "^4.58.0",
|
"memfs": "^4.68.1",
|
||||||
"standard-version": "^9.5.0",
|
"standard-version": "^9.5.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^7.0.2",
|
||||||
"vitest": "4.1.10"
|
"vitest": "4.1.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^3.0.1",
|
"@actions/core": "^3.0.1",
|
||||||
"@aws-sdk/client-sts": "^3.1080.0",
|
"@aws-sdk/client-sts": "^3.1116.0",
|
||||||
"@smithy/node-http-handler": "^4.9.1",
|
"@aws-sdk/credential-provider-node": "^3.972.63",
|
||||||
|
"@smithy/node-http-handler": "^4.11.3",
|
||||||
"proxy-agent": "^8.0.2"
|
"proxy-agent": "^8.0.2"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { info } from '@actions/core';
|
import { info } from '@actions/core';
|
||||||
import { STSClient } from '@aws-sdk/client-sts';
|
import { STSClient } from '@aws-sdk/client-sts';
|
||||||
|
import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
||||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||||
import { ProxyAgent } from 'proxy-agent';
|
import { ProxyAgent } from 'proxy-agent';
|
||||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
import { buildCustomUserAgent, errorMessage, getCallerIdentity, maskProxyCredentials } from './helpers';
|
||||||
import { ProxyResolver } from './ProxyResolver';
|
import { ProxyResolver } from './ProxyResolver';
|
||||||
|
|
||||||
if (!process.env.AWS_EXECUTION_ENV) {
|
if (!process.env.AWS_EXECUTION_ENV) {
|
||||||
@@ -31,6 +32,7 @@ export class CredentialsClient {
|
|||||||
}
|
}
|
||||||
if (props.proxyServer) {
|
if (props.proxyServer) {
|
||||||
info('Configuring proxy handler for STS client');
|
info('Configuring proxy handler for STS client');
|
||||||
|
maskProxyCredentials(props.proxyServer);
|
||||||
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
||||||
httpProxy: props.proxyServer,
|
httpProxy: props.proxyServer,
|
||||||
httpsProxy: props.proxyServer,
|
httpsProxy: props.proxyServer,
|
||||||
@@ -105,9 +107,15 @@ export class CredentialsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadCredentials() {
|
private async loadCredentials() {
|
||||||
const config = {} as { requestHandler?: NodeHttpHandler };
|
// Previously we constructed a new client, but that picks up the default provider chain including the endpoint.
|
||||||
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
|
// Explicitly calling the default provider chain allows us to pass in the endpoint and region as well as the
|
||||||
const client = new STSClient(config);
|
// proxy config.
|
||||||
return client.config.credentials();
|
return defaultProvider({
|
||||||
|
clientConfig: {
|
||||||
|
...(this.region !== undefined && { region: this.region }),
|
||||||
|
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
|
||||||
|
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
|
||||||
|
},
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -2,11 +2,7 @@ import assert from 'node:assert';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import type { AssumeRoleCommandInput, STSClient, Tag } from '@aws-sdk/client-sts';
|
import type { AssumeRoleCommandInput, STSClient, Tag } from '@aws-sdk/client-sts';
|
||||||
import {
|
import { AssumeRoleCommand, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
|
||||||
AssumeRoleCommand,
|
|
||||||
AssumeRoleWithWebIdentityCommand,
|
|
||||||
PackedPolicyTooLargeException,
|
|
||||||
} from '@aws-sdk/client-sts';
|
|
||||||
import type { CredentialsClient } from './CredentialsClient';
|
import type { CredentialsClient } from './CredentialsClient';
|
||||||
import { errorMessage, isDefined, readFileUtf8, sanitizeGitHubVariables } from './helpers';
|
import { errorMessage, isDefined, readFileUtf8, sanitizeGitHubVariables } from './helpers';
|
||||||
|
|
||||||
@@ -65,7 +61,7 @@ async function assumeRoleWithCredentials(params: AssumeRoleCommandInput, client:
|
|||||||
const creds = await client.send(new AssumeRoleCommand({ ...params }));
|
const creds = await client.send(new AssumeRoleCommand({ ...params }));
|
||||||
return creds;
|
return creds;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof PackedPolicyTooLargeException) {
|
if ((error as { name?: string })?.name === 'PackedPolicyTooLargeException') {
|
||||||
core.info('Session tag size is too large; dropping droppable tags and retrying.');
|
core.info('Session tag size is too large; dropping droppable tags and retrying.');
|
||||||
const droppableKeys = new Set(DROPPABLE_TAG_SOURCES.map((s) => s.key));
|
const droppableKeys = new Set(DROPPABLE_TAG_SOURCES.map((s) => s.key));
|
||||||
params.Tags = params.Tags?.filter((tag) => !droppableKeys.has(tag.Key ?? ''));
|
params.Tags = params.Tags?.filter((tag) => !droppableKeys.has(tag.Key ?? ''));
|
||||||
|
|||||||
+30
-23
@@ -5,7 +5,6 @@ import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
|||||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||||
import type { UserAgent } from '@smithy/types';
|
import type { UserAgent } from '@smithy/types';
|
||||||
import type { CredentialsClient } from './CredentialsClient';
|
|
||||||
|
|
||||||
const MAX_TAG_VALUE_LENGTH = 256;
|
const MAX_TAG_VALUE_LENGTH = 256;
|
||||||
const SANITIZATION_CHARACTER = '_';
|
const SANITIZATION_CHARACTER = '_';
|
||||||
@@ -167,15 +166,13 @@ export function exportAccountId(identity: { Account: string; Arn: string }, mask
|
|||||||
// Validates that the account of the already-resolved caller identity is in the allow-list provided via the
|
// Validates that the account of the already-resolved caller identity is in the allow-list provided via the
|
||||||
// `allowed-account-ids` input.
|
// `allowed-account-ids` input.
|
||||||
export function validateAccountId(expectedAccountIds: string[] | undefined, account: string | undefined): void {
|
export function validateAccountId(expectedAccountIds: string[] | undefined, account: string | undefined): void {
|
||||||
if (!expectedAccountIds || expectedAccountIds.length === 0 || expectedAccountIds[0] === '') {
|
const allowedAccountIds = expectedAccountIds?.filter((id) => id !== '') ?? [];
|
||||||
|
if (allowedAccountIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!account || !expectedAccountIds.includes(account)) {
|
if (!account || !allowedAccountIds.includes(account)) {
|
||||||
throw new Error(
|
// Account IDs are deliberately omitted: this error reaches the job log before any mask exists.
|
||||||
`The account ID of the provided credentials (${
|
throw new Error('The account ID of the provided credentials does not match any of the allowed account IDs');
|
||||||
account ?? 'unknown'
|
|
||||||
}) does not match any of the expected account IDs: ${expectedAccountIds.join(', ')}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +190,29 @@ export function toCredentialIdentity(creds?: Partial<Credentials>): AwsCredentia
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Registers any userinfo embedded in a proxy URL as secrets so it is masked in job logs.
|
||||||
|
// First the literal proxy string, then any username/password components if parseable.
|
||||||
|
// If the username/password is percent-encoded, the decoded form is also masked.
|
||||||
|
export function maskProxyCredentials(proxyServer: string): void {
|
||||||
|
core.setSecret(proxyServer);
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(proxyServer);
|
||||||
|
} catch (_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const part of [url.username, url.password]) {
|
||||||
|
if (!part) continue;
|
||||||
|
core.setSecret(part);
|
||||||
|
try {
|
||||||
|
const decoded = decodeURIComponent(part);
|
||||||
|
if (decoded !== part) core.setSecret(decoded);
|
||||||
|
} catch (_) {
|
||||||
|
// malformed percent-encoding; the raw form is already masked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tags have a more restrictive set of acceptable characters than GitHub environment variables can.
|
// 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.
|
// 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.
|
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
|
||||||
@@ -260,13 +280,13 @@ export async function retryAndBackoff<T>(
|
|||||||
`Retrying after ${Math.floor(delay)}ms.`,
|
`Retrying after ${Math.floor(delay)}ms.`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await sleep(delay);
|
|
||||||
|
|
||||||
if (nextRetry >= maxRetries) {
|
if (nextRetry >= maxRetries) {
|
||||||
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await sleep(delay);
|
||||||
|
|
||||||
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,19 +301,6 @@ export function isDefined<T>(i: T | undefined | null): i is T {
|
|||||||
}
|
}
|
||||||
/* c8 ignore stop */
|
/* 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.
|
* Like core.getBooleanInput, but respects the required option.
|
||||||
*
|
*
|
||||||
|
|||||||
+13
-5
@@ -3,12 +3,12 @@ import type { AssumeRoleCommandOutput } from '@aws-sdk/client-sts';
|
|||||||
import { assumeRole } from './assumeRole';
|
import { assumeRole } from './assumeRole';
|
||||||
import { CredentialsClient } from './CredentialsClient';
|
import { CredentialsClient } from './CredentialsClient';
|
||||||
import {
|
import {
|
||||||
areCredentialsValid,
|
|
||||||
errorMessage,
|
errorMessage,
|
||||||
exportAccountId,
|
exportAccountId,
|
||||||
exportCredentials,
|
exportCredentials,
|
||||||
exportRegion,
|
exportRegion,
|
||||||
getBooleanInput,
|
getBooleanInput,
|
||||||
|
getCallerIdentity,
|
||||||
retryAndBackoff,
|
retryAndBackoff,
|
||||||
toCredentialIdentity,
|
toCredentialIdentity,
|
||||||
translateEnvVariables,
|
translateEnvVariables,
|
||||||
@@ -53,8 +53,8 @@ export async function run() {
|
|||||||
});
|
});
|
||||||
const roleChaining = getBooleanInput('role-chaining', { required: false });
|
const roleChaining = getBooleanInput('role-chaining', { required: false });
|
||||||
const outputCredentials = getBooleanInput('output-credentials', { required: false });
|
const outputCredentials = getBooleanInput('output-credentials', { required: false });
|
||||||
// Default to always outputting environment credentials unless profile is specified. If profile is specified, default
|
// Default to always outputting environment credentials unless profile is specified. If profile is specified,
|
||||||
// to no environment credentials (but still output them if the user specifically requests it).
|
// 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 outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: !awsProfile });
|
||||||
const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false });
|
const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false });
|
||||||
let disableRetry = getBooleanInput('disable-retry', { required: false });
|
let disableRetry = getBooleanInput('disable-retry', { required: false });
|
||||||
@@ -165,8 +165,16 @@ export async function run() {
|
|||||||
|
|
||||||
//if the user wants to attempt to use existing credentials, check if we have some already
|
//if the user wants to attempt to use existing credentials, check if we have some already
|
||||||
if (useExistingCredentials) {
|
if (useExistingCredentials) {
|
||||||
const validCredentials = await areCredentialsValid(credentialsClient);
|
const identity = await (async () => {
|
||||||
if (validCredentials) {
|
try {
|
||||||
|
return await getCallerIdentity(credentialsClient.stsClient);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
if (identity) {
|
||||||
|
// The allowed-account-ids guardrail applies to reused credentials too.
|
||||||
|
validateAccountId(expectedAccountIds, identity.Account);
|
||||||
core.notice('Pre-existing credentials are valid. No need to generate new ones.');
|
core.notice('Pre-existing credentials are valid. No need to generate new ones.');
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -53,8 +53,15 @@ export function parseIni(iniData: string): Record<string, Record<string, string>
|
|||||||
export function stringifyIni(data: Record<string, Record<string, string>>): string {
|
export function stringifyIni(data: Record<string, Record<string, string>>): string {
|
||||||
const sections: string[] = [];
|
const sections: string[] = [];
|
||||||
for (const [sectionName, sectionData] of Object.entries(data)) {
|
for (const [sectionName, sectionData] of Object.entries(data)) {
|
||||||
|
if (/[\r\n]/.test(sectionName)) {
|
||||||
|
throw new Error('INI section names must not contain newline characters');
|
||||||
|
}
|
||||||
const lines: string[] = [`[${sectionName}]`];
|
const lines: string[] = [`[${sectionName}]`];
|
||||||
for (const [key, value] of Object.entries(sectionData)) {
|
for (const [key, value] of Object.entries(sectionData)) {
|
||||||
|
// A newline in a key or value would inject arbitrary INI lines (e.g. credential_process).
|
||||||
|
if (/[\r\n]/.test(key) || /[\r\n]/.test(value)) {
|
||||||
|
throw new Error('INI keys and values must not contain newline characters');
|
||||||
|
}
|
||||||
lines.push(`${key} = ${value}`);
|
lines.push(`${key} = ${value}`);
|
||||||
}
|
}
|
||||||
sections.push(lines.join('\n'));
|
sections.push(lines.join('\n'));
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('@aws-sdk/credential-provider-node', () => ({
|
||||||
|
defaultProvider: vi.fn(() => async () => ({ accessKeyId: 'AKIA', secretAccessKey: 'secret' })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
||||||
|
import { CredentialsClient } from '../src/CredentialsClient';
|
||||||
|
|
||||||
|
describe('CredentialsClient', {}, () => {
|
||||||
|
it('pins ambient credential resolution to the configured region and STS endpoint', {}, async () => {
|
||||||
|
const client = new CredentialsClient({
|
||||||
|
region: 'eu-west-1',
|
||||||
|
stsEndpoint: 'https://sts.example.com',
|
||||||
|
roleChaining: false,
|
||||||
|
});
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: any required to call private method
|
||||||
|
await (client as any).loadCredentials();
|
||||||
|
expect(defaultProvider).toHaveBeenCalledWith({
|
||||||
|
clientConfig: expect.objectContaining({ region: 'eu-west-1', endpoint: 'https://sts.example.com' }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits unset client config values from ambient credential resolution', {}, async () => {
|
||||||
|
const client = new CredentialsClient({ region: 'eu-west-1', roleChaining: false });
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: any required to call private method
|
||||||
|
await (client as any).loadCredentials();
|
||||||
|
expect(defaultProvider).toHaveBeenLastCalledWith({ clientConfig: { region: 'eu-west-1' } });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -126,6 +126,45 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
|||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('maskProxyCredentials', {}, () => {
|
||||||
|
it('masks username and password embedded in a proxy URL', {}, () => {
|
||||||
|
helpers.maskProxyCredentials('http://user:secretpass@proxy.example.com:8080');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('user');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('masks both encoded and decoded forms of the credentials', {}, () => {
|
||||||
|
helpers.maskProxyCredentials('http://user:p%40ss@proxy.example.com:8080');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('p%40ss');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('p@ss');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('masks the whole value even without embedded credentials or when unparseable', {}, () => {
|
||||||
|
helpers.maskProxyCredentials('http://proxy.example.com:8080');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('http://proxy.example.com:8080');
|
||||||
|
helpers.maskProxyCredentials('not a url');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('not a url');
|
||||||
|
// no username/password parts, so exactly one mask per call
|
||||||
|
expect(core.setSecret).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateAccountId', {}, () => {
|
||||||
|
it('enforces the allow-list even when the first element is empty', {}, () => {
|
||||||
|
expect(() => helpers.validateAccountId(['', '999999999999'], '111111111111')).toThrow(/does not match/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes an allowed account despite empty entries in the list', {}, () => {
|
||||||
|
expect(() => helpers.validateAccountId(['', '111111111111'], '111111111111')).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips validation only when no non-empty entries exist', {}, () => {
|
||||||
|
expect(() => helpers.validateAccountId(undefined, '111111111111')).not.toThrow();
|
||||||
|
expect(() => helpers.validateAccountId([], '111111111111')).not.toThrow();
|
||||||
|
expect(() => helpers.validateAccountId([''], '111111111111')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('filesystem helpers', {}, () => {
|
describe('filesystem helpers', {}, () => {
|
||||||
describe('isSymlink', {}, () => {
|
describe('isSymlink', {}, () => {
|
||||||
it('returns true for a symlink', {}, () => {
|
it('returns true for a symlink', {}, () => {
|
||||||
|
|||||||
+52
-10
@@ -3,7 +3,6 @@ import {
|
|||||||
AssumeRoleCommand,
|
AssumeRoleCommand,
|
||||||
AssumeRoleWithWebIdentityCommand,
|
AssumeRoleWithWebIdentityCommand,
|
||||||
GetCallerIdentityCommand,
|
GetCallerIdentityCommand,
|
||||||
PackedPolicyTooLargeException,
|
|
||||||
STSClient,
|
STSClient,
|
||||||
} from '@aws-sdk/client-sts';
|
} from '@aws-sdk/client-sts';
|
||||||
import { mockClient } from 'aws-sdk-client-mock';
|
import { mockClient } from 'aws-sdk-client-mock';
|
||||||
@@ -331,10 +330,11 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
});
|
});
|
||||||
it('drops droppable tags and retries on PackedPolicyTooLargeException', {}, async () => {
|
it('drops droppable tags and retries on PackedPolicyTooLargeException', {}, async () => {
|
||||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
mockedSTSClient
|
// Reject with a plain error carrying only the `name`, NOT an instance of the SDK class. This
|
||||||
.on(AssumeRoleCommand)
|
// mirrors the bundled action, where the error can be deserialized by a second, non-identical
|
||||||
.rejectsOnce(new PackedPolicyTooLargeException({ message: 'too large', $metadata: {} }))
|
// copy of PackedPolicyTooLargeException so `instanceof` fails; the recovery must key off `name`.
|
||||||
.resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
const packedPolicyError = Object.assign(new Error('too large'), { name: 'PackedPolicyTooLargeException' });
|
||||||
|
mockedSTSClient.on(AssumeRoleCommand).rejectsOnce(packedPolicyError).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||||
await run();
|
await run();
|
||||||
expect(core.info).toHaveBeenCalledWith('Session tag size is too large; dropping droppable tags and retrying.');
|
expect(core.info).toHaveBeenCalledWith('Session tag size is too large; dropping droppable tags and retrying.');
|
||||||
const retryInput = mockedSTSClient.commandCalls(AssumeRoleCommand)[1].args[0].input;
|
const retryInput = mockedSTSClient.commandCalls(AssumeRoleCommand)[1].args[0].input;
|
||||||
@@ -841,7 +841,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -861,7 +861,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999, 888888888888',
|
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -917,7 +917,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -936,7 +936,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -956,7 +956,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1015,6 +1015,33 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails on the use-existing-credentials path when the account is not allowed', async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.USE_EXISTING_CREDENTIALS_INPUTS,
|
||||||
|
'allowed-account-ids': '999999999999',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||||
|
|
||||||
|
await run();
|
||||||
|
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('does not match'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reuses existing credentials when their account is allowed', async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.USE_EXISTING_CREDENTIALS_INPUTS,
|
||||||
|
'allowed-account-ids': '111111111111',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||||
|
|
||||||
|
await run();
|
||||||
|
expect(core.notice).toHaveBeenCalledWith('Pre-existing credentials are valid. No need to generate new ones.');
|
||||||
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Global Timeout Configuration', {}, () => {
|
describe('Global Timeout Configuration', {}, () => {
|
||||||
@@ -1240,6 +1267,21 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('masks credentials embedded in the proxy URL', async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.GH_OIDC_INPUTS,
|
||||||
|
'http-proxy': 'http://user:secretpass@proxy.example.com:8080',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('user');
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
||||||
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AWS Profile Support', {}, () => {
|
describe('AWS Profile Support', {}, () => {
|
||||||
|
|||||||
@@ -114,6 +114,22 @@ describe('Profile Manager', {}, () => {
|
|||||||
const result = stringifyIni({ dev: {} });
|
const result = stringifyIni({ dev: {} });
|
||||||
expect(result).toBe('[dev]\n');
|
expect(result).toBe('[dev]\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects values containing newlines', {}, () => {
|
||||||
|
expect(() =>
|
||||||
|
stringifyIni({ dev: { aws_session_token: 'token\n[injected]\ncredential_process = evil' } }),
|
||||||
|
).toThrow('must not contain newline characters');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects keys containing newlines', {}, () => {
|
||||||
|
expect(() => stringifyIni({ dev: { 'key\ninjected': 'val' } })).toThrow('must not contain newline characters');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects section names containing newlines', {}, () => {
|
||||||
|
expect(() => stringifyIni({ 'dev\r\n[injected]': { key: 'val' } })).toThrow(
|
||||||
|
'must not contain newline characters',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateProfileName', {}, () => {
|
describe('validateProfileName', {}, () => {
|
||||||
@@ -423,6 +439,24 @@ describe('Profile Manager', {}, () => {
|
|||||||
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('refuses to write credentials containing newlines instead of injecting profiles', {}, () => {
|
||||||
|
expect(() =>
|
||||||
|
writeProfileFiles(
|
||||||
|
'dev',
|
||||||
|
{
|
||||||
|
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||||
|
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||||
|
SessionToken: 'token\n[injected]\ncredential_process = evil-command',
|
||||||
|
},
|
||||||
|
'us-east-1',
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
).toThrow('must not contain newline characters');
|
||||||
|
|
||||||
|
const credsPath = getProfileFilePaths().credentials;
|
||||||
|
expect(fs.existsSync(credsPath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('uses correct section naming for default profile', {}, () => {
|
it('uses correct section naming for default profile', {}, () => {
|
||||||
writeProfileFiles(
|
writeProfileFiles(
|
||||||
'default',
|
'default',
|
||||||
|
|||||||
Reference in New Issue
Block a user