Skip to content

Commit

Permalink
feat: allow-list option for aws account id
Browse files Browse the repository at this point in the history
Users may set the `allowed-account-ids` input variable to define a
comma-separated list of accounts that may be authenticated against.

Resolves aws-actions#432
  • Loading branch information
jacksonwelsh committed Dec 18, 2022
1 parent 90d1b38 commit b1d1374
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 16 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/I
* [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows.
* [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows.

To prevent accidental deploys to the incorrect environment, you may define a comma-separated list of allowed account IDs to configure credentials for:

```yaml
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
aws-region: us-east-2
allowed-account-ids: 123456789100
```

## Assuming a Role
We recommend using [GitHub's OIDC provider](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) to get short-lived credentials needed for your actions.
Specifying `role-to-assume` **without** providing an `aws-access-key-id` or a `web-identity-token-file` will signal to the action that you wish to use the OIDC provider.
Expand Down
35 changes: 20 additions & 15 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: '"Configure AWS Credentials" Action For GitHub Actions'
description: 'Configure AWS credential and region environment variables for use with the AWS CLI and AWS SDKs'
description: "Configure AWS credential and region environment variables for use with the AWS CLI and AWS SDKs"
branding:
icon: 'cloud'
color: 'orange'
icon: "cloud"
color: "orange"
inputs:
audience:
default: 'sts.amazonaws.com'
description: 'The audience to use for the OIDC provider'
default: "sts.amazonaws.com"
description: "The audience to use for the OIDC provider"
required: false
aws-access-key-id:
description: >-
Expand All @@ -21,10 +21,10 @@ inputs:
for example on an EC2 instance.
required: false
aws-session-token:
description: 'AWS Session Token'
description: "AWS Session Token"
required: false
aws-region:
description: 'AWS Region, e.g. us-east-2'
description: "AWS Region, e.g. us-east-2"
required: true
mask-aws-account-id:
description: >-
Expand All @@ -47,21 +47,26 @@ inputs:
description: "Role duration in seconds (default: 6 hours, 1 hour for OIDC/specified aws-session-token)"
required: false
role-session-name:
description: 'Role session name (default: GitHubActions)'
description: "Role session name (default: GitHubActions)"
required: false
role-external-id:
description: 'The external ID of the role to assume'
description: "The external ID of the role to assume"
required: false
role-skip-session-tagging:
description: 'Skip session tagging during role assumption'
description: "Skip session tagging during role assumption"
required: false
http-proxy:
description: 'Proxy to use for the AWS SDK agent'
description: "Proxy to use for the AWS SDK agent"
required: false
allowed-account-ids:
description: >-
Comma-separated list of allowed AWS account IDs to prevent accidental
deploys to the wrong environment.
required: false
outputs:
aws-account-id:
description: 'The AWS account ID for the provided credentials'
description: "The AWS account ID for the provided credentials"
runs:
using: 'node12'
main: 'dist/index.js'
post: 'dist/cleanup/index.js'
using: "node12"
main: "dist/index.js"
post: "dist/cleanup/index.js"
25 changes: 24 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_';
const ROLE_SESSION_NAME = 'GitHubActions';
const REGION_REGEX = /^[a-z0-9-]+$/g;
const ACCOUNT_ID_LIST_REGEX = /^\d{12}(,\s?\d{12})*$/

async function assumeRole(params) {
// Assume a role to get short-lived credentials using longer-lived credentials.
Expand Down Expand Up @@ -300,13 +301,22 @@ async function run() {
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
const proxyServer = core.getInput('http-proxy', { required: false });
const allowedAccountIds = core.getInput('allowed-account-ids', { required: false });

if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
}

exportRegion(region);

if (allowedAccountIds && !allowedAccountIds.match(ACCOUNT_ID_LIST_REGEX)) {
let errorMessage = "Allowed account ID list is not valid, must be comma-separated list of 12-digit IDs";
if (maskAccountId.toLowerCase() == 'false') {
errorMessage += `: ${allowedAccountIds}`;
}
throw new Error(errorMessage);
}

// This wraps the logic for deciding if we should rely on the GH OIDC provider since we may need to reference
// the decision in a few differennt places. Consolidating it here makes the logic clearer elsewhere.
const useGitHubOIDCProvider = () => {
Expand Down Expand Up @@ -375,7 +385,20 @@ async function run() {
if (!process.env.GITHUB_ACTIONS || accessKeyId) {
await validateCredentials(roleCredentials.accessKeyId);
}
await exportAccountId(maskAccountId, region);
sourceAccountId = await exportAccountId(maskAccountId, region);
}

// Check if configured account ID is in the provided allow-list
if (allowedAccountIds) {
// Convert string to a list and trim whitespace.
const accountIdList = allowedAccountIds.split(",").map((id) => id.trim());
if (!accountIdList.includes(sourceAccountId)) {
let errorMessage = "Account ID of the provided credentials is not in 'allowed-account-ids'";
if (maskAccountId.toLowerCase() == 'false') {
errorMessage += `: ${sourceAccountId}`;
}
throw new Error(errorMessage);
}
}
}
catch (error) {
Expand Down
31 changes: 31 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,37 @@ describe('Configure AWS Credentials', () => {

await run();
});

test('denies accounts not defined in allow-account-ids', async () => {
process.env.SHOW_STACK_TRACE = 'false';

core.getInput = jest
.fn()
.mockImplementation(mockGetInput({ ...DEFAULT_INPUTS, 'allowed-account-ids': '000000000000' }));

await run();
expect(core.setFailed).toHaveBeenCalledWith(`Account ID of the provided credentials is not in 'allowed-account-ids'`);
});

test('allows accounts defined in allow-account-ids', async () => {
core.getInput = jest
.fn()
.mockImplementation(mockGetInput({ ...DEFAULT_INPUTS, 'allowed-account-ids': `${FAKE_ACCOUNT_ID}, 000000000000` }));

await run();
});

test('throws if account id list is invalid', async () => {
process.env.SHOW_STACK_TRACE = 'false';

// account id is too short
core.getInput = jest
.fn()
.mockImplementation(mockGetInput({ ...DEFAULT_INPUTS, 'allowed-account-ids': '0' }));

await run();
expect(core.setFailed).toHaveBeenCalledWith('Allowed account ID list is not valid, must be comma-separated list of 12-digit IDs');
})

describe('proxy settings', () => {

Expand Down

0 comments on commit b1d1374

Please sign in to comment.