mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-06-05 19:53:32 +00:00
Compare commits
51 commits
kellertk/3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
037dd16322 |
||
|
|
89a34d9b83 |
||
|
|
d63f12fba5 |
||
|
|
aefb6ea018 |
||
|
|
bf27562715 | ||
|
|
4f3ef32554 |
||
|
|
26b365ff2f |
||
|
|
262ce4cfb5 |
||
|
|
e7f100cf4c |
||
|
|
bbbffeab00 | ||
|
|
d6f5dc331b |
||
|
|
12014c0798 |
||
|
|
4ab3589ed2 |
||
|
|
99214aa688 | ||
|
|
217d17914b |
||
|
|
5548f3441b | ||
|
|
77cd089899 |
||
|
|
dbacf3135e |
||
|
|
87eb0cf693 |
||
|
|
acca2b1b20 |
||
|
|
c329d242ce | ||
|
|
c39f282697 |
||
|
|
8188bee95b |
||
|
|
477988d772 |
||
|
|
9a5ab5bbe8 | ||
|
|
baa1fdfef9 |
||
|
|
4be0a3c167 |
||
|
|
f85f964a2e | ||
|
|
6fddd0cf67 |
||
|
|
254d42c4b6 | ||
|
|
01e40c97bf |
||
|
|
48a00774d0 | ||
|
|
18e0298e7d |
||
|
|
36eb080f7f | ||
|
|
3d40fa4093 |
||
|
|
3cc0e19239 |
||
|
|
e8614cfbf0 | ||
|
|
4684f47f89 |
||
|
|
48b8685c96 | ||
|
|
fe6ad3af19 |
||
|
|
2520c5e921 | ||
|
|
bc1093db1d |
||
|
|
ffde832a1d |
||
|
|
707acd96f6 | ||
|
|
a7c33ae483 |
||
|
|
713aaabfec |
||
|
|
e6e8eba750 | ||
|
|
58e7c47adf |
||
|
|
f35a7d7d7e |
||
|
|
3884f59ecd | ||
|
|
e0ba768507 |
17 changed files with 22573 additions and 20592 deletions
|
|
@ -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.1.1"
|
".": "6.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -2,6 +2,34 @@
|
||||||
|
|
||||||
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.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)
|
## [6.1.1](https://github.com/aws-actions/configure-aws-credentials/compare/v6.1.0...v6.1.1) (2026-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
394
README.md
394
README.md
|
|
@ -1,77 +1,76 @@
|
||||||
# Configure AWS Credentials
|
# Configure AWS Credentials
|
||||||
|
|
||||||
Authenticate to AWS in GitHub Actions! Works especially well with
|
Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
||||||
[AWS Secrets Manager][secretsmanager].
|
[AWS Secrets Manager][secretsmanager].
|
||||||
|
|
||||||
[secretsmanager]:
|
[secretsmanager]: https://github.com/aws-actions/aws-secretsmanager-get-secrets
|
||||||
https://github.com/aws-actions/aws-secretsmanager-get-secrets
|
|
||||||
|
|
||||||
## Quick Start (OIDC, recommended)
|
## Quick Start (OIDC, recommended)
|
||||||
|
|
||||||
1. Create an IAM Identity Provider in your AWS account for GitHub OIDC. (See
|
1. Create an IAM Identity Provider in your AWS account for GitHub OIDC. (See
|
||||||
[OIDC configuration](#oidc-configuration-details) below for details.)
|
[OIDC configuration](#oidc-configuration-details) below for details.)
|
||||||
2. Create an IAM Role in your AWS account with a trust policy that allows
|
2. Create an IAM Role in your AWS account with a trust policy that allows GitHub
|
||||||
GitHub Actions to assume it. (Expand the sections below) <details>
|
Actions to assume it. (Expand the sections below) <details>
|
||||||
<summary>GitHub OIDC Trust Policy</summary>
|
<summary>GitHub OIDC Trust Policy</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Principal": {
|
"Principal": {
|
||||||
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
|
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
|
||||||
},
|
},
|
||||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||||
"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>/<GITHUB_REPOSITORY>:ref:refs/heads/<GITHUB_BRANCH>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
Note: if you are running in a GitHub environment based workflow, the value
|
Note: if you are running in a GitHub environment based workflow, the value
|
||||||
for the Sub claim will be different, in the form of
|
for the Sub claim will be different, in the form of
|
||||||
`repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:environment:<ENVIRONMENT_NAME>`.
|
`repo:<GITHUB_ORG>/<GITHUB_REPOSITORY>:environment:<ENVIRONMENT_NAME>`.
|
||||||
Adjust the trust policy accordingly if you are using environment-based
|
Adjust the trust policy accordingly if you are using environment-based
|
||||||
workflows.
|
workflows.
|
||||||
|
|
||||||
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.
|
||||||
4. Add the following to your GitHub Actions workflow: <details>
|
4. Add the following to your GitHub Actions workflow: <details>
|
||||||
<summary>Example Workflow</summary>
|
<summary>Example Workflow</summary>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Need ID token write permission to use OIDC
|
# Need ID token write permission to use OIDC
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
jobs:
|
jobs:
|
||||||
run_job_with_aws:
|
run_job_with_aws:
|
||||||
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.1.0
|
||||||
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>
|
||||||
- name: Additional steps
|
- name: Additional steps
|
||||||
run: |
|
run: |
|
||||||
# Your commands that require AWS credentials
|
# Your commands that require AWS credentials
|
||||||
aws sts get-caller-identity
|
aws sts get-caller-identity
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
That's it! Your GitHub Actions workflow can now access AWS resources using
|
That's it! Your GitHub Actions workflow can now access AWS resources using the
|
||||||
the IAM Role you created. Other authentication scenarios are also supported
|
IAM Role you created. Other authentication scenarios are also supported (see
|
||||||
(see below).
|
below).
|
||||||
|
|
||||||
## Security Recommendations
|
## Security Recommendations
|
||||||
|
|
||||||
|
|
@ -87,8 +86,8 @@ Authenticate to AWS in GitHub Actions! Works especially well with
|
||||||
of the credentials used in workflows.
|
of the credentials used in workflows.
|
||||||
- Periodically rotate any long-lived credentials that you use.
|
- Periodically rotate any long-lived credentials that you use.
|
||||||
- Store sensitive information in a secure way, such as using
|
- Store sensitive information in a secure way, such as using
|
||||||
[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) or
|
[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) or [GitHub
|
||||||
[GitHub Secrets][gh-secrets].
|
Secrets][gh-secrets].
|
||||||
- Be especially careful about running Actions in non-ephemeral environments, or
|
- 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)
|
[triggering workflows on `pull_request_target`](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target)
|
||||||
events.
|
events.
|
||||||
|
|
@ -111,11 +110,12 @@ by specifying different inputs.
|
||||||
5. Use credentials stored in the Action environment to fetch temporary
|
5. Use credentials stored in the Action environment to fetch temporary
|
||||||
credentials via STS AssumeRole.
|
credentials via STS AssumeRole.
|
||||||
|
|
||||||
Because we use the AWS JavaScript SDK, we always will use the
|
Because we use the AWS JavaScript SDK, we always will use the [credential
|
||||||
[credential resolution flow for Node.js][cred-resolution].
|
resolution flow for Node.js][cred-resolution].
|
||||||
|
|
||||||
[cred-resolution]:
|
[cred-resolution]:
|
||||||
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
|
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.
|
Depending on your inputs, the action might override parts of this flow.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
@ -137,8 +137,8 @@ enabling this option._
|
||||||
|
|
||||||
Additionally, **`aws-region`** is always required.
|
Additionally, **`aws-region`** is always required.
|
||||||
|
|
||||||
_Note: If you use GitHub Enterprise Server, you may need to adjust examples
|
_Note: If you use GitHub Enterprise Server, you may need to adjust examples here
|
||||||
here to match your environment._
|
to match your environment._
|
||||||
|
|
||||||
## Additional Options
|
## Additional Options
|
||||||
|
|
||||||
|
|
@ -150,36 +150,39 @@ detail.
|
||||||
<details>
|
<details>
|
||||||
<summary>Options list and descriptions</summary>
|
<summary>Options list and descriptions</summary>
|
||||||
|
|
||||||
| Option | Description | Required |
|
| Option | Description | Required |
|
||||||
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||||
| aws-region | Which AWS region to use | Yes |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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-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-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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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-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-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-session-name | Defaults to "GitHubActions", but may be changed if required. | No |
|
||||||
| role-skip-session-tagging | Skips session tagging if set. | 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 |
|
| transitive-tag-keys | Define a list of transitive tag keys to pass when assuming a role. | No |
|
||||||
| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | 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 |
|
||||||
| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No |
|
| inline-session-policy | You may further restrict the assumed role policy by defining an inline 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 |
|
| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | 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 |
|
| 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 |
|
||||||
| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | 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 |
|
||||||
| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No |
|
| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No |
|
||||||
| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No |
|
| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | 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 |
|
| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No |
|
||||||
| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | 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 |
|
||||||
| 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 |
|
| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | No |
|
||||||
| force-skip-oidc | When set, the action will skip using GitHub OIDC provider even if the id-token permission is set. | 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 |
|
||||||
| 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 |
|
| 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>
|
</details>
|
||||||
|
|
||||||
|
|
@ -216,8 +219,8 @@ Profile names may not contain whitespace, square brackets, or forward or
|
||||||
backslashes.
|
backslashes.
|
||||||
|
|
||||||
Writing to a profile will prevent credentials being written to the environment
|
Writing to a profile will prevent credentials being written to the environment
|
||||||
by default. Use `output-env-credentials: true` if you would like the
|
by default. Use `output-env-credentials: true` if you would like the credentials
|
||||||
credentials to also be exported as environment variables.
|
to also be exported as environment variables.
|
||||||
|
|
||||||
By default, the action will not overwrite existing profiles. If you would like
|
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`.
|
to overwrite a profile, set the `overwrite-aws-profile` input to `true`.
|
||||||
|
|
@ -232,8 +235,8 @@ 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
|
valid credentials unintentionally. Writing to configuration files is intended
|
||||||
for unusual authentication scenarios._
|
for unusual authentication scenarios._
|
||||||
|
|
||||||
For using profiles with static IAM User Credentials or when using one
|
For using profiles with static IAM User Credentials or when using one role to
|
||||||
role to assume another, role chaining is needed:
|
assume another, role chaining is needed:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
|
|
@ -243,7 +246,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.1.0
|
||||||
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
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
|
@ -254,9 +257,9 @@ specify the profile name as an environment variable in the job step:
|
||||||
AWS_PROFILE: MyProfile1
|
AWS_PROFILE: MyProfile1
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using one role to assume another while using profiles, the
|
If you are using one role to assume another while using profiles, the subsequent
|
||||||
subsequent steps must set `role-chaining: true` and specify the prior profile's
|
steps must set `role-chaining: true` and specify the prior profile's name as
|
||||||
name as step environment variables:
|
step environment variables:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
|
|
@ -288,8 +291,8 @@ from the environment. To skip this step, set the `AWS_SKIP_CLEANUP_STEP`
|
||||||
environment variable to `true`:
|
environment variable to `true`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
env:
|
env:
|
||||||
AWS_SKIP_CLEANUP_STEP: 'true'
|
AWS_SKIP_CLEANUP_STEP: "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Use an HTTP proxy
|
#### Use an HTTP proxy
|
||||||
|
|
@ -322,11 +325,12 @@ HTTP_PROXY="http://companydomain.com:3128"
|
||||||
#### Special characters in AWS_SECRET_ACCESS_KEY
|
#### Special characters in AWS_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
Some edge cases are unable to properly parse an `AWS_SECRET_ACCESS_KEY` if it
|
Some edge cases are unable to properly parse an `AWS_SECRET_ACCESS_KEY` if it
|
||||||
contains special characters. For more information, please see the
|
contains special characters. For more information, please see the [AWS CLI
|
||||||
[AWS CLI documentation][aws-cli-troubleshooting].
|
documentation][aws-cli-troubleshooting].
|
||||||
|
|
||||||
[aws-cli-troubleshooting]:
|
[aws-cli-troubleshooting]:
|
||||||
https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-troubleshooting.html#tshoot-signature-does-not-match
|
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
|
If you set the `special-characters-workaround` option, this action will
|
||||||
continually retry fetching credentials until we get one that does not have
|
continually retry fetching credentials until we get one that does not have
|
||||||
special characters. This option overrides the `disable-retry` and
|
special characters. This option overrides the `disable-retry` and
|
||||||
|
|
@ -343,15 +347,13 @@ _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
|
`${{ github.run_id }}` so as to clarify in audit logs which AWS actions were
|
||||||
performed by which workflow run._
|
performed by which workflow run._
|
||||||
|
|
||||||
The session will be tagged with the following tags: (Refer to
|
The session will be tagged with the following tags: (Refer to [GitHub's
|
||||||
[GitHub's documentation for `GITHUB_` environment variable
|
documentation for `GITHUB_` environment variable definitions][gh-env-vars])
|
||||||
definitions][gh-env-vars])
|
|
||||||
|
|
||||||
[gh-env-vars]:
|
[gh-env-vars]:
|
||||||
https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
||||||
|
|
||||||
**Protected tags** are always emitted when session tags are used, and cannot be
|
**Default tags** are always emitted when session tags are used.
|
||||||
overridden via `custom-tags`:
|
|
||||||
|
|
||||||
| Key | Value |
|
| Key | Value |
|
||||||
| ---------- | ----------------- |
|
| ---------- | ----------------- |
|
||||||
|
|
@ -363,38 +365,45 @@ overridden via `custom-tags`:
|
||||||
| Commit | GITHUB_SHA |
|
| Commit | GITHUB_SHA |
|
||||||
| Branch | GITHUB_REF |
|
| Branch | GITHUB_REF |
|
||||||
|
|
||||||
**Overrideable tags** are automatically added to the set of default session
|
**Droppable tags** are automatically added to the set of default session tags.
|
||||||
tags but may be overridden via `custom-tags`. AWS has a maximum limit of 50
|
If the session tags exceed the [packed size limit][packed-size-limit], these
|
||||||
session tags; tags from this list are dropped in reverse priority order if
|
tags will be dropped, and the AssumeRole call will be retried. If it still
|
||||||
your `custom-tags` set plus the protected set exceeds this limit.
|
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.)
|
||||||
|
|
||||||
| Key | Value | Priority |
|
[packed-size-limit]:
|
||||||
| --------------- | ----------------------- | -------- |
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_know
|
||||||
| EventName | GITHUB_EVENT_NAME | 1 |
|
|
||||||
| BaseRef | GITHUB_BASE_REF | 2 |
|
|
||||||
| HeadRef | GITHUB_HEAD_REF | 3 |
|
|
||||||
| RefName | GITHUB_REF_NAME | 4 |
|
|
||||||
| RunId | GITHUB_RUN_ID | 5 |
|
|
||||||
| RefType | GITHUB_REF_TYPE | 6 |
|
|
||||||
| Job | GITHUB_JOB | 7 |
|
|
||||||
| TriggeringActor | GITHUB_TRIGGERING_ACTOR | 8 |
|
|
||||||
|
|
||||||
Tags whose source environment variable is unset are omitted (e.g., `BaseRef`
|
| Key | Value |
|
||||||
and `HeadRef` are only set on `pull_request` events).
|
| --------------- | ----------------------- |
|
||||||
|
| 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
|
_Note: all tag values must conform to
|
||||||
[the tag requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html).
|
[the tag requirements][sts-tag-requirements].
|
||||||
Values longer than 256 characters will be truncated, and characters outside the
|
Values longer than 256 characters will be truncated, and characters outside the
|
||||||
allowed set will be replaced with an underscore (`_`)._
|
allowed set will be replaced with an underscore (`_`)._
|
||||||
|
|
||||||
The action will use session tagging by default unless you are using OIDC.
|
[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
|
To [forward session tags to subsequent sessions in a role
|
||||||
chain][session-tag-chaining], you can use
|
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
|
[session-tag-chaining]:
|
||||||
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining
|
||||||
the `transitive-tag-keys` input to specify the keys of the tags to be passed.
|
|
||||||
|
|
||||||
_Note that all subsequent roles in the chain must have
|
_Note that all subsequent roles in the chain must have
|
||||||
`role-skip-session-tagging` set to `true`_
|
`role-skip-session-tagging` set to `true`_
|
||||||
|
|
@ -412,9 +421,10 @@ with:
|
||||||
### Custom session tags
|
### Custom session tags
|
||||||
|
|
||||||
You can add custom session tags using the `custom-tags` input, which accepts a
|
You can add custom session tags using the `custom-tags` input, which accepts a
|
||||||
JSON object. Custom tags cannot override protected tags, but they can override
|
JSON object. Custom tags cannot override existing tags. Note that AWS allows a
|
||||||
overrideable tags (in which case the overrideable tag's slot is freed for the
|
maximum of 50 tags (so you can supply a maximum of 43 custom tags), although it
|
||||||
next overrideable tag in the priority list, if any).
|
is likely that you will exceed the [packed size limit][packed-size-limit]
|
||||||
|
before you exceed the maximum number of tags.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
|
|
@ -566,41 +576,42 @@ aws iam create-open-id-connect-provider \
|
||||||
|
|
||||||
### Claims and scoping permissions
|
### Claims and scoping permissions
|
||||||
|
|
||||||
To align with the Amazon IAM best practice of
|
To align with the Amazon IAM best practice of [granting least
|
||||||
[granting least privilege][least-privilege],
|
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]:
|
[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
|
||||||
the assume role policy document should contain a
|
[gh-blog-oidc]:
|
||||||
[`Condition`](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html)
|
https://aws.amazon.com/about-aws/whats-new/2026/01/aws-sts-supports-validation-identity-provider-claims/
|
||||||
that specifies a subject (`sub`) allowed to assume the role.
|
[sub-claim-custom]:
|
||||||
[GitHub also recommends](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#defining-trust-conditions-on-cloud-roles-using-oidc-claims)
|
https://docs.github.com/en/rest/actions/oidc?apiVersion=2026-03-10
|
||||||
filtering for the correct audience (`aud`). See
|
|
||||||
[AWS IAM documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif)
|
|
||||||
on which claims you can filter for in your trust policies.
|
|
||||||
|
|
||||||
Without a subject (`sub`) condition, any GitHub user or repository could
|
#### Inspecting the token
|
||||||
potentially assume the role. The subject can be scoped to a GitHub organization
|
|
||||||
and repository as shown in the CloudFormation template. However, scoping it down
|
If you aren't sure what claim values your workflow is producing, the
|
||||||
to your org and repo may cause the role assumption to fail in some cases. See
|
|
||||||
[Example subject claims](https://docs.github.com/en/actions/reference/security/oidc#example-subject-claims)
|
|
||||||
for specific details on what the subject value will be depending on your
|
|
||||||
workflow. You can also
|
|
||||||
[customize your subject claim](https://docs.github.com/en/actions/reference/security/oidc#customizing-the-token-claims)
|
|
||||||
if you want full control over the information you can filter for in your trust
|
|
||||||
policy. If you aren't sure what your subject (`sub`) key is, you can add the
|
|
||||||
[`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger)
|
[`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger)
|
||||||
action to your workflow to see the value of the subject (`sub`) key, as well as
|
action will print the decoded JWT payload. Run it in a private repository
|
||||||
other claims.
|
only — the token itself is short-lived but the claim values may be sensitive.
|
||||||
|
|
||||||
Additional claim conditions can be added for higher specificity as explained in
|
See the GitHub [security-hardening guide][gh-oidc-hardening] for further
|
||||||
the
|
discussion of trust conditions and threat modeling.
|
||||||
[GitHub documentation][gh-oidc-hardening].
|
|
||||||
|
|
||||||
[gh-oidc-hardening]:
|
[gh-oidc-hardening]:
|
||||||
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
|
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
|
||||||
Due to implementation details, not every OIDC claim is presently supported by
|
|
||||||
IAM.
|
|
||||||
|
|
||||||
### Further information about OIDC
|
### Further information about OIDC
|
||||||
|
|
||||||
|
|
@ -612,6 +623,57 @@ For further information on OIDC and GitHub Actions, please see:
|
||||||
- [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 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/)
|
- [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
|
## Examples
|
||||||
|
|
||||||
### AssumeRoleWithWebIdentity
|
### AssumeRoleWithWebIdentity
|
||||||
|
|
@ -709,6 +771,13 @@ This example shows that you can reference the fetched credentials as outputs if
|
||||||
the `aws-session-token` input in a situation where session tokens are fetched
|
the `aws-session-token` input in a situation where session tokens are fetched
|
||||||
and passed to this action.
|
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
|
### Configure multiple AWS profiles in a single workflow
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
@ -741,8 +810,8 @@ and passed to this action.
|
||||||
This example shows how to configure multiple named AWS profiles in a single
|
This example shows how to configure multiple named AWS profiles in a single
|
||||||
workflow. When using the `aws-profile` input, credentials are written to
|
workflow. When using the `aws-profile` input, credentials are written to
|
||||||
`~/.aws/credentials` and `~/.aws/config` files, allowing you to reference
|
`~/.aws/credentials` and `~/.aws/config` files, allowing you to reference
|
||||||
different profiles using the `--profile` flag with AWS CLI, SDKs, CDK, and
|
different profiles using the `--profile` flag with AWS CLI, SDKs, CDK, and other
|
||||||
other tools.
|
tools.
|
||||||
|
|
||||||
Each profile is independent and can authenticate to different AWS accounts or
|
Each profile is independent and can authenticate to different AWS accounts or
|
||||||
use different roles. This is particularly useful for multi-account deployments
|
use different roles. This is particularly useful for multi-account deployments
|
||||||
|
|
@ -755,6 +824,7 @@ Starting with version 5.0.0, this action uses semantic-style release tags and
|
||||||
|
|
||||||
[immutable-releases]:
|
[immutable-releases]:
|
||||||
https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/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
|
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.).
|
to the latest major version (vN -> vN.2.1, vM -> vM.0.0, etc.).
|
||||||
|
|
||||||
|
|
|
||||||
521
THIRD-PARTY
521
THIRD-PARTY
|
|
@ -431,11 +431,12 @@ 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 packages may be included in this product:
|
||||||
|
|
||||||
|
- @aws-crypto/crc32@5.2.0
|
||||||
- @aws-crypto/util@5.2.0
|
- @aws-crypto/util@5.2.0
|
||||||
|
|
||||||
This package contains the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
|
|
@ -641,20 +642,11 @@ Apache License
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/client-sts@3.1044.0
|
- @aws-sdk/client-sts@3.1061.0
|
||||||
- @aws-sdk/util-user-agent-browser@3.972.10
|
|
||||||
- @aws-sdk/util-user-agent-node@3.973.24
|
|
||||||
- @smithy/middleware-retry@4.5.7
|
|
||||||
- @smithy/querystring-builder@4.2.14
|
|
||||||
- @smithy/querystring-parser@4.2.14
|
|
||||||
- @smithy/service-error-classification@4.3.1
|
|
||||||
- @smithy/url-parser@4.2.14
|
|
||||||
- @smithy/util-defaults-mode-browser@4.3.49
|
|
||||||
- @smithy/util-defaults-mode-node@4.2.54
|
|
||||||
|
|
||||||
These packages each contain the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
|
|
@ -862,17 +854,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/middleware-host-header@3.972.10
|
- @aws-sdk/signature-v4-multi-region@3.996.31
|
||||||
- @aws-sdk/middleware-recursion-detection@3.972.11
|
- @smithy/core@3.24.6
|
||||||
- @aws-sdk/middleware-sdk-s3@3.972.37
|
- @smithy/types@4.14.3
|
||||||
- @aws-sdk/middleware-user-agent@3.972.38
|
|
||||||
- @aws-sdk/signature-v4-multi-region@3.996.25
|
|
||||||
- @smithy/core@3.23.17
|
|
||||||
- @smithy/invalid-dependency@4.2.14
|
|
||||||
- @smithy/middleware-serde@4.2.20
|
|
||||||
- @smithy/protocol-http@5.3.14
|
|
||||||
- @smithy/smithy-client@4.12.13
|
|
||||||
- @smithy/types@4.14.1
|
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|
@ -1270,7 +1254,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.8
|
- @aws-sdk/core@3.974.17
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|
@ -1690,40 +1674,20 @@ 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.34
|
- @aws-sdk/credential-provider-env@3.972.43
|
||||||
- @aws-sdk/credential-provider-ini@3.972.38
|
- @aws-sdk/credential-provider-ini@3.972.48
|
||||||
- @aws-sdk/credential-provider-node@3.972.39
|
- @aws-sdk/credential-provider-node@3.972.50
|
||||||
- @aws-sdk/region-config-resolver@3.972.13
|
- @aws-sdk/token-providers@3.1060.0
|
||||||
- @aws-sdk/token-providers@3.1041.0
|
- @aws-sdk/types@3.973.10
|
||||||
- @aws-sdk/types@3.973.8
|
|
||||||
- @aws-sdk/util-arn-parser@3.972.3
|
|
||||||
- @aws-sdk/util-endpoints@3.996.8
|
|
||||||
- @aws-sdk/util-locate-window@3.965.5
|
- @aws-sdk/util-locate-window@3.965.5
|
||||||
- @aws-sdk/xml-builder@3.972.22
|
- @aws-sdk/xml-builder@3.972.27
|
||||||
- @smithy/config-resolver@4.4.17
|
- @smithy/credential-provider-imds@4.3.7
|
||||||
- @smithy/credential-provider-imds@4.2.14
|
- @smithy/fetch-http-handler@5.4.6
|
||||||
- @smithy/fetch-http-handler@5.3.17
|
|
||||||
- @smithy/hash-node@4.2.14
|
|
||||||
- @smithy/is-array-buffer@2.2.0
|
- @smithy/is-array-buffer@2.2.0
|
||||||
- @smithy/is-array-buffer@4.2.2
|
- @smithy/node-http-handler@4.7.6
|
||||||
- @smithy/middleware-content-length@4.2.14
|
- @smithy/signature-v4@5.4.6
|
||||||
- @smithy/middleware-endpoint@4.4.32
|
|
||||||
- @smithy/middleware-stack@4.2.14
|
|
||||||
- @smithy/node-http-handler@4.6.1
|
|
||||||
- @smithy/property-provider@4.2.14
|
|
||||||
- @smithy/shared-ini-file-loader@4.4.9
|
|
||||||
- @smithy/signature-v4@5.3.14
|
|
||||||
- @smithy/util-base64@4.3.2
|
|
||||||
- @smithy/util-body-length-browser@4.2.2
|
|
||||||
- @smithy/util-body-length-node@4.2.3
|
|
||||||
- @smithy/util-buffer-from@2.2.0
|
- @smithy/util-buffer-from@2.2.0
|
||||||
- @smithy/util-buffer-from@4.2.2
|
|
||||||
- @smithy/util-hex-encoding@4.2.2
|
|
||||||
- @smithy/util-stream@4.5.25
|
|
||||||
- @smithy/util-uri-escape@4.2.2
|
|
||||||
- @smithy/util-utf8@2.3.0
|
- @smithy/util-utf8@2.3.0
|
||||||
- @smithy/util-utf8@4.2.2
|
|
||||||
- @smithy/uuid@1.1.2
|
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|
@ -1933,9 +1897,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.34
|
- @aws-sdk/credential-provider-process@3.972.43
|
||||||
- @aws-sdk/credential-provider-sso@3.972.38
|
- @aws-sdk/credential-provider-sso@3.972.47
|
||||||
- @aws-sdk/credential-provider-web-identity@3.972.38
|
- @aws-sdk/credential-provider-web-identity@3.972.47
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|
@ -2145,433 +2109,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/middleware-logger@3.972.10
|
- @aws-sdk/credential-provider-http@3.972.45
|
||||||
- @smithy/node-config-provider@4.3.14
|
- @aws-sdk/credential-provider-login@3.972.47
|
||||||
- @smithy/util-config-provider@4.2.2
|
- @aws-sdk/nested-clients@3.997.15
|
||||||
|
|
||||||
These packages each contain the following license:
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
-----------
|
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
|
||||||
|
|
||||||
- @smithy/util-endpoints@3.4.2
|
|
||||||
- @smithy/util-middleware@4.2.14
|
|
||||||
- @smithy/util-retry@4.3.8
|
|
||||||
|
|
||||||
These packages each contain the following license:
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
-----------
|
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
|
||||||
|
|
||||||
- @aws-sdk/credential-provider-http@3.972.36
|
|
||||||
- @aws-sdk/credential-provider-login@3.972.38
|
|
||||||
- @aws-sdk/nested-clients@3.997.6
|
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|
@ -2795,8 +2335,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @nodable/entities@2.1.0
|
- @nodable/entities@2.1.1
|
||||||
- quickjs-wasi@2.2.0
|
- quickjs-wasi@2.2.0
|
||||||
|
- xml-naming@0.1.0
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|
@ -2836,7 +2377,7 @@ SOFTWARE.
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- fast-xml-parser@5.7.2
|
- fast-xml-parser@5.7.3
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|
@ -2866,7 +2407,7 @@ SOFTWARE.
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- strnum@2.2.3
|
- strnum@2.3.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|
@ -2926,7 +2467,7 @@ SOFTWARE.
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- fast-xml-builder@1.1.9
|
- fast-xml-builder@1.2.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|
|
||||||
24
dist/cleanup/index.js
generated
vendored
24
dist/cleanup/index.js
generated
vendored
|
|
@ -5707,7 +5707,7 @@ var require_client_h1 = __commonJS({
|
||||||
kResume,
|
kResume,
|
||||||
kHTTPContext
|
kHTTPContext
|
||||||
} = require_symbols();
|
} = require_symbols();
|
||||||
var constants3 = require_constants2();
|
var constants4 = require_constants2();
|
||||||
var EMPTY_BUF = Buffer.alloc(0);
|
var EMPTY_BUF = Buffer.alloc(0);
|
||||||
var FastBuffer = Buffer[Symbol.species];
|
var FastBuffer = Buffer[Symbol.species];
|
||||||
var addListener = util.addListener;
|
var addListener = util.addListener;
|
||||||
|
|
@ -5779,7 +5779,7 @@ var require_client_h1 = __commonJS({
|
||||||
constructor(client, socket, { exports: exports3 }) {
|
constructor(client, socket, { exports: exports3 }) {
|
||||||
assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0);
|
assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0);
|
||||||
this.llhttp = exports3;
|
this.llhttp = exports3;
|
||||||
this.ptr = this.llhttp.llhttp_alloc(constants3.TYPE.RESPONSE);
|
this.ptr = this.llhttp.llhttp_alloc(constants4.TYPE.RESPONSE);
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
|
|
@ -5874,19 +5874,19 @@ var require_client_h1 = __commonJS({
|
||||||
currentBufferRef = null;
|
currentBufferRef = null;
|
||||||
}
|
}
|
||||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr;
|
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr;
|
||||||
if (ret === constants3.ERROR.PAUSED_UPGRADE) {
|
if (ret === constants4.ERROR.PAUSED_UPGRADE) {
|
||||||
this.onUpgrade(data.slice(offset));
|
this.onUpgrade(data.slice(offset));
|
||||||
} else if (ret === constants3.ERROR.PAUSED) {
|
} else if (ret === constants4.ERROR.PAUSED) {
|
||||||
this.paused = true;
|
this.paused = true;
|
||||||
socket.unshift(data.slice(offset));
|
socket.unshift(data.slice(offset));
|
||||||
} else if (ret !== constants3.ERROR.OK) {
|
} else if (ret !== constants4.ERROR.OK) {
|
||||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr);
|
const ptr = llhttp.llhttp_get_error_reason(this.ptr);
|
||||||
let message = "";
|
let message = "";
|
||||||
if (ptr) {
|
if (ptr) {
|
||||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0);
|
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0);
|
||||||
message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")";
|
message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")";
|
||||||
}
|
}
|
||||||
throw new HTTPParserError(message, constants3.ERROR[ret], data.slice(offset));
|
throw new HTTPParserError(message, constants4.ERROR[ret], data.slice(offset));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
util.destroy(socket, err);
|
util.destroy(socket, err);
|
||||||
|
|
@ -6061,7 +6061,7 @@ var require_client_h1 = __commonJS({
|
||||||
socket[kBlocking] = false;
|
socket[kBlocking] = false;
|
||||||
client[kResume]();
|
client[kResume]();
|
||||||
}
|
}
|
||||||
return pause ? constants3.ERROR.PAUSED : 0;
|
return pause ? constants4.ERROR.PAUSED : 0;
|
||||||
}
|
}
|
||||||
onBody(buf) {
|
onBody(buf) {
|
||||||
const { client, socket, statusCode, maxResponseSize } = this;
|
const { client, socket, statusCode, maxResponseSize } = this;
|
||||||
|
|
@ -6083,7 +6083,7 @@ var require_client_h1 = __commonJS({
|
||||||
}
|
}
|
||||||
this.bytesRead += buf.length;
|
this.bytesRead += buf.length;
|
||||||
if (request.onData(buf) === false) {
|
if (request.onData(buf) === false) {
|
||||||
return constants3.ERROR.PAUSED;
|
return constants4.ERROR.PAUSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMessageComplete() {
|
onMessageComplete() {
|
||||||
|
|
@ -6118,13 +6118,13 @@ var require_client_h1 = __commonJS({
|
||||||
if (socket[kWriting]) {
|
if (socket[kWriting]) {
|
||||||
assert(client[kRunning] === 0);
|
assert(client[kRunning] === 0);
|
||||||
util.destroy(socket, new InformationalError("reset"));
|
util.destroy(socket, new InformationalError("reset"));
|
||||||
return constants3.ERROR.PAUSED;
|
return constants4.ERROR.PAUSED;
|
||||||
} else if (!shouldKeepAlive) {
|
} else if (!shouldKeepAlive) {
|
||||||
util.destroy(socket, new InformationalError("reset"));
|
util.destroy(socket, new InformationalError("reset"));
|
||||||
return constants3.ERROR.PAUSED;
|
return constants4.ERROR.PAUSED;
|
||||||
} else if (socket[kReset] && client[kRunning] === 0) {
|
} else if (socket[kReset] && client[kRunning] === 0) {
|
||||||
util.destroy(socket, new InformationalError("reset"));
|
util.destroy(socket, new InformationalError("reset"));
|
||||||
return constants3.ERROR.PAUSED;
|
return constants4.ERROR.PAUSED;
|
||||||
} else if (client[kPipelining] == null || client[kPipelining] === 1) {
|
} else if (client[kPipelining] == null || client[kPipelining] === 1) {
|
||||||
setImmediate(() => client[kResume]());
|
setImmediate(() => client[kResume]());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -19128,6 +19128,7 @@ function error(message, properties = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/helpers.ts
|
// src/helpers.ts
|
||||||
|
var fs3 = __toESM(require("node:fs"));
|
||||||
function errorMessage(error2) {
|
function errorMessage(error2) {
|
||||||
return error2 instanceof Error ? error2.message : String(error2);
|
return error2 instanceof Error ? error2.message : String(error2);
|
||||||
}
|
}
|
||||||
|
|
@ -19145,6 +19146,7 @@ function getBooleanInput(name, options) {
|
||||||
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``
|
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
var O_NOFOLLOW = fs3.constants.O_NOFOLLOW ?? 0;
|
||||||
|
|
||||||
// src/cleanup/index.ts
|
// src/cleanup/index.ts
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
|
|
||||||
24971
dist/index.js
generated
vendored
24971
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
1870
package-lock.json
generated
1870
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"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.1.1",
|
"version": "6.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "biome check --error-on-warnings ./src && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
||||||
"lint:fix": "biome check --write ./src && markdownlint -i node_modules -i CHANGELOG.md -f '**/*.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",
|
"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",
|
"test": "npm run lint && vitest run && npm run build",
|
||||||
"clean": "del-cli coverage test-reports node_modules",
|
"clean": "del-cli coverage test-reports node_modules",
|
||||||
|
|
@ -17,25 +17,25 @@
|
||||||
"organization": true
|
"organization": true
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/credential-provider-env": "^3.972.32",
|
"@aws-sdk/credential-provider-env": "^3.972.39",
|
||||||
"@biomejs/biome": "2.4.15",
|
"@biomejs/biome": "2.4.16",
|
||||||
"@smithy/property-provider": "^4.3.1",
|
"@smithy/property-provider": "^4.3.6",
|
||||||
"@types/node": "^25.7.0",
|
"@types/node": "^25.9.1",
|
||||||
"@vitest/coverage-v8": "^4.1.6",
|
"@vitest/coverage-v8": "4.1.8",
|
||||||
"aws-sdk-client-mock": "^4.1.0",
|
"aws-sdk-client-mock": "^4.1.0",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"generate-license-file": "^4.1.1",
|
"generate-license-file": "^4.2.1",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"markdownlint-cli": "^0.48.0",
|
"markdownlint-cli": "^0.48.0",
|
||||||
"memfs": "^4.57.2",
|
"memfs": "^4.57.6",
|
||||||
"standard-version": "^9.5.0",
|
"standard-version": "^9.5.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vitest": "^4.1.5"
|
"vitest": "4.1.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^3.0.1",
|
"@actions/core": "^3.0.1",
|
||||||
"@aws-sdk/client-sts": "^3.1045.0",
|
"@aws-sdk/client-sts": "^3.1061.0",
|
||||||
"@smithy/node-http-handler": "^4.7.1",
|
"@smithy/node-http-handler": "^4.7.3",
|
||||||
"proxy-agent": "^8.0.1"
|
"proxy-agent": "^8.0.1"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import assert from 'node:assert';
|
import assert from 'node:assert';
|
||||||
import fs from 'node:fs';
|
|
||||||
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 { AssumeRoleCommand, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
|
import {
|
||||||
|
AssumeRoleCommand,
|
||||||
|
AssumeRoleWithWebIdentityCommand,
|
||||||
|
PackedPolicyTooLargeException,
|
||||||
|
} from '@aws-sdk/client-sts';
|
||||||
import type { CredentialsClient } from './CredentialsClient';
|
import type { CredentialsClient } from './CredentialsClient';
|
||||||
import { errorMessage, isDefined, sanitizeGitHubVariables } from './helpers';
|
import { errorMessage, isDefined, readFileUtf8, sanitizeGitHubVariables } from './helpers';
|
||||||
|
|
||||||
async function assumeRoleWithOIDC(params: AssumeRoleCommandInput, client: STSClient, webIdentityToken: string) {
|
async function assumeRoleWithOIDC(params: AssumeRoleCommandInput, client: STSClient, webIdentityToken: string) {
|
||||||
delete params.Tags;
|
delete params.Tags;
|
||||||
|
|
@ -36,13 +39,14 @@ async function assumeRoleWithWebIdentityTokenFile(
|
||||||
const webIdentityTokenFilePath = path.isAbsolute(webIdentityTokenFile)
|
const webIdentityTokenFilePath = path.isAbsolute(webIdentityTokenFile)
|
||||||
? webIdentityTokenFile
|
? webIdentityTokenFile
|
||||||
: path.join(workspace, webIdentityTokenFile);
|
: path.join(workspace, webIdentityTokenFile);
|
||||||
if (!fs.existsSync(webIdentityTokenFilePath)) {
|
const webIdentityToken = readFileUtf8(webIdentityTokenFilePath);
|
||||||
|
if (webIdentityToken === null) {
|
||||||
throw new Error(`Web identity token file does not exist: ${webIdentityTokenFilePath}`);
|
throw new Error(`Web identity token file does not exist: ${webIdentityTokenFilePath}`);
|
||||||
}
|
}
|
||||||
core.info('Assuming role with web identity token file');
|
core.info('Assuming role with web identity token file');
|
||||||
try {
|
try {
|
||||||
const webIdentityToken = fs.readFileSync(webIdentityTokenFilePath, 'utf8');
|
|
||||||
delete params.Tags;
|
delete params.Tags;
|
||||||
|
delete params.TransitiveTagKeys;
|
||||||
const creds = await client.send(
|
const creds = await client.send(
|
||||||
new AssumeRoleWithWebIdentityCommand({
|
new AssumeRoleWithWebIdentityCommand({
|
||||||
...params,
|
...params,
|
||||||
|
|
@ -61,6 +65,13 @@ 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) {
|
||||||
|
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)}`);
|
throw new Error(`Could not assume role with user credentials: ${errorMessage(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,8 +98,8 @@ const MAX_TAG_KEY_LENGTH = 128;
|
||||||
const MAX_TAG_VALUE_LENGTH = 256;
|
const MAX_TAG_VALUE_LENGTH = 256;
|
||||||
const MAX_SESSION_TAGS = 50;
|
const MAX_SESSION_TAGS = 50;
|
||||||
|
|
||||||
// Identity/audit primitives. Always emitted and cannot be overridden by custom-tags.
|
// Identity/audit primitives. Always emitted and cannot be dropped.
|
||||||
const PROTECTED_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
const NON_DROPPABLE_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||||
{ key: 'Repository', envVar: 'GITHUB_REPOSITORY' },
|
{ key: 'Repository', envVar: 'GITHUB_REPOSITORY' },
|
||||||
{ key: 'Workflow', envVar: 'GITHUB_WORKFLOW' },
|
{ key: 'Workflow', envVar: 'GITHUB_WORKFLOW' },
|
||||||
{ key: 'Action', envVar: 'GITHUB_ACTION' },
|
{ key: 'Action', envVar: 'GITHUB_ACTION' },
|
||||||
|
|
@ -97,21 +108,22 @@ const PROTECTED_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||||
{ key: 'Branch', envVar: 'GITHUB_REF' },
|
{ key: 'Branch', envVar: 'GITHUB_REF' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Convenience metadata. Custom-tags may override (suppresses the default for that key).
|
// Convenience metadata. If the AssumeRole call fails due to compressed size of
|
||||||
// Listed in priority order; lower-priority entries are dropped first if the user's custom-tags
|
// session tags being too large, we will drop these tags and retry once.
|
||||||
// would push the total above MAX_SESSION_TAGS.
|
const DROPPABLE_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||||
const OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY: ReadonlyArray<{ key: string; envVar: string }> = [
|
|
||||||
{ key: 'EventName', envVar: 'GITHUB_EVENT_NAME' },
|
{ key: 'EventName', envVar: 'GITHUB_EVENT_NAME' },
|
||||||
{ key: 'BaseRef', envVar: 'GITHUB_BASE_REF' },
|
{ key: 'BaseRef', envVar: 'GITHUB_BASE_REF' },
|
||||||
{ key: 'HeadRef', envVar: 'GITHUB_HEAD_REF' },
|
{ key: 'HeadRef', envVar: 'GITHUB_HEAD_REF' },
|
||||||
{ key: 'RefName', envVar: 'GITHUB_REF_NAME' },
|
|
||||||
{ key: 'RunId', envVar: 'GITHUB_RUN_ID' },
|
{ key: 'RunId', envVar: 'GITHUB_RUN_ID' },
|
||||||
{ key: 'RefType', envVar: 'GITHUB_REF_TYPE' },
|
|
||||||
{ key: 'Job', envVar: 'GITHUB_JOB' },
|
{ key: 'Job', envVar: 'GITHUB_JOB' },
|
||||||
{ key: 'TriggeringActor', envVar: 'GITHUB_TRIGGERING_ACTOR' },
|
{ key: 'TriggeringActor', envVar: 'GITHUB_TRIGGERING_ACTOR' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROTECTED_TAG_KEYS = new Set<string>(['GitHub', ...PROTECTED_TAG_SOURCES.map((s) => s.key)]);
|
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[] {
|
export function parseAndValidateCustomTags(customTags: string, existingTags: Tag[]): Tag[] {
|
||||||
let parsed: unknown;
|
let parsed: unknown;
|
||||||
|
|
@ -198,7 +210,13 @@ export async function assumeRole(params: assumeRoleParams) {
|
||||||
// Build session tags. Values are sanitized because the AWS tag value spec is more
|
// Build session tags. Values are sanitized because the AWS tag value spec is more
|
||||||
// restrictive than permissible characters in environment variables.
|
// restrictive than permissible characters in environment variables.
|
||||||
const protectedTags: Tag[] = [{ Key: 'GitHub', Value: 'Actions' }];
|
const protectedTags: Tag[] = [{ Key: 'GitHub', Value: 'Actions' }];
|
||||||
for (const { key, envVar } of PROTECTED_TAG_SOURCES) {
|
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];
|
const value = process.env[envVar];
|
||||||
if (value) {
|
if (value) {
|
||||||
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||||
|
|
@ -206,26 +224,15 @@ export async function assumeRole(params: assumeRoleParams) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedCustomTags: Tag[] = customTags ? parseAndValidateCustomTags(customTags, protectedTags) : [];
|
const parsedCustomTags: Tag[] = customTags ? parseAndValidateCustomTags(customTags, protectedTags) : [];
|
||||||
const customTagKeys = new Set(parsedCustomTags.map((t) => t.Key));
|
|
||||||
|
|
||||||
const availableOverrideableSlots = MAX_SESSION_TAGS - protectedTags.length - parsedCustomTags.length;
|
const tagArray: Tag[] = [...protectedTags, ...parsedCustomTags];
|
||||||
const overrideableTags: Tag[] = [];
|
|
||||||
for (const { key, envVar } of OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY) {
|
|
||||||
if (overrideableTags.length >= availableOverrideableSlots) break;
|
|
||||||
if (customTagKeys.has(key)) continue;
|
|
||||||
const value = process.env[envVar];
|
|
||||||
if (value) {
|
|
||||||
overrideableTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagArray: Tag[] = [...protectedTags, ...overrideableTags, ...parsedCustomTags];
|
|
||||||
|
|
||||||
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
core.debug('Role session tagging has been skipped.');
|
core.debug('Role session tagging has been skipped.');
|
||||||
} else {
|
} else {
|
||||||
core.debug(`${tags.length} role session tags are being used:`);
|
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
|
//only populate transitiveTagKeys array if user is actually using session tagging
|
||||||
|
|
|
||||||
105
src/helpers.ts
105
src/helpers.ts
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
||||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||||
|
|
@ -291,3 +293,106 @@ export function getBooleanInput(name: string, options?: core.InputOptions & { de
|
||||||
`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``,
|
`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)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
28
src/index.ts
28
src/index.ts
|
|
@ -210,11 +210,16 @@ export async function run() {
|
||||||
// Validate that the SDK can actually pick up credentials.
|
// Validate that the SDK can actually pick up credentials.
|
||||||
// This validates cases where this action is using existing environment 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.
|
// and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
|
||||||
await withRetry(
|
// Skip when output-env-credentials is false: input IAM keys were not written to env, so
|
||||||
() => credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds),
|
// the default chain would resolve to ambient runner credentials and the access-key check
|
||||||
'validateCredentials',
|
// would spuriously fail (see #1554).
|
||||||
);
|
if (outputEnvCredentials) {
|
||||||
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
await withRetry(
|
||||||
|
() => credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds),
|
||||||
|
'validateCredentials',
|
||||||
|
);
|
||||||
|
sourceAccountId = await withRetry(() => exportAccountId(credentialsClient, maskAccountId), 'exportAccountId');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (customTags && (useGitHubOIDCProvider() || webIdentityTokenFile)) {
|
if (customTags && (useGitHubOIDCProvider() || webIdentityTokenFile)) {
|
||||||
core.warning(
|
core.warning(
|
||||||
|
|
@ -247,13 +252,12 @@ export async function run() {
|
||||||
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
|
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
|
||||||
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
|
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
|
||||||
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
|
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
|
||||||
// We need to validate the credentials in 2 of our use-cases
|
// Validate that the SDK can pick up the assumed-role credentials from the environment.
|
||||||
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable
|
// Skip when output-env-credentials is false: the credentials were never written to env,
|
||||||
// is set to `true` then we are NOT in a self-hosted runner.
|
// so the default credential provider chain would resolve to ambient runner credentials
|
||||||
// Second: Customer provided credentials manually (IAM User keys stored in GH Secrets)
|
// (e.g. an EC2 instance profile) and the access-key-id check would spuriously fail.
|
||||||
// If we are using a profile, don't validate credentials yet (since they most likely won't be in the environment).
|
// Skip when using a profile: validation runs after the profile file is written below.
|
||||||
// Wait until after creds are written to the profile file to try validation.
|
if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile && outputEnvCredentials) {
|
||||||
if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile) {
|
|
||||||
await withRetry(
|
await withRetry(
|
||||||
() =>
|
() =>
|
||||||
credentialsClient.validateCredentials(
|
credentialsClient.validateCredentials(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import type { Credentials } from '@aws-sdk/client-sts';
|
import type { Credentials } from '@aws-sdk/client-sts';
|
||||||
|
import { mkdir, readFileUtf8, writeFileUtf8 } from './helpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an INI-format string into a nested object.
|
* Parse an INI-format string into a nested object.
|
||||||
|
|
@ -87,10 +87,8 @@ export function getProfileFilePaths(): ProfileFilePaths {
|
||||||
*/
|
*/
|
||||||
export function ensureAwsDirectoryExists(filePath: string): void {
|
export function ensureAwsDirectoryExists(filePath: string): void {
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
if (!fs.existsSync(dir)) {
|
core.debug(`Ensuring directory exists: ${dir}`);
|
||||||
core.debug(`Creating directory: ${dir}`);
|
mkdir(dir, 0o700);
|
||||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -127,14 +125,8 @@ export function mergeProfileSection(
|
||||||
data: Record<string, string>,
|
data: Record<string, string>,
|
||||||
overwriteAwsProfile: boolean,
|
overwriteAwsProfile: boolean,
|
||||||
): void {
|
): void {
|
||||||
let existingContent: Record<string, Record<string, string>> = {};
|
const fileContent = readFileUtf8(filePath);
|
||||||
|
const existingContent: Record<string, Record<string, string>> = fileContent === null ? {} : parseIni(fileContent);
|
||||||
// Read existing file if it exists
|
|
||||||
if (fs.existsSync(filePath)) {
|
|
||||||
core.debug(`Reading existing file: ${filePath}`);
|
|
||||||
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
existingContent = parseIni(fileContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingContent[sectionName] && !overwriteAwsProfile) {
|
if (existingContent[sectionName] && !overwriteAwsProfile) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -147,7 +139,7 @@ export function mergeProfileSection(
|
||||||
const content = stringifyIni(existingContent);
|
const content = stringifyIni(existingContent);
|
||||||
|
|
||||||
core.debug(`Writing profile to ${filePath}`);
|
core.debug(`Writing profile to ${filePath}`);
|
||||||
fs.writeFileSync(filePath, content, { mode: 0o600 });
|
writeFileUtf8(filePath, content, 0o600);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
63
test/assumeRole.test.ts
Normal file
63
test/assumeRole.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
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,12 +1,16 @@
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
import { fs, vol } from 'memfs';
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import * as helpers from '../src/helpers';
|
import * as helpers from '../src/helpers';
|
||||||
|
|
||||||
|
vi.mock('node:fs');
|
||||||
vi.mock('@actions/core');
|
vi.mock('@actions/core');
|
||||||
|
|
||||||
describe('Configure AWS Credentials helpers', {}, () => {
|
describe('Configure AWS Credentials helpers', {}, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.restoreAllMocks();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vol.reset();
|
||||||
});
|
});
|
||||||
it('removes brackets from GitHub Actor', {}, () => {
|
it('removes brackets from GitHub Actor', {}, () => {
|
||||||
const actor = 'actor[bot]';
|
const actor = 'actor[bot]';
|
||||||
|
|
@ -48,6 +52,9 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
||||||
helpers.reset();
|
helpers.reset();
|
||||||
});
|
});
|
||||||
it('can output creds when told to', {}, () => {
|
it('can output creds when told to', {}, () => {
|
||||||
|
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
|
||||||
|
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
|
||||||
|
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
|
||||||
helpers.exportCredentials(
|
helpers.exportCredentials(
|
||||||
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
||||||
true,
|
true,
|
||||||
|
|
@ -68,6 +75,9 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
||||||
process.env = env;
|
process.env = env;
|
||||||
});
|
});
|
||||||
it(`won't output credentials to env if told not to`, {}, () => {
|
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(
|
helpers.exportCredentials(
|
||||||
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
{ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) },
|
||||||
true,
|
true,
|
||||||
|
|
@ -95,22 +105,163 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles getBooleanInput correctly', {}, () => {
|
it('handles getBooleanInput correctly', {}, () => {
|
||||||
vi.mocked(core.getInput).mockReturnValue('true');
|
vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||||
expect(helpers.getBooleanInput('test')).toBe(true);
|
expect(helpers.getBooleanInput('test')).toBe(true);
|
||||||
|
|
||||||
vi.mocked(core.getInput).mockReturnValue('false');
|
vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||||
expect(helpers.getBooleanInput('test')).toBe(false);
|
expect(helpers.getBooleanInput('test')).toBe(false);
|
||||||
|
|
||||||
vi.mocked(core.getInput).mockReturnValue('');
|
vi.spyOn(core, 'getInput').mockReturnValue('');
|
||||||
expect(helpers.getBooleanInput('test', { default: true })).toBe(true);
|
expect(helpers.getBooleanInput('test', { default: true })).toBe(true);
|
||||||
|
|
||||||
vi.mocked(core.getInput).mockReturnValue('invalid');
|
vi.spyOn(core, 'getInput').mockReturnValue('invalid');
|
||||||
expect(() => helpers.getBooleanInput('test')).toThrow();
|
expect(() => helpers.getBooleanInput('test')).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears session token when not provided', {}, () => {
|
it('clears session token when not provided', {}, () => {
|
||||||
|
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
|
||||||
|
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
|
||||||
process.env.AWS_SESSION_TOKEN = 'old-token';
|
process.env.AWS_SESSION_TOKEN = 'old-token';
|
||||||
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test' }, false, true);
|
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test' }, false, true);
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
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\)/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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';
|
||||||
|
|
@ -151,6 +152,29 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression test for #1554: IAM keys + role-to-assume on a self-hosted runner
|
||||||
|
// with ambient credentials (e.g. an EC2 instance profile), and output-env-credentials=false.
|
||||||
|
// The post-assume-role validation must be skipped, otherwise the SDK loads the runner's
|
||||||
|
// ambient access key (which doesn't match the assumed role's) and the action fails.
|
||||||
|
describe('AssumeRole with IAM LTC and output-env-credentials=false', {}, () => {
|
||||||
|
it('does not validate against ambient credentials', async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_NO_ENV_INPUTS));
|
||||||
|
mockedSTSClient.on(AssumeRoleCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||||
|
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||||
|
// Simulate the runner's ambient instance-profile credentials.
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
||||||
|
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockResolvedValue({
|
||||||
|
accessKeyId: 'AMBIENTINSTANCEPROFILEID',
|
||||||
|
});
|
||||||
|
await run();
|
||||||
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
|
expect(core.exportVariable).not.toHaveBeenCalled();
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('aws-access-key-id', 'STSAWSACCESSKEYID');
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('aws-secret-access-key', 'STSAWSSECRETACCESSKEY');
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('aws-session-token', 'STSAWSSESSIONTOKEN');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('AssumeRole with WebIdentityTokeFile', {}, () => {
|
describe('AssumeRole with WebIdentityTokeFile', {}, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.WEBIDENTITY_TOKEN_FILE_INPUTS));
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.WEBIDENTITY_TOKEN_FILE_INPUTS));
|
||||||
|
|
@ -179,6 +203,18 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
expect(core.setOutput).toHaveBeenCalledTimes(2);
|
expect(core.setOutput).toHaveBeenCalledTimes(2);
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
it('does not send Tags or TransitiveTagKeys to AssumeRoleWithWebIdentity', async () => {
|
||||||
|
// AssumeRoleWithWebIdentity reads session tags from JWT claims, not the request.
|
||||||
|
// Both fields must be stripped before the STS call.
|
||||||
|
vi.mocked(core.getMultilineInput).mockImplementation((name: string) => {
|
||||||
|
if (name === 'transitive-tag-keys') return ['Repository'];
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
await run();
|
||||||
|
const callInput = mockedSTSClient.commandCalls(AssumeRoleWithWebIdentityCommand)[0].args[0].input;
|
||||||
|
expect(callInput.Tags).toBeUndefined();
|
||||||
|
expect(callInput.TransitiveTagKeys).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Assume existing role', {}, () => {
|
describe('Assume existing role', {}, () => {
|
||||||
|
|
@ -259,9 +295,9 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
await run();
|
await run();
|
||||||
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
// 7 protected (GitHub + Repository, Workflow, Action, Actor, Commit, Branch)
|
// 7 protected (GitHub + Repository, Workflow, Action, Actor, Commit, Branch)
|
||||||
// + 8 overrideable (EventName, BaseRef, HeadRef, RefName, RunId, RefType, Job, TriggeringActor).
|
// + 6 droppable (EventName, BaseRef, HeadRef, RunId, Job, TriggeringActor).
|
||||||
// No custom-tags, all env vars set in mocks.envs → all 15 should be present, nothing else.
|
// No custom-tags, all env vars set in mocks.envs → all 13 should be present, nothing else.
|
||||||
expect(tags).toHaveLength(15);
|
expect(tags).toHaveLength(13);
|
||||||
const tagsByKey = Object.fromEntries(tags.map((t) => [t.Key, t.Value]));
|
const tagsByKey = Object.fromEntries(tags.map((t) => [t.Key, t.Value]));
|
||||||
expect(tagsByKey).toEqual({
|
expect(tagsByKey).toEqual({
|
||||||
GitHub: 'Actions',
|
GitHub: 'Actions',
|
||||||
|
|
@ -274,14 +310,12 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
EventName: 'pull_request',
|
EventName: 'pull_request',
|
||||||
BaseRef: 'main',
|
BaseRef: 'main',
|
||||||
HeadRef: 'feature-branch',
|
HeadRef: 'feature-branch',
|
||||||
RefName: 'feature-branch',
|
|
||||||
RunId: '16412345678',
|
RunId: '16412345678',
|
||||||
RefType: 'branch',
|
|
||||||
Job: 'build',
|
Job: 'build',
|
||||||
TriggeringActor: 'MY-USERNAME_bot_',
|
TriggeringActor: 'MY-USERNAME_bot_',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('omits overrideable tags whose env vars are unset', {}, async () => {
|
it('omits droppable tags whose env vars are unset', {}, async () => {
|
||||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
delete process.env.GITHUB_BASE_REF;
|
delete process.env.GITHUB_BASE_REF;
|
||||||
delete process.env.GITHUB_HEAD_REF;
|
delete process.env.GITHUB_HEAD_REF;
|
||||||
|
|
@ -295,6 +329,27 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
expect(tagKeys).toContain('EventName');
|
expect(tagKeys).toContain('EventName');
|
||||||
expect(tagKeys).toContain('RunId');
|
expect(tagKeys).toContain('RunId');
|
||||||
});
|
});
|
||||||
|
it('drops droppable tags and retries on PackedPolicyTooLargeException', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
|
mockedSTSClient
|
||||||
|
.on(AssumeRoleCommand)
|
||||||
|
.rejectsOnce(new PackedPolicyTooLargeException({ message: 'too large', $metadata: {} }))
|
||||||
|
.resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||||
|
await run();
|
||||||
|
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 retryTagKeys = (retryInput.Tags ?? []).map((t) => t.Key);
|
||||||
|
expect(retryTagKeys).not.toContain('EventName');
|
||||||
|
expect(retryTagKeys).not.toContain('BaseRef');
|
||||||
|
expect(retryTagKeys).not.toContain('HeadRef');
|
||||||
|
expect(retryTagKeys).not.toContain('RunId');
|
||||||
|
expect(retryTagKeys).not.toContain('Job');
|
||||||
|
expect(retryTagKeys).not.toContain('TriggeringActor');
|
||||||
|
// Protected tags remain
|
||||||
|
expect(retryTagKeys).toContain('GitHub');
|
||||||
|
expect(retryTagKeys).toContain('Repository');
|
||||||
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
it('sanitizes invalid characters in env-derived tag values', {}, async () => {
|
it('sanitizes invalid characters in env-derived tag values', {}, async () => {
|
||||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
process.env.GITHUB_HEAD_REF = 'feature/has spaces&bad?chars';
|
process.env.GITHUB_HEAD_REF = 'feature/has spaces&bad?chars';
|
||||||
|
|
@ -347,8 +402,6 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
{ Key: 'EventName', Value: 'pull_request' },
|
{ Key: 'EventName', Value: 'pull_request' },
|
||||||
{ Key: 'RunId', Value: '16412345678' },
|
{ Key: 'RunId', Value: '16412345678' },
|
||||||
{ Key: 'Job', Value: 'build' },
|
{ Key: 'Job', Value: 'build' },
|
||||||
{ Key: 'RefName', Value: 'feature-branch' },
|
|
||||||
{ Key: 'RefType', Value: 'branch' },
|
|
||||||
{ Key: 'TriggeringActor', Value: 'MY-USERNAME_bot_' },
|
{ Key: 'TriggeringActor', Value: 'MY-USERNAME_bot_' },
|
||||||
{ Key: 'Environment', Value: 'Production' },
|
{ Key: 'Environment', Value: 'Production' },
|
||||||
{ Key: 'Team', Value: 'DevOps' },
|
{ Key: 'Team', Value: 'DevOps' },
|
||||||
|
|
@ -397,7 +450,7 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
await run();
|
await run();
|
||||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("'custom-tags' is set but will be ignored"));
|
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("'custom-tags' is set but will be ignored"));
|
||||||
});
|
});
|
||||||
it('lets custom tags override overrideable default tag keys', {}, async () => {
|
it('rejects custom tags that conflict with droppable tag keys', {}, async () => {
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
mocks.getInput({
|
mocks.getInput({
|
||||||
...mocks.IAM_ASSUMEROLE_INPUTS,
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
|
@ -405,13 +458,10 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await run();
|
await run();
|
||||||
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
const eventNameTags = tags.filter((t) => t.Key === 'EventName');
|
"custom-tags: key 'EventName' conflicts with a protected session tag set by this action and cannot be overridden",
|
||||||
const baseRefTags = tags.filter((t) => t.Key === 'BaseRef');
|
);
|
||||||
expect(eventNameTags).toHaveLength(1);
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
expect(eventNameTags[0]?.Value).toBe('workflow_dispatch');
|
|
||||||
expect(baseRefTags).toHaveLength(1);
|
|
||||||
expect(baseRefTags[0]?.Value).toBe('release/2026');
|
|
||||||
});
|
});
|
||||||
it('rejects custom tags that conflict with the protected Branch tag', {}, async () => {
|
it('rejects custom tags that conflict with the protected Branch tag', {}, async () => {
|
||||||
// Regression guard: Branch was a default before v6.2 and must remain unoverridable.
|
// Regression guard: Branch was a default before v6.2 and must remain unoverridable.
|
||||||
|
|
@ -427,62 +477,10 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
);
|
);
|
||||||
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
it('drops lower-priority overrideable tags when custom-tags would exceed the session-tag limit', {}, async () => {
|
it('rejects custom-tags that would exceed the session-tag limit', {}, async () => {
|
||||||
// 7 protected (GitHub + 6 from PROTECTED_TAG_SOURCES) + 40 custom = 47 used → 3 overrideable slots.
|
// 13 existing tags (7 non-droppable + 6 droppable) + 38 custom = 51 > 50.
|
||||||
// The first 3 overrideable tags by priority are EventName, BaseRef, HeadRef (RefName, RunId, RefType,
|
|
||||||
// Job, TriggeringActor must be dropped).
|
|
||||||
const customTagsObj: Record<string, string> = {};
|
const customTagsObj: Record<string, string> = {};
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < 38; i++) {
|
||||||
customTagsObj[`Custom${i}`] = `value${i}`;
|
|
||||||
}
|
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
|
||||||
mocks.getInput({
|
|
||||||
...mocks.IAM_ASSUMEROLE_INPUTS,
|
|
||||||
'custom-tags': JSON.stringify(customTagsObj),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
await run();
|
|
||||||
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
|
||||||
const tagKeys = tags.map((t) => t.Key);
|
|
||||||
expect(tags).toHaveLength(50);
|
|
||||||
expect(tagKeys).toContain('Branch');
|
|
||||||
expect(tagKeys).toContain('EventName');
|
|
||||||
expect(tagKeys).toContain('BaseRef');
|
|
||||||
expect(tagKeys).toContain('HeadRef');
|
|
||||||
expect(tagKeys).not.toContain('RefName');
|
|
||||||
expect(tagKeys).not.toContain('RunId');
|
|
||||||
expect(tagKeys).not.toContain('RefType');
|
|
||||||
expect(tagKeys).not.toContain('Job');
|
|
||||||
expect(tagKeys).not.toContain('TriggeringActor');
|
|
||||||
});
|
|
||||||
it('overridden overrideable tags free a slot for a lower-priority overrideable tag', {}, async () => {
|
|
||||||
// Same 40-custom-tag scenario as above, but one of the customs overrides BaseRef.
|
|
||||||
// BaseRef no longer competes for the overrideable budget, so the next-priority overrideable (RefName) gets in.
|
|
||||||
const customTagsObj: Record<string, string> = { BaseRef: 'release/2026' };
|
|
||||||
for (let i = 0; i < 39; i++) {
|
|
||||||
customTagsObj[`Custom${i}`] = `value${i}`;
|
|
||||||
}
|
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
|
||||||
mocks.getInput({
|
|
||||||
...mocks.IAM_ASSUMEROLE_INPUTS,
|
|
||||||
'custom-tags': JSON.stringify(customTagsObj),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
await run();
|
|
||||||
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
|
||||||
const tagKeys = tags.map((t) => t.Key);
|
|
||||||
expect(tags).toHaveLength(50);
|
|
||||||
expect(tagKeys).toContain('Branch');
|
|
||||||
expect(tagKeys).toContain('EventName');
|
|
||||||
expect(tagKeys).toContain('BaseRef');
|
|
||||||
expect(tagKeys).toContain('HeadRef');
|
|
||||||
expect(tagKeys).toContain('RefName');
|
|
||||||
expect(tagKeys).not.toContain('RunId');
|
|
||||||
});
|
|
||||||
it('rejects custom-tags that would exceed the session-tag limit on their own', {}, async () => {
|
|
||||||
// 7 protected + 44 custom = 51, which is over 50 even with zero overrideable tags.
|
|
||||||
const customTagsObj: Record<string, string> = {};
|
|
||||||
for (let i = 0; i < 44; i++) {
|
|
||||||
customTagsObj[`Custom${i}`] = `value${i}`;
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
}
|
}
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
|
@ -495,12 +493,10 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('would exceed the AWS limit of 50'));
|
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('would exceed the AWS limit of 50'));
|
||||||
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
it('drops transitive-tag-keys entries that refer to evicted overrideable tags', {}, async () => {
|
it('allows custom-tags up to the session-tag limit', {}, async () => {
|
||||||
// Force eviction of all overrideable tags below EventName/BaseRef/HeadRef. The user transitive-tags
|
// 13 existing tags + 37 custom = 50, exactly at the limit.
|
||||||
// RunId (which gets evicted) and Repository (which is protected and stays). The TransitiveTagKeys
|
|
||||||
// payload must include only the keys that actually appear in Tags.
|
|
||||||
const customTagsObj: Record<string, string> = {};
|
const customTagsObj: Record<string, string> = {};
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < 37; i++) {
|
||||||
customTagsObj[`Custom${i}`] = `value${i}`;
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
}
|
}
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
|
@ -509,15 +505,10 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
'custom-tags': JSON.stringify(customTagsObj),
|
'custom-tags': JSON.stringify(customTagsObj),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
vi.mocked(core.getMultilineInput).mockImplementation((name: string) => {
|
|
||||||
if (name === 'transitive-tag-keys') return ['Repository', 'RunId'];
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
await run();
|
await run();
|
||||||
const callInput = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input;
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
const tagKeys = (callInput.Tags ?? []).map((t) => t.Key);
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
expect(tagKeys).not.toContain('RunId');
|
expect(tags).toHaveLength(50);
|
||||||
expect(callInput.TransitiveTagKeys).toEqual(['Repository']);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,14 @@ const inputs = {
|
||||||
'output-env-credentials': 'false',
|
'output-env-credentials': 'false',
|
||||||
'output-credentials': 'true',
|
'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 = {
|
const envs = {
|
||||||
|
|
@ -97,8 +105,6 @@ const envs = {
|
||||||
GITHUB_EVENT_NAME: 'pull_request',
|
GITHUB_EVENT_NAME: 'pull_request',
|
||||||
GITHUB_RUN_ID: '16412345678',
|
GITHUB_RUN_ID: '16412345678',
|
||||||
GITHUB_JOB: 'build',
|
GITHUB_JOB: 'build',
|
||||||
GITHUB_REF_NAME: 'feature-branch',
|
|
||||||
GITHUB_REF_TYPE: 'branch',
|
|
||||||
GITHUB_BASE_REF: 'main',
|
GITHUB_BASE_REF: 'main',
|
||||||
GITHUB_HEAD_REF: 'feature-branch',
|
GITHUB_HEAD_REF: 'feature-branch',
|
||||||
GITHUB_TRIGGERING_ACTOR: 'MY-USERNAME[bot]',
|
GITHUB_TRIGGERING_ACTOR: 'MY-USERNAME[bot]',
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,13 @@ import {
|
||||||
writeProfileFiles,
|
writeProfileFiles,
|
||||||
} from '../src/profileManager';
|
} from '../src/profileManager';
|
||||||
|
|
||||||
vi.mock('@actions/core');
|
|
||||||
vi.mock('node:fs');
|
vi.mock('node:fs');
|
||||||
|
vi.mock('@actions/core');
|
||||||
|
|
||||||
describe('Profile Manager', {}, () => {
|
describe('Profile Manager', {}, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.restoreAllMocks();
|
||||||
|
vi.clearAllMocks();
|
||||||
vol.reset();
|
vol.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -735,4 +736,69 @@ describe('Profile Manager', {}, () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue