Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow-list option for aws account id #605

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 4 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ inputs:
role-skip-session-tagging:
description: 'Skip session tagging during role assumption'
required: false
http-proxy:
description: 'Proxy to use for the AWS SDK agent'
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:
Expand Down
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