mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-02 05:55:10 +09:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b26f2851b7 |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
".release-please-manifest.json": "4.0.2",
|
".release-please-manifest.json": "4.0.2",
|
||||||
"package.json": "6.0.0",
|
"package.json": "6.0.0",
|
||||||
".": "6.2.4"
|
".": "6.2.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
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.4](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.3...v6.2.4) (2026-08-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* account-ids handling, mask proxy as secret in logs ([#1943](https://github.com/aws-actions/configure-aws-credentials/issues/1943)) ([aa65264](https://github.com/aws-actions/configure-aws-credentials/commit/aa6526434b08748f8776b29964e3f1f5d90e7b63))
|
|
||||||
* skip backoff sleep after the final retryAndBackoff attempt ([#1937](https://github.com/aws-actions/configure-aws-credentials/issues/1937)) ([3852440](https://github.com/aws-actions/configure-aws-credentials/commit/3852440c21363386b7b790605685d08a7c1a4876))
|
|
||||||
|
|
||||||
## [6.2.3](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.2...v6.2.3) (2026-07-22)
|
## [6.2.3](https://github.com/aws-actions/configure-aws-credentials/compare/v6.2.2...v6.2.3) (2026-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Authenticate to AWS in GitHub Actions (and others)! Works especially well with
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
role-to-assume: <Role ARN you created in step 2>
|
role-to-assume: <Role ARN you created in step 2>
|
||||||
aws-region: <AWS Region you want to use>
|
aws-region: <AWS Region you want to use>
|
||||||
@@ -250,7 +250,7 @@ specify the profile name as an environment variable in the job step:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-role
|
||||||
@@ -268,14 +268,14 @@ step environment variables:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-first-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-first-role
|
||||||
aws-profile: firstRoleInChain
|
aws-profile: firstRoleInChain
|
||||||
|
|
||||||
- name: assume second role
|
- name: assume second role
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||||
@@ -311,7 +311,7 @@ this action will always consider the `HTTP_PROXY` environment variable.
|
|||||||
Manually configured proxy:
|
Manually configured proxy:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: my-github-actions-role
|
role-to-assume: my-github-actions-role
|
||||||
@@ -458,7 +458,7 @@ line.
|
|||||||
<summary>Inline session policy examples</summary>
|
<summary>Inline session policy examples</summary>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
|
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
|
||||||
```
|
```
|
||||||
@@ -466,7 +466,7 @@ with:
|
|||||||
Or we can have a nicely formatted JSON as well:
|
Or we can have a nicely formatted JSON as well:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
inline-session-policy: >-
|
inline-session-policy: >-
|
||||||
{
|
{
|
||||||
@@ -494,7 +494,7 @@ the role.
|
|||||||
<summary>Managed session policy examples</summary>
|
<summary>Managed session policy examples</summary>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||||
```
|
```
|
||||||
@@ -502,7 +502,7 @@ with:
|
|||||||
And we can pass multiple managed policies likes this:
|
And we can pass multiple managed policies likes this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
managed-session-policies: |
|
managed-session-policies: |
|
||||||
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
|
||||||
@@ -548,7 +548,7 @@ specify the audience through the `audience` input:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials for China region audience
|
- name: Configure AWS Credentials for China region audience
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
audience: sts.amazonaws.com.cn
|
audience: sts.amazonaws.com.cn
|
||||||
aws-region: cn-northwest-1
|
aws-region: cn-northwest-1
|
||||||
@@ -709,7 +709,7 @@ Provider. The audience would still be `sts.amazonaws.com` by default.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
@@ -725,13 +725,13 @@ environment variable and use it to assume the role
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
role-session-name: MySessionName
|
role-session-name: MySessionName
|
||||||
- name: Configure other AWS Credentials
|
- name: Configure other AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
role-to-assume: arn:aws:iam::987654321000:role/my-second-role
|
||||||
@@ -753,7 +753,7 @@ alternatively, the `TagSession` permission can be omitted if you are using the
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
@@ -774,7 +774,7 @@ like `role-to-assume: my-github-actions-role`.
|
|||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials 1
|
- name: Configure AWS Credentials 1
|
||||||
id: creds
|
id: creds
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
|
||||||
@@ -783,7 +783,7 @@ like `role-to-assume: my-github-actions-role`.
|
|||||||
run: |
|
run: |
|
||||||
aws sts get-caller-identity
|
aws sts get-caller-identity
|
||||||
- name: Configure AWS Credentials 2
|
- name: Configure AWS Credentials 2
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }}
|
aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }}
|
||||||
@@ -814,14 +814,14 @@ provided.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS Credentials for Dev
|
- name: Configure AWS Credentials for Dev
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
role-to-assume: arn:aws:iam::111111111111:role/dev-role
|
role-to-assume: arn:aws:iam::111111111111:role/dev-role
|
||||||
aws-profile: dev
|
aws-profile: dev
|
||||||
|
|
||||||
- name: Configure AWS Credentials for Prod
|
- name: Configure AWS Credentials for Prod
|
||||||
uses: aws-actions/configure-aws-credentials@v6.2.4
|
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||||
with:
|
with:
|
||||||
aws-region: us-west-2
|
aws-region: us-west-2
|
||||||
role-to-assume: arn:aws:iam::222222222222:role/prod-role
|
role-to-assume: arn:aws:iam::222222222222:role/prod-role
|
||||||
|
|||||||
+1
-1
@@ -222,7 +222,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/client-sts@3.1121.0
|
- @aws-sdk/client-sts@3.1116.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
|
|||||||
+1224
-562
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
## [federated-setup](./federated-setup/README.md)
|
||||||
|
|
||||||
|
The directory contains templates for setting up the `configure-aws-credentials`
|
||||||
|
federation between your GitHub Organization/repository and your AWS account.
|
||||||
|
|
||||||
|
## [cfn-deploy-example](./cfn-deploy-example/README.md)
|
||||||
|
|
||||||
|
Repository example uses aws-action `configure-aws-credentials` with OIDC
|
||||||
|
federation template
|
||||||
|
[github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml).
|
||||||
|
Example demonstrates a repository that deploys AWS CloudFormation template using
|
||||||
|
cfn-deploy GitHub Action.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
name: 'compliance'
|
||||||
|
## run ci testing on all push events
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
## Guard rule set
|
||||||
|
sast-guard:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: grolston/guard-action@main
|
||||||
|
with:
|
||||||
|
data_directory: './cloudformation/' ## change to your template directory
|
||||||
|
rule_set: 'FedRAMP-Moderate'
|
||||||
|
show_summary: 'all'
|
||||||
|
output_format: 'single-line-summary'
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: deploy
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
env:
|
||||||
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
|
AWS_DEFAULT_OUTPUT: json
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-cfn:
|
||||||
|
name: deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# These permissions are needed to interact with GitHub’s OIDC Token endpoint.
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
|
with:
|
||||||
|
aws-region: us-east-1
|
||||||
|
## the following creates an ARN based on the values entered into github secrets
|
||||||
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_DEPLOY_ROLE }}
|
||||||
|
role-session-name: myGitHubActions
|
||||||
|
- name: Deploy EC2 Bastion
|
||||||
|
uses: aws-actions/aws-cloudformation-github-deploy@v1.3.0
|
||||||
|
with:
|
||||||
|
name: myEC2bastion
|
||||||
|
## change to path to template in your github repo
|
||||||
|
template: cloudformation/ec2-bastion.yml
|
||||||
|
capabilities: CAPABILITY_IAM, CAPABILITY_NAMED_IAM
|
||||||
|
no-fail-on-empty-changeset: "1"
|
||||||
|
## parameter set in github secrets
|
||||||
|
parameter-overrides: "pVpc=${{ secrets.VPC_ID }},pSubnet=${{ secrets.SUBNET_ID }}"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# cfn-deploy example
|
||||||
|
|
||||||
|
Example uses aws-action `configure-aws-credentials` with OIDC federation. Prior
|
||||||
|
to using this example project, the user needs to deploy the
|
||||||
|
[github-actions-oidc-federation-and-role](../federated-setup/github-actions-oidc-federation-and-role.yml)
|
||||||
|
template in the AWS account they want to deploy the CloudFormation template
|
||||||
|
into. Specify the GitHub Organization name, repository name, and the specific
|
||||||
|
branch you want to deploy on.
|
||||||
|
|
||||||
|
Within the [github/workflows](./.github/workflows/) directory there is a
|
||||||
|
[compliance.yml](./.github/workflows/compliance.yml) and a
|
||||||
|
[deploy.yml](./.github/workflows/deploy.yml). The deploy.yml file leverages the
|
||||||
|
aws-action `configure-aws-credentials` and accesses GitHub Action Secrets for
|
||||||
|
some of the variables. The compliance.yml runs static application security
|
||||||
|
testing using cfn-guard.
|
||||||
|
|
||||||
|
To use the example you will need to set the following GitHub Action Secrets:
|
||||||
|
|
||||||
|
| Secret Key | Used With | Description |
|
||||||
|
| --------------- | -------------------------------- | ---------------------------------------- |
|
||||||
|
| AWS_ACCOUNT_ID | configure-aws-credentials | The AWS account ID |
|
||||||
|
| AWS_DEPLOY_ROLE | configure-aws-credentials | The name of the IAM role |
|
||||||
|
| VPC_ID | aws-cloudformation-github-deploy | VPC ID the EC2 Bastion is deployed to |
|
||||||
|
| SUBNET_ID | aws-cloudformation-github-deploy | Subnet ID the EC2 Bastion is deployed to |
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
AWSTemplateFormatVersion: "2010-09-09"
|
||||||
|
Description: EC2 bastion for latest AWS Linux 2 EC2 deployment
|
||||||
|
Metadata:
|
||||||
|
AWS::CloudFormation::Interface:
|
||||||
|
ParameterGroups:
|
||||||
|
- Label:
|
||||||
|
default: "EC2 Configuration"
|
||||||
|
Parameters:
|
||||||
|
- pTagNameValue
|
||||||
|
- pOperatingSystem
|
||||||
|
- pInstanceType
|
||||||
|
- pVolumeSize
|
||||||
|
- pEbsDeleteOnTermination
|
||||||
|
- Label:
|
||||||
|
default: "Network Configuration"
|
||||||
|
Parameters:
|
||||||
|
- pVpc
|
||||||
|
- pSubnet
|
||||||
|
ParameterLabels:
|
||||||
|
pOperatingSystem:
|
||||||
|
default: "Operating System"
|
||||||
|
pInstanceType:
|
||||||
|
default: "Instance Type"
|
||||||
|
pTagNameValue:
|
||||||
|
default: "EC2 Name"
|
||||||
|
pVolumeSize:
|
||||||
|
default: "Volume Size"
|
||||||
|
pEbsDeleteOnTermination:
|
||||||
|
default: "Delete EBS Volume on Termination"
|
||||||
|
pSubnet:
|
||||||
|
default: "Subnet"
|
||||||
|
pVpc:
|
||||||
|
default: "VPC"
|
||||||
|
Parameters:
|
||||||
|
pSubnet:
|
||||||
|
Description: The subnet to launch the instance in to. It must be part of the VPC chosen above.
|
||||||
|
Type: AWS::EC2::Subnet::Id
|
||||||
|
pVpc:
|
||||||
|
Description: The VPC to launch the EC2 instance in to.
|
||||||
|
Type: AWS::EC2::VPC::Id
|
||||||
|
pOperatingSystem:
|
||||||
|
Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
|
||||||
|
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs"
|
||||||
|
pInstanceType:
|
||||||
|
Description: Desired Instance Size
|
||||||
|
Type: String
|
||||||
|
Default: t3.small
|
||||||
|
AllowedValues:
|
||||||
|
- t3.small
|
||||||
|
- t3.medium
|
||||||
|
- t3.nano
|
||||||
|
pTagNameValue:
|
||||||
|
Description: "Required: Enter the tag name you'd like applied to the instance. Tag Name gives the name to the EC2 instance."
|
||||||
|
Type: String
|
||||||
|
MinLength: 1
|
||||||
|
Default: "myBastion"
|
||||||
|
pVolumeSize:
|
||||||
|
Description:
|
||||||
|
Enter the number of GBs you want your volume to be. The minimum value
|
||||||
|
is 8 GBs
|
||||||
|
Type: Number
|
||||||
|
Default: 50
|
||||||
|
MinValue: 8
|
||||||
|
pEbsDeleteOnTermination:
|
||||||
|
Description: "Specify if the EBS volume should be deleted if EC2 is deleted."
|
||||||
|
Type: String
|
||||||
|
Default: true
|
||||||
|
AllowedValues:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
Rules:
|
||||||
|
SubnetInVPC:
|
||||||
|
Assertions:
|
||||||
|
- Assert: !EachMemberIn
|
||||||
|
- !ValueOfAll
|
||||||
|
- AWS::EC2::Subnet::Id
|
||||||
|
- VpcId
|
||||||
|
- !RefAll "AWS::EC2::VPC::Id"
|
||||||
|
AssertDescription: All subnets must in the VPC
|
||||||
|
Resources:
|
||||||
|
rSecurityGroupDefault:
|
||||||
|
Type: AWS::EC2::SecurityGroup
|
||||||
|
Properties:
|
||||||
|
GroupDescription: !Sub "Default SG for SC Product ${pTagNameValue} "
|
||||||
|
VpcId: !Ref pVpc
|
||||||
|
SecurityGroupEgress:
|
||||||
|
- Description: Outbound unrestricted traffic
|
||||||
|
IpProtocol: "-1"
|
||||||
|
CidrIp: 0.0.0.0/0
|
||||||
|
Tags:
|
||||||
|
- Key: Name
|
||||||
|
Value: !Ref pTagNameValue
|
||||||
|
rLinuxEc2:
|
||||||
|
Type: AWS::EC2::Instance
|
||||||
|
Metadata:
|
||||||
|
guard:
|
||||||
|
SuppressedRules:
|
||||||
|
- 'EC2_INSTANCE_DETAILED_MONITORING_ENABLED'
|
||||||
|
Properties:
|
||||||
|
ImageId: !Ref pOperatingSystem
|
||||||
|
IamInstanceProfile: !Ref rec2InstanceProfile
|
||||||
|
Monitoring: false
|
||||||
|
InstanceType: !Ref pInstanceType
|
||||||
|
EbsOptimized: true
|
||||||
|
SourceDestCheck: true
|
||||||
|
SubnetId: !Ref pSubnet
|
||||||
|
SecurityGroupIds:
|
||||||
|
- !Ref rSecurityGroupDefault
|
||||||
|
BlockDeviceMappings:
|
||||||
|
- DeviceName: "/dev/xvda"
|
||||||
|
Ebs:
|
||||||
|
VolumeSize: !Ref pVolumeSize
|
||||||
|
DeleteOnTermination: !Ref pEbsDeleteOnTermination
|
||||||
|
Tags:
|
||||||
|
- Key: Name
|
||||||
|
Value: !Ref pTagNameValue
|
||||||
|
UserData:
|
||||||
|
Fn::Base64:
|
||||||
|
yum update -y
|
||||||
|
## Instance Profiles
|
||||||
|
## EC2 IAM Roles
|
||||||
|
rEc2Role:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
RoleName: !Sub "ec2-role-${AWS::StackName}"
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: [ec2.amazonaws.com]
|
||||||
|
Action: ['sts:AssumeRole']
|
||||||
|
Path: /
|
||||||
|
ManagedPolicyArns:
|
||||||
|
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
|
||||||
|
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy'
|
||||||
|
rec2InstanceProfile:
|
||||||
|
Type: AWS::IAM::InstanceProfile
|
||||||
|
Properties:
|
||||||
|
InstanceProfileName: !Sub "ec2-profile-${AWS::StackName}"
|
||||||
|
Path: /
|
||||||
|
Roles:
|
||||||
|
- !Ref rEc2Role
|
||||||
|
Outputs:
|
||||||
|
oLinuxEc2InstanceId:
|
||||||
|
Description: Resource ID of the newly created EC2 instance
|
||||||
|
Value: !Ref rLinuxEc2
|
||||||
|
oLinuxEc2PrivateIP:
|
||||||
|
Description: Private IP Address for EC2
|
||||||
|
Value: !GetAtt rLinuxEc2.PrivateIp
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# federated-setup
|
||||||
|
|
||||||
|
## [github-action-oidc-federation](./github-actions-oidc-federation.yml)
|
||||||
|
|
||||||
|
Setup of the OIDC federation between your GitHub Organization/repository and
|
||||||
|
your AWS account.
|
||||||
|
|
||||||
|
## [github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml)
|
||||||
|
|
||||||
|
Setup of the OIDC federation between your GitHub Organization/repository and
|
||||||
|
your AWS account along with a role that only executes on specific branch.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
AWSTemplateFormatVersion: "2010-09-09"
|
||||||
|
Description: Github Actions configuration - OIDC IAM IdP and associated role CI/CD
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
GitHubOrganization:
|
||||||
|
Type: String
|
||||||
|
Description: This is the root organization or personal account where repos are stored (Case Sensitive)
|
||||||
|
|
||||||
|
RepositoryName:
|
||||||
|
Type: String
|
||||||
|
Description: The repo(s) these roles will have access to. (Use * for all org or personal repos)
|
||||||
|
Default: "*"
|
||||||
|
|
||||||
|
BranchName:
|
||||||
|
Type: String
|
||||||
|
Description: Name of the git branch to to trust. (Use * for all branches)
|
||||||
|
Default: "*"
|
||||||
|
|
||||||
|
RoleName:
|
||||||
|
Type: String
|
||||||
|
Description: Name the Role
|
||||||
|
|
||||||
|
UseExistingProvider:
|
||||||
|
Type: String
|
||||||
|
Description: "Only one GitHub Provider can exists. Choose yes if one is already present in account"
|
||||||
|
Default: "no"
|
||||||
|
AllowedValues:
|
||||||
|
- "yes"
|
||||||
|
- "no"
|
||||||
|
|
||||||
|
Conditions:
|
||||||
|
|
||||||
|
CreateProvider: !Equals ["no", !Ref UseExistingProvider]
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
|
||||||
|
IdpGitHubOidc:
|
||||||
|
Type: AWS::IAM::OIDCProvider
|
||||||
|
Condition: CreateProvider
|
||||||
|
Properties:
|
||||||
|
Url: https://token.actions.githubusercontent.com
|
||||||
|
ClientIdList:
|
||||||
|
- sts.amazonaws.com
|
||||||
|
- !Sub https://github.com/${GitHubOrganization}/${RepositoryName}
|
||||||
|
ThumbprintList:
|
||||||
|
- 6938fd4d98bab03faadb97b34396831e3780aea1
|
||||||
|
Tags:
|
||||||
|
- Key: Name
|
||||||
|
Value: !Sub ${RoleName}-OIDC-Provider
|
||||||
|
|
||||||
|
RoleGithubActions:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
RoleName: !Ref RoleName
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Action: sts:AssumeRoleWithWebIdentity
|
||||||
|
Principal:
|
||||||
|
Federated: !If
|
||||||
|
- CreateProvider
|
||||||
|
- !Ref IdpGitHubOidc
|
||||||
|
- !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com
|
||||||
|
Condition:
|
||||||
|
StringLike:
|
||||||
|
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrganization}/${RepositoryName}:ref:refs/heads/${BranchName}
|
||||||
|
ManagedPolicyArns:
|
||||||
|
## edit the managed policy to give least privileges
|
||||||
|
- !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
IdpGitHubOidc:
|
||||||
|
Condition: CreateProvider
|
||||||
|
Description: "ARN of Github OIDC Provider"
|
||||||
|
Value: !GetAtt IdpGitHubOidc.Arn
|
||||||
|
|
||||||
|
RoleGithubActionsARN:
|
||||||
|
Description: "CICD Role for GitHub Actions"
|
||||||
|
Value: !GetAtt RoleGithubActions.Arn
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
AWSTemplateFormatVersion: "2010-09-09"
|
||||||
|
Description: Github Actions configuration - OIDC IAM IdP Federation
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
GitHubOrganization:
|
||||||
|
Type: String
|
||||||
|
Description: This is the root organization or personal account where repos are stored (Case Sensitive)
|
||||||
|
Default: ""
|
||||||
|
|
||||||
|
RepositoryName:
|
||||||
|
Type: String
|
||||||
|
Description: The repo(s) these roles will have access to. (Use * for all org or personal repos)
|
||||||
|
Default: "*"
|
||||||
|
|
||||||
|
RoleName:
|
||||||
|
Type: String
|
||||||
|
Description: Name the Role
|
||||||
|
Default: ""
|
||||||
|
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
|
||||||
|
IdpGitHubOidc:
|
||||||
|
Type: AWS::IAM::OIDCProvider
|
||||||
|
Properties:
|
||||||
|
Url: https://token.actions.githubusercontent.com
|
||||||
|
ClientIdList:
|
||||||
|
- sts.amazonaws.com
|
||||||
|
- !Sub https://github.com/${GitHubOrganization}/${RepositoryName}
|
||||||
|
ThumbprintList:
|
||||||
|
- 6938fd4d98bab03faadb97b34396831e3780aea1
|
||||||
|
Tags:
|
||||||
|
- Key: Name
|
||||||
|
Value: !Sub ${RoleName}-OIDC-Provider
|
||||||
|
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
IdpGitHubOidc:
|
||||||
|
Description: "ARN of Github OIDC Provider"
|
||||||
|
Value: !GetAtt IdpGitHubOidc.Arn
|
||||||
Generated
+607
-480
File diff suppressed because it is too large
Load Diff
+5
-6
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "configure-aws-credentials",
|
"name": "configure-aws-credentials",
|
||||||
"description": "A GitHub Action to configure AWS credentials",
|
"description": "A GitHub Action to configure AWS credentials",
|
||||||
"version": "6.2.4",
|
"version": "6.2.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
"lint": "biome check --error-on-warnings ./src ./test && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'",
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/credential-provider-env": "^3.972.70",
|
"@aws-sdk/credential-provider-env": "^3.972.70",
|
||||||
"@biomejs/biome": "2.5.11",
|
"@biomejs/biome": "2.5.10",
|
||||||
"@smithy/property-provider": "^4.5.2",
|
"@smithy/property-provider": "^4.5.2",
|
||||||
"@types/node": "^26.4.0",
|
"@types/node": "^26.2.0",
|
||||||
"@vitest/coverage-v8": "4.1.10",
|
"@vitest/coverage-v8": "4.1.10",
|
||||||
"aws-sdk-client-mock": "^4.1.0",
|
"aws-sdk-client-mock": "^4.1.0",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
"generate-license-file": "^4.2.4",
|
"generate-license-file": "^4.2.1",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"markdownlint-cli": "^0.49.1",
|
"markdownlint-cli": "^0.49.1",
|
||||||
"memfs": "^4.68.1",
|
"memfs": "^4.68.1",
|
||||||
@@ -34,8 +34,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^3.0.1",
|
"@actions/core": "^3.0.1",
|
||||||
"@aws-sdk/client-sts": "^3.1121.0",
|
"@aws-sdk/client-sts": "^3.1116.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.972.63",
|
|
||||||
"@smithy/node-http-handler": "^4.11.3",
|
"@smithy/node-http-handler": "^4.11.3",
|
||||||
"proxy-agent": "^8.0.2"
|
"proxy-agent": "^8.0.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { info } from '@actions/core';
|
import { info } from '@actions/core';
|
||||||
import { STSClient } from '@aws-sdk/client-sts';
|
import { STSClient } from '@aws-sdk/client-sts';
|
||||||
import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
|
||||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||||
import { ProxyAgent } from 'proxy-agent';
|
import { ProxyAgent } from 'proxy-agent';
|
||||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity, maskProxyCredentials } from './helpers';
|
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
||||||
import { ProxyResolver } from './ProxyResolver';
|
import { ProxyResolver } from './ProxyResolver';
|
||||||
|
|
||||||
if (!process.env.AWS_EXECUTION_ENV) {
|
if (!process.env.AWS_EXECUTION_ENV) {
|
||||||
@@ -32,7 +31,6 @@ export class CredentialsClient {
|
|||||||
}
|
}
|
||||||
if (props.proxyServer) {
|
if (props.proxyServer) {
|
||||||
info('Configuring proxy handler for STS client');
|
info('Configuring proxy handler for STS client');
|
||||||
maskProxyCredentials(props.proxyServer);
|
|
||||||
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
||||||
httpProxy: props.proxyServer,
|
httpProxy: props.proxyServer,
|
||||||
httpsProxy: props.proxyServer,
|
httpsProxy: props.proxyServer,
|
||||||
@@ -107,15 +105,9 @@ export class CredentialsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadCredentials() {
|
private async loadCredentials() {
|
||||||
// Previously we constructed a new client, but that picks up the default provider chain including the endpoint.
|
const config = {} as { requestHandler?: NodeHttpHandler };
|
||||||
// Explicitly calling the default provider chain allows us to pass in the endpoint and region as well as the
|
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
|
||||||
// proxy config.
|
const client = new STSClient(config);
|
||||||
return defaultProvider({
|
return client.config.credentials();
|
||||||
clientConfig: {
|
|
||||||
...(this.region !== undefined && { region: this.region }),
|
|
||||||
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
|
|
||||||
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
|
|
||||||
},
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-30
@@ -5,6 +5,7 @@ import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
|||||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||||
import type { UserAgent } from '@smithy/types';
|
import type { UserAgent } from '@smithy/types';
|
||||||
|
import type { CredentialsClient } from './CredentialsClient';
|
||||||
|
|
||||||
const MAX_TAG_VALUE_LENGTH = 256;
|
const MAX_TAG_VALUE_LENGTH = 256;
|
||||||
const SANITIZATION_CHARACTER = '_';
|
const SANITIZATION_CHARACTER = '_';
|
||||||
@@ -166,13 +167,15 @@ export function exportAccountId(identity: { Account: string; Arn: string }, mask
|
|||||||
// Validates that the account of the already-resolved caller identity is in the allow-list provided via the
|
// Validates that the account of the already-resolved caller identity is in the allow-list provided via the
|
||||||
// `allowed-account-ids` input.
|
// `allowed-account-ids` input.
|
||||||
export function validateAccountId(expectedAccountIds: string[] | undefined, account: string | undefined): void {
|
export function validateAccountId(expectedAccountIds: string[] | undefined, account: string | undefined): void {
|
||||||
const allowedAccountIds = expectedAccountIds?.filter((id) => id !== '') ?? [];
|
if (!expectedAccountIds || expectedAccountIds.length === 0 || expectedAccountIds[0] === '') {
|
||||||
if (allowedAccountIds.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!account || !allowedAccountIds.includes(account)) {
|
if (!account || !expectedAccountIds.includes(account)) {
|
||||||
// Account IDs are deliberately omitted: this error reaches the job log before any mask exists.
|
throw new Error(
|
||||||
throw new Error('The account ID of the provided credentials does not match any of the allowed account IDs');
|
`The account ID of the provided credentials (${
|
||||||
|
account ?? 'unknown'
|
||||||
|
}) does not match any of the expected account IDs: ${expectedAccountIds.join(', ')}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,29 +193,6 @@ export function toCredentialIdentity(creds?: Partial<Credentials>): AwsCredentia
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registers any userinfo embedded in a proxy URL as secrets so it is masked in job logs.
|
|
||||||
// First the literal proxy string, then any username/password components if parseable.
|
|
||||||
// If the username/password is percent-encoded, the decoded form is also masked.
|
|
||||||
export function maskProxyCredentials(proxyServer: string): void {
|
|
||||||
core.setSecret(proxyServer);
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL(proxyServer);
|
|
||||||
} catch (_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const part of [url.username, url.password]) {
|
|
||||||
if (!part) continue;
|
|
||||||
core.setSecret(part);
|
|
||||||
try {
|
|
||||||
const decoded = decodeURIComponent(part);
|
|
||||||
if (decoded !== part) core.setSecret(decoded);
|
|
||||||
} catch (_) {
|
|
||||||
// malformed percent-encoding; the raw form is already masked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags have a more restrictive set of acceptable characters than GitHub environment variables can.
|
// Tags have a more restrictive set of acceptable characters than GitHub environment variables can.
|
||||||
// This replaces anything not conforming to the tag restrictions by inverting the regular expression.
|
// This replaces anything not conforming to the tag restrictions by inverting the regular expression.
|
||||||
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
|
// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
|
||||||
@@ -280,13 +260,13 @@ export async function retryAndBackoff<T>(
|
|||||||
`Retrying after ${Math.floor(delay)}ms.`,
|
`Retrying after ${Math.floor(delay)}ms.`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await sleep(delay);
|
||||||
|
|
||||||
if (nextRetry >= maxRetries) {
|
if (nextRetry >= maxRetries) {
|
||||||
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
core.info(`Retry${opName}: reached max retries (${maxRetries}); giving up.`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(delay);
|
|
||||||
|
|
||||||
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
return await retryAndBackoff(fn, isRetryable, maxRetries, nextRetry, base, label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,6 +281,19 @@ export function isDefined<T>(i: T | undefined | null): i is T {
|
|||||||
}
|
}
|
||||||
/* c8 ignore stop */
|
/* c8 ignore stop */
|
||||||
|
|
||||||
|
export async function areCredentialsValid(credentialsClient: CredentialsClient) {
|
||||||
|
const client = credentialsClient.stsClient;
|
||||||
|
try {
|
||||||
|
const identity = await client.send(new GetCallerIdentityCommand({}));
|
||||||
|
if (identity.Account) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like core.getBooleanInput, but respects the required option.
|
* Like core.getBooleanInput, but respects the required option.
|
||||||
*
|
*
|
||||||
|
|||||||
+5
-13
@@ -3,12 +3,12 @@ import type { AssumeRoleCommandOutput } from '@aws-sdk/client-sts';
|
|||||||
import { assumeRole } from './assumeRole';
|
import { assumeRole } from './assumeRole';
|
||||||
import { CredentialsClient } from './CredentialsClient';
|
import { CredentialsClient } from './CredentialsClient';
|
||||||
import {
|
import {
|
||||||
|
areCredentialsValid,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
exportAccountId,
|
exportAccountId,
|
||||||
exportCredentials,
|
exportCredentials,
|
||||||
exportRegion,
|
exportRegion,
|
||||||
getBooleanInput,
|
getBooleanInput,
|
||||||
getCallerIdentity,
|
|
||||||
retryAndBackoff,
|
retryAndBackoff,
|
||||||
toCredentialIdentity,
|
toCredentialIdentity,
|
||||||
translateEnvVariables,
|
translateEnvVariables,
|
||||||
@@ -53,8 +53,8 @@ export async function run() {
|
|||||||
});
|
});
|
||||||
const roleChaining = getBooleanInput('role-chaining', { required: false });
|
const roleChaining = getBooleanInput('role-chaining', { required: false });
|
||||||
const outputCredentials = getBooleanInput('output-credentials', { required: false });
|
const outputCredentials = getBooleanInput('output-credentials', { required: false });
|
||||||
// Default to always outputting environment credentials unless profile is specified. If profile is specified,
|
// Default to always outputting environment credentials unless profile is specified. If profile is specified, default
|
||||||
// default to no environment credentials (but still output them if the user specifically requests it).
|
// to no environment credentials (but still output them if the user specifically requests it).
|
||||||
const outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: !awsProfile });
|
const outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: !awsProfile });
|
||||||
const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false });
|
const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false });
|
||||||
let disableRetry = getBooleanInput('disable-retry', { required: false });
|
let disableRetry = getBooleanInput('disable-retry', { required: false });
|
||||||
@@ -165,16 +165,8 @@ export async function run() {
|
|||||||
|
|
||||||
//if the user wants to attempt to use existing credentials, check if we have some already
|
//if the user wants to attempt to use existing credentials, check if we have some already
|
||||||
if (useExistingCredentials) {
|
if (useExistingCredentials) {
|
||||||
const identity = await (async () => {
|
const validCredentials = await areCredentialsValid(credentialsClient);
|
||||||
try {
|
if (validCredentials) {
|
||||||
return await getCallerIdentity(credentialsClient.stsClient);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
if (identity) {
|
|
||||||
// The allowed-account-ids guardrail applies to reused credentials too.
|
|
||||||
validateAccountId(expectedAccountIds, identity.Account);
|
|
||||||
core.notice('Pre-existing credentials are valid. No need to generate new ones.');
|
core.notice('Pre-existing credentials are valid. No need to generate new ones.');
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -53,15 +53,8 @@ export function parseIni(iniData: string): Record<string, Record<string, string>
|
|||||||
export function stringifyIni(data: Record<string, Record<string, string>>): string {
|
export function stringifyIni(data: Record<string, Record<string, string>>): string {
|
||||||
const sections: string[] = [];
|
const sections: string[] = [];
|
||||||
for (const [sectionName, sectionData] of Object.entries(data)) {
|
for (const [sectionName, sectionData] of Object.entries(data)) {
|
||||||
if (/[\r\n]/.test(sectionName)) {
|
|
||||||
throw new Error('INI section names must not contain newline characters');
|
|
||||||
}
|
|
||||||
const lines: string[] = [`[${sectionName}]`];
|
const lines: string[] = [`[${sectionName}]`];
|
||||||
for (const [key, value] of Object.entries(sectionData)) {
|
for (const [key, value] of Object.entries(sectionData)) {
|
||||||
// A newline in a key or value would inject arbitrary INI lines (e.g. credential_process).
|
|
||||||
if (/[\r\n]/.test(key) || /[\r\n]/.test(value)) {
|
|
||||||
throw new Error('INI keys and values must not contain newline characters');
|
|
||||||
}
|
|
||||||
lines.push(`${key} = ${value}`);
|
lines.push(`${key} = ${value}`);
|
||||||
}
|
}
|
||||||
sections.push(lines.join('\n'));
|
sections.push(lines.join('\n'));
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
|
||||||
|
|
||||||
vi.mock('@aws-sdk/credential-provider-node', () => ({
|
|
||||||
defaultProvider: vi.fn(() => async () => ({ accessKeyId: 'AKIA', secretAccessKey: 'secret' })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
|
||||||
import { CredentialsClient } from '../src/CredentialsClient';
|
|
||||||
|
|
||||||
describe('CredentialsClient', {}, () => {
|
|
||||||
it('pins ambient credential resolution to the configured region and STS endpoint', {}, async () => {
|
|
||||||
const client = new CredentialsClient({
|
|
||||||
region: 'eu-west-1',
|
|
||||||
stsEndpoint: 'https://sts.example.com',
|
|
||||||
roleChaining: false,
|
|
||||||
});
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: any required to call private method
|
|
||||||
await (client as any).loadCredentials();
|
|
||||||
expect(defaultProvider).toHaveBeenCalledWith({
|
|
||||||
clientConfig: expect.objectContaining({ region: 'eu-west-1', endpoint: 'https://sts.example.com' }),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('omits unset client config values from ambient credential resolution', {}, async () => {
|
|
||||||
const client = new CredentialsClient({ region: 'eu-west-1', roleChaining: false });
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: any required to call private method
|
|
||||||
await (client as any).loadCredentials();
|
|
||||||
expect(defaultProvider).toHaveBeenLastCalledWith({ clientConfig: { region: 'eu-west-1' } });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -126,45 +126,6 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
|||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('maskProxyCredentials', {}, () => {
|
|
||||||
it('masks username and password embedded in a proxy URL', {}, () => {
|
|
||||||
helpers.maskProxyCredentials('http://user:secretpass@proxy.example.com:8080');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('user');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('masks both encoded and decoded forms of the credentials', {}, () => {
|
|
||||||
helpers.maskProxyCredentials('http://user:p%40ss@proxy.example.com:8080');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('p%40ss');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('p@ss');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('masks the whole value even without embedded credentials or when unparseable', {}, () => {
|
|
||||||
helpers.maskProxyCredentials('http://proxy.example.com:8080');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('http://proxy.example.com:8080');
|
|
||||||
helpers.maskProxyCredentials('not a url');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('not a url');
|
|
||||||
// no username/password parts, so exactly one mask per call
|
|
||||||
expect(core.setSecret).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('validateAccountId', {}, () => {
|
|
||||||
it('enforces the allow-list even when the first element is empty', {}, () => {
|
|
||||||
expect(() => helpers.validateAccountId(['', '999999999999'], '111111111111')).toThrow(/does not match/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('passes an allowed account despite empty entries in the list', {}, () => {
|
|
||||||
expect(() => helpers.validateAccountId(['', '111111111111'], '111111111111')).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('skips validation only when no non-empty entries exist', {}, () => {
|
|
||||||
expect(() => helpers.validateAccountId(undefined, '111111111111')).not.toThrow();
|
|
||||||
expect(() => helpers.validateAccountId([], '111111111111')).not.toThrow();
|
|
||||||
expect(() => helpers.validateAccountId([''], '111111111111')).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('filesystem helpers', {}, () => {
|
describe('filesystem helpers', {}, () => {
|
||||||
describe('isSymlink', {}, () => {
|
describe('isSymlink', {}, () => {
|
||||||
it('returns true for a symlink', {}, () => {
|
it('returns true for a symlink', {}, () => {
|
||||||
|
|||||||
+5
-47
@@ -841,7 +841,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -861,7 +861,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999, 888888888888',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -917,7 +917,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -936,7 +936,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -956,7 +956,7 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
'The account ID of the provided credentials does not match any of the allowed account IDs',
|
'The account ID of the provided credentials (111111111111) does not match any of the expected account IDs: 999999999999',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1015,33 +1015,6 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails on the use-existing-credentials path when the account is not allowed', async () => {
|
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
|
||||||
mocks.getInput({
|
|
||||||
...mocks.USE_EXISTING_CREDENTIALS_INPUTS,
|
|
||||||
'allowed-account-ids': '999999999999',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
|
||||||
|
|
||||||
await run();
|
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('does not match'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reuses existing credentials when their account is allowed', async () => {
|
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
|
||||||
mocks.getInput({
|
|
||||||
...mocks.USE_EXISTING_CREDENTIALS_INPUTS,
|
|
||||||
'allowed-account-ids': '111111111111',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
|
||||||
|
|
||||||
await run();
|
|
||||||
expect(core.notice).toHaveBeenCalledWith('Pre-existing credentials are valid. No need to generate new ones.');
|
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Global Timeout Configuration', {}, () => {
|
describe('Global Timeout Configuration', {}, () => {
|
||||||
@@ -1267,21 +1240,6 @@ describe('Configure AWS Credentials', {}, () => {
|
|||||||
|
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
expect(core.setFailed).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('masks credentials embedded in the proxy URL', async () => {
|
|
||||||
vi.mocked(core.getInput).mockImplementation(
|
|
||||||
mocks.getInput({
|
|
||||||
...mocks.GH_OIDC_INPUTS,
|
|
||||||
'http-proxy': 'http://user:secretpass@proxy.example.com:8080',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('user');
|
|
||||||
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AWS Profile Support', {}, () => {
|
describe('AWS Profile Support', {}, () => {
|
||||||
|
|||||||
@@ -114,22 +114,6 @@ describe('Profile Manager', {}, () => {
|
|||||||
const result = stringifyIni({ dev: {} });
|
const result = stringifyIni({ dev: {} });
|
||||||
expect(result).toBe('[dev]\n');
|
expect(result).toBe('[dev]\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects values containing newlines', {}, () => {
|
|
||||||
expect(() =>
|
|
||||||
stringifyIni({ dev: { aws_session_token: 'token\n[injected]\ncredential_process = evil' } }),
|
|
||||||
).toThrow('must not contain newline characters');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects keys containing newlines', {}, () => {
|
|
||||||
expect(() => stringifyIni({ dev: { 'key\ninjected': 'val' } })).toThrow('must not contain newline characters');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects section names containing newlines', {}, () => {
|
|
||||||
expect(() => stringifyIni({ 'dev\r\n[injected]': { key: 'val' } })).toThrow(
|
|
||||||
'must not contain newline characters',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateProfileName', {}, () => {
|
describe('validateProfileName', {}, () => {
|
||||||
@@ -439,24 +423,6 @@ describe('Profile Manager', {}, () => {
|
|||||||
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('refuses to write credentials containing newlines instead of injecting profiles', {}, () => {
|
|
||||||
expect(() =>
|
|
||||||
writeProfileFiles(
|
|
||||||
'dev',
|
|
||||||
{
|
|
||||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
|
||||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
||||||
SessionToken: 'token\n[injected]\ncredential_process = evil-command',
|
|
||||||
},
|
|
||||||
'us-east-1',
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toThrow('must not contain newline characters');
|
|
||||||
|
|
||||||
const credsPath = getProfileFilePaths().credentials;
|
|
||||||
expect(fs.existsSync(credsPath)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses correct section naming for default profile', {}, () => {
|
it('uses correct section naming for default profile', {}, () => {
|
||||||
writeProfileFiles(
|
writeProfileFiles(
|
||||||
'default',
|
'default',
|
||||||
|
|||||||
Reference in New Issue
Block a user