Skip to content

Commit d00f6c6

Browse files
Allow inline session policies for assuming role (#739)
* Allow to pass inline session policy as a parameter Update the action file Regenerate the dist/ content Add test * Fix typos * Fix stylistic error * Move the inline policy logic to allow assumeRole to use it as well; Update and add tests * Add an option for managed policies * Regenerate the dist/ files * Use multiline input for managed policies * Update readme * Update readme --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent ae73407 commit d00f6c6

File tree

5 files changed

+239
-10
lines changed

5 files changed

+239
-10
lines changed

README.md

+43
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,49 @@ within the Action. You can skip this session tagging by providing
320320
role-skip-session-tagging: true
321321
```
322322

323+
### Inline session policy
324+
An IAM policy in stringified JSON format that you want to use as an inline session policy.
325+
Depending on preferences, the JSON could be written on a single line like this:
326+
```yaml
327+
uses: aws-actions/configure-aws-credentials@v2
328+
with:
329+
inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}'
330+
```
331+
Or we can have a nicely formatted JSON as well:
332+
```yaml
333+
uses: aws-actions/configure-aws-credentials@v2
334+
with:
335+
inline-session-policy: >-
336+
{
337+
"Version": "2012-10-17",
338+
"Statement": [
339+
{
340+
"Sid":"Stmt1",
341+
"Effect":"Allow",
342+
"Action":"s3:List*",
343+
"Resource":"*"
344+
}
345+
]
346+
}
347+
```
348+
349+
### Managed session policies
350+
The Amazon Resource Names (ARNs) of the IAM managed policies that you want to use as managed session policies.
351+
The policies must exist in the same account as the role. You can pass a single managed policy like this:
352+
```yaml
353+
uses: aws-actions/configure-aws-credentials@v2
354+
with:
355+
managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
356+
```
357+
And we can pass multiple managed policies likes this:
358+
```yaml
359+
uses: aws-actions/configure-aws-credentials@v2
360+
with:
361+
managed-session-policies: |
362+
arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
363+
arn:aws:iam::aws:policy/AmazonS3OutpostsReadOnlyAccess
364+
```
365+
323366
## Self-Hosted Runners
324367

325368
If you run your GitHub Actions in a

action.yml

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ inputs:
6161
role-chaining:
6262
description: 'Use existing credentials from the environment to assume a new role'
6363
required: false
64+
inline-session-policy:
65+
description: 'Inline session policy'
66+
required: false
67+
managed-session-policies:
68+
description: 'List of managed session policies'
69+
required: false
6470
outputs:
6571
aws-account-id:
6672
description: 'The AWS account ID for the provided credentials'

dist/index.js

+23-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ async function assumeRole(params) {
2929
region,
3030
roleSkipSessionTagging,
3131
webIdentityTokenFile,
32-
webIdentityToken
32+
webIdentityToken,
33+
inlineSessionPolicy,
34+
managedSessionPolicies
3335
} = params;
3436
assert(
3537
[roleToAssume, roleDurationSeconds, roleSessionName, region].every(isDefined),
@@ -86,6 +88,18 @@ async function assumeRole(params) {
8688
assumeRoleRequest.ExternalId = roleExternalId;
8789
}
8890

91+
if (isDefined(inlineSessionPolicy)) {
92+
assumeRoleRequest.Policy = inlineSessionPolicy;
93+
}
94+
95+
if (managedSessionPolicies && managedSessionPolicies.length) {
96+
const policyArns = []
97+
for (const managedSessionPolicy of managedSessionPolicies) {
98+
policyArns.push({arn: managedSessionPolicy})
99+
}
100+
assumeRoleRequest.PolicyArns = policyArns;
101+
}
102+
89103
let assumeFunction = sts.assumeRole.bind(sts);
90104

91105
// These are customizations needed for the GH OIDC Provider
@@ -305,6 +319,8 @@ async function run() {
305319
const roleSkipSessionTagging = roleSkipSessionTaggingInput.toLowerCase() === 'true';
306320
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
307321
const proxyServer = core.getInput('http-proxy', { required: false });
322+
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
323+
const managedSessionPolicies = core.getMultilineInput('managed-session-policies', { required: false })
308324

309325
if (!region.match(REGION_REGEX)) {
310326
throw new Error(`Region is not valid: ${region}`);
@@ -313,12 +329,12 @@ async function run() {
313329
exportRegion(region);
314330

315331
// This wraps the logic for deciding if we should rely on the GH OIDC provider since we may need to reference
316-
// the decision in a few differennt places. Consolidating it here makes the logic clearer elsewhere.
332+
// the decision in a few different places. Consolidating it here makes the logic clearer elsewhere.
317333
const useGitHubOIDCProvider = () => {
318334
// The assumption here is that self-hosted runners won't be populating the `ACTIONS_ID_TOKEN_REQUEST_TOKEN`
319-
// environment variable and they won't be providing a web idenity token file or access key either.
335+
// environment variable, and they won't be providing a web identity token file or access key either.
320336
// V2 of the action might relax this a bit and create an explicit precedence for these so that customers
321-
// can provide as much info as they want and we will follow the established credential loading precedence.
337+
// can provide as much info as they want, and we will follow the established credential loading precedence.
322338

323339
return roleToAssume && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && !accessKeyId && !webIdentityTokenFile && !roleChaining
324340
}
@@ -371,7 +387,9 @@ async function run() {
371387
roleSessionName,
372388
roleSkipSessionTagging,
373389
webIdentityTokenFile,
374-
webIdentityToken
390+
webIdentityToken,
391+
inlineSessionPolicy,
392+
managedSessionPolicies
375393
}) }, true);
376394
exportCredentials(roleCredentials);
377395
// We need to validate the credentials in 2 of our use-cases

index.test.js

+144
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const DEFAULT_INPUTS = {
4545
'aws-region': FAKE_REGION,
4646
'mask-aws-account-id': 'TRUE'
4747
};
48+
const DEFAULT_MULTILINE_INPUTS = {}
4849
const ASSUME_ROLE_INPUTS = {...CREDS_INPUTS, 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION};
4950

5051
const mockStsCallerIdentity = jest.fn();
@@ -90,6 +91,10 @@ describe('Configure AWS Credentials', () => {
9091
.fn()
9192
.mockImplementation(mockGetInput(DEFAULT_INPUTS));
9293

94+
core.getMultilineInput = jest
95+
.fn()
96+
.mockImplementation(mockGetInput(DEFAULT_MULTILINE_INPUTS));
97+
9398
core.getIDToken = jest
9499
.fn()
95100
.mockImplementation(() => {
@@ -624,6 +629,49 @@ describe('Configure AWS Credentials', () => {
624629
})
625630
});
626631

632+
test('Web identity token file with a inline session policy', async () => {
633+
const CUSTOM_SESSION_POLICY = "{ super_secure_policy }";
634+
core.getInput = jest
635+
.fn()
636+
.mockImplementation(mockGetInput({'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION, 'web-identity-token-file': '/fake/token/file', 'inline-session-policy': CUSTOM_SESSION_POLICY}));
637+
638+
await run();
639+
expect(mockStsAssumeRoleWithWebIdentity).toHaveBeenCalledWith({
640+
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
641+
RoleSessionName: 'GitHubActions',
642+
DurationSeconds: 6 * 3600,
643+
Policy: CUSTOM_SESSION_POLICY,
644+
WebIdentityToken: 'testpayload'
645+
})
646+
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCOUNT_ID);
647+
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_ACCESS_KEY_ID);
648+
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SECRET_ACCESS_KEY);
649+
expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_SESSION_TOKEN);
650+
});
651+
652+
test('Web identity token file with a managed session policies', async () => {
653+
const MANAGED_SESSION_POLICIES = ["arn:aws:iam::111111111111:policy/foo", "arn:aws:iam::111111111111:policy/bar"];
654+
core.getInput = jest
655+
.fn()
656+
.mockImplementation(mockGetInput({'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION, 'web-identity-token-file': '/fake/token/file'}));
657+
core.getMultilineInput = jest
658+
.fn()
659+
.mockImplementation(mockGetInput({'managed-session-policies': MANAGED_SESSION_POLICIES}))
660+
661+
await run();
662+
expect(mockStsAssumeRoleWithWebIdentity).toHaveBeenCalledWith({
663+
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
664+
RoleSessionName: 'GitHubActions',
665+
DurationSeconds: 6 * 3600,
666+
PolicyArns: [{arn: MANAGED_SESSION_POLICIES[0]}, {arn: MANAGED_SESSION_POLICIES[1]}],
667+
WebIdentityToken: 'testpayload'
668+
})
669+
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCOUNT_ID);
670+
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_ACCESS_KEY_ID);
671+
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SECRET_ACCESS_KEY);
672+
expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_SESSION_TOKEN);
673+
});
674+
627675
test('only role arn and region provided to use GH OIDC Token', async () => {
628676
process.env.GITHUB_ACTIONS = 'true';
629677
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
@@ -664,6 +712,51 @@ describe('Configure AWS Credentials', () => {
664712
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SESSION_TOKEN);
665713
});
666714

715+
test('GH OIDC With inline session policy', async () => {
716+
const CUSTOM_SESSION_POLICY = "{ super_secure_policy }";
717+
process.env.GITHUB_ACTIONS = 'true';
718+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
719+
core.getInput = jest
720+
.fn()
721+
.mockImplementation(mockGetInput({'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION, 'inline-session-policy': CUSTOM_SESSION_POLICY}));
722+
723+
await run();
724+
expect(mockStsAssumeRoleWithWebIdentity).toHaveBeenCalledWith({
725+
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
726+
RoleSessionName: 'GitHubActions',
727+
DurationSeconds: 3600,
728+
Policy: CUSTOM_SESSION_POLICY,
729+
WebIdentityToken: 'testtoken'
730+
});
731+
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_STS_ACCESS_KEY_ID);
732+
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_SECRET_ACCESS_KEY);
733+
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SESSION_TOKEN);
734+
});
735+
736+
test('GH OIDC With managed session policy', async () => {
737+
const MANAGED_SESSION_POLICIES = ["arn:aws:iam::111111111111:policy/foo", "arn:aws:iam::111111111111:policy/bar"];
738+
process.env.GITHUB_ACTIONS = 'true';
739+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
740+
core.getInput = jest
741+
.fn()
742+
.mockImplementation(mockGetInput({'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION}));
743+
core.getMultilineInput = jest
744+
.fn()
745+
.mockImplementation(mockGetInput({'managed-session-policies': MANAGED_SESSION_POLICIES}))
746+
747+
await run();
748+
expect(mockStsAssumeRoleWithWebIdentity).toHaveBeenCalledWith({
749+
RoleArn: 'arn:aws:iam::111111111111:role/MY-ROLE',
750+
RoleSessionName: 'GitHubActions',
751+
DurationSeconds: 3600,
752+
PolicyArns: [{arn: MANAGED_SESSION_POLICIES[0]}, {arn: MANAGED_SESSION_POLICIES[1]}],
753+
WebIdentityToken: 'testtoken'
754+
});
755+
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_STS_ACCESS_KEY_ID);
756+
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_STS_SECRET_ACCESS_KEY);
757+
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_STS_SESSION_TOKEN);
758+
});
759+
667760
test('role assumption fails after maximun trials using OIDC Provider', async () => {
668761
process.env.GITHUB_ACTIONS = 'true';
669762
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token';
@@ -704,6 +797,57 @@ describe('Configure AWS Credentials', () => {
704797
})
705798
});
706799

800+
test('inline session policy provided', async () => {
801+
const CUSTOM_SESSION_POLICY = "{ super_secure_policy }";
802+
core.getInput = jest
803+
.fn()
804+
.mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS, 'inline-session-policy': CUSTOM_SESSION_POLICY}));
805+
806+
await run();
807+
expect(mockStsAssumeRole).toHaveBeenCalledWith({
808+
RoleArn: ROLE_ARN,
809+
RoleSessionName: 'GitHubActions',
810+
DurationSeconds: 6 * 3600,
811+
Tags: [
812+
{Key: 'GitHub', Value: 'Actions'},
813+
{Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
814+
{Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW},
815+
{Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
816+
{Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
817+
{Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
818+
{Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
819+
],
820+
Policy: CUSTOM_SESSION_POLICY
821+
})
822+
});
823+
824+
test('managed session policy provided', async () => {
825+
const MANAGED_SESSION_POLICIES = ["arn:aws:iam::111111111111:policy/foo", "arn:aws:iam::111111111111:policy/bar"];
826+
core.getInput = jest
827+
.fn()
828+
.mockImplementation(mockGetInput({...ASSUME_ROLE_INPUTS}));
829+
core.getMultilineInput = jest
830+
.fn()
831+
.mockImplementation(mockGetInput({'managed-session-policies': MANAGED_SESSION_POLICIES}))
832+
833+
await run();
834+
expect(mockStsAssumeRole).toHaveBeenCalledWith({
835+
RoleArn: ROLE_ARN,
836+
RoleSessionName: 'GitHubActions',
837+
DurationSeconds: 6 * 3600,
838+
Tags: [
839+
{Key: 'GitHub', Value: 'Actions'},
840+
{Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
841+
{Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW},
842+
{Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
843+
{Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
844+
{Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
845+
{Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
846+
],
847+
PolicyArns: [{arn: MANAGED_SESSION_POLICIES[0]}, {arn: MANAGED_SESSION_POLICIES[1]}],
848+
})
849+
});
850+
707851
test('workflow name sanitized in role assumption tags', async () => {
708852
core.getInput = jest
709853
.fn()

0 commit comments

Comments
 (0)