-
Notifications
You must be signed in to change notification settings - Fork 513
feat: add support for assuming a role #17
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
Merged
Merged
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
231a2c9
first draft attempt at adding role assumption option
mattsb42-aws 9300b8d
refinements
mattsb42-aws 07bd0f1
const not var
mattsb42-aws 7e38407
clean up asserts
mattsb42-aws 731510e
set explicit sts endpoint and clarify required inputs error message
mattsb42-aws 4070b33
streamline mocks
mattsb42-aws 28e4d92
add new inputs to Action definition
mattsb42-aws f643a76
ignore .idea directory
mattsb42-aws 410c0dc
add initial assume role test
mattsb42-aws c8d7dfa
make tests fail usefully when not in GitHub Actions
mattsb42-aws 94e4963
add logic to handle suppression of stack trace
mattsb42-aws b084b72
pull credentials exports out into function
mattsb42-aws 3452b67
convert environment variable patching to use object for source and ad…
mattsb42-aws e41ee3d
add test for STS call
mattsb42-aws 42f8b5b
compartmentalization and use custom user agent in role assumption STS…
mattsb42-aws 9e5498a
change DO_NOT_SUPRESS_STACK_TRACE to SHOW_STACK_TRACE
mattsb42-aws b2b2c02
update role-to-assume input description
mattsb42-aws File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ node_modules/ | |
|
||
# Editors | ||
.vscode | ||
.idea | ||
|
||
# Logs | ||
logs | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,94 @@ | ||
const core = require('@actions/core'); | ||
const aws = require('aws-sdk'); | ||
const assert = require('assert'); | ||
const util = require('util'); | ||
|
||
// The max time that a GitHub action is allowed to run is 6 hours. | ||
// That seems like a reasonable default to use if no role duration is defined. | ||
const MAX_ACTION_RUNTIME = 6 * 3600; | ||
const USER_AGENT = 'configure-aws-credentials-for-github-actions'; | ||
|
||
async function assumeRole(params) { | ||
// Assume a role to get short-lived credentials using longer-lived credentials. | ||
const isDefined = i => !!i; | ||
|
||
const {roleToAssume, roleDurationSeconds, accessKeyId, secretAccessKey, sessionToken, region} = params; | ||
assert( | ||
[roleToAssume, roleDurationSeconds, accessKeyId, secretAccessKey, region].every(isDefined), | ||
"Missing required input when assuming a Role." | ||
); | ||
|
||
const {GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_REF, GITHUB_SHA} = process.env; | ||
assert( | ||
[GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_REF, GITHUB_SHA].every(isDefined), | ||
'Missing required environment value. Are you running in GitHub Actions?' | ||
); | ||
|
||
const endpoint = util.format('https://sts.%s.amazonaws.com', region); | ||
|
||
const sts = new aws.STS({ | ||
accessKeyId, secretAccessKey, sessionToken, region, endpoint, customUserAgent: USER_AGENT | ||
}); | ||
return sts.assumeRole({ | ||
RoleArn: roleToAssume, | ||
RoleSessionName: 'GitHubActions', | ||
DurationSeconds: roleDurationSeconds, | ||
Tags: [ | ||
{Key: 'GitHub', Value: 'Actions'}, | ||
{Key: 'Repository', Value: GITHUB_REPOSITORY}, | ||
{Key: 'Workflow', Value: GITHUB_WORKFLOW}, | ||
{Key: 'Action', Value: GITHUB_ACTION}, | ||
{Key: 'Actor', Value: GITHUB_ACTOR}, | ||
{Key: 'Branch', Value: GITHUB_REF}, | ||
{Key: 'Commit', Value: GITHUB_SHA}, | ||
] | ||
}) | ||
.promise() | ||
.then(function (data) { | ||
return { | ||
accessKeyId: data.Credentials.AccessKeyId, | ||
secretAccessKey: data.Credentials.SecretAccessKey, | ||
sessionToken: data.Credentials.SessionToken, | ||
}; | ||
}); | ||
} | ||
|
||
function exportCredentials(params){ | ||
// Configure the AWS CLI and AWS SDKs using environment variables | ||
const {accessKeyId, secretAccessKey, sessionToken} = params; | ||
|
||
// AWS_ACCESS_KEY_ID: | ||
// Specifies an AWS access key associated with an IAM user or role | ||
core.exportVariable('AWS_ACCESS_KEY_ID', accessKeyId); | ||
|
||
// AWS_SECRET_ACCESS_KEY: | ||
// Specifies the secret key associated with the access key. This is essentially the "password" for the access key. | ||
core.exportVariable('AWS_SECRET_ACCESS_KEY', secretAccessKey); | ||
|
||
// AWS_SESSION_TOKEN: | ||
// Specifies the session token value that is required if you are using temporary security credentials. | ||
if (sessionToken) { | ||
core.exportVariable('AWS_SESSION_TOKEN', sessionToken); | ||
} | ||
} | ||
|
||
function exportRegion(region) { | ||
// AWS_DEFAULT_REGION and AWS_REGION: | ||
// Specifies the AWS Region to send requests to | ||
core.exportVariable('AWS_DEFAULT_REGION', region); | ||
core.exportVariable('AWS_REGION', region); | ||
} | ||
|
||
async function exportAccountId(maskAccountId) { | ||
// Get the AWS account ID | ||
const sts = new aws.STS({customUserAgent: USER_AGENT}); | ||
const identity = await sts.getCallerIdentity().promise(); | ||
const accountId = identity.Account; | ||
core.setOutput('aws-account-id', accountId); | ||
if (!maskAccountId || maskAccountId.toLowerCase() == 'true') { | ||
core.setSecret(accountId); | ||
} | ||
} | ||
|
||
async function run() { | ||
try { | ||
|
@@ -9,41 +98,32 @@ async function run() { | |
const region = core.getInput('aws-region', { required: true }); | ||
const sessionToken = core.getInput('aws-session-token', { required: false }); | ||
const maskAccountId = core.getInput('mask-aws-account-id', { required: false }); | ||
const roleToAssume = core.getInput('role-to-assume', {required: false}); | ||
const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME; | ||
|
||
// Configure the AWS CLI and AWS SDKs using environment variables | ||
|
||
// AWS_ACCESS_KEY_ID: | ||
// Specifies an AWS access key associated with an IAM user or role | ||
core.exportVariable('AWS_ACCESS_KEY_ID', accessKeyId); | ||
|
||
// AWS_SECRET_ACCESS_KEY: | ||
// Specifies the secret key associated with the access key. This is essentially the "password" for the access key. | ||
core.exportVariable('AWS_SECRET_ACCESS_KEY', secretAccessKey); | ||
|
||
// AWS_SESSION_TOKEN: | ||
// Specifies the session token value that is required if you are using temporary security credentials. | ||
if (sessionToken) { | ||
core.exportVariable('AWS_SESSION_TOKEN', sessionToken); | ||
// Get role credentials if configured to do so | ||
if (roleToAssume) { | ||
const roleCredentials = await assumeRole( | ||
{accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleDurationSeconds} | ||
); | ||
exportCredentials(roleCredentials); | ||
} else { | ||
exportCredentials({accessKeyId, secretAccessKey, sessionToken}); | ||
} | ||
|
||
// AWS_DEFAULT_REGION and AWS_REGION: | ||
// Specifies the AWS Region to send requests to | ||
core.exportVariable('AWS_DEFAULT_REGION', region); | ||
core.exportVariable('AWS_REGION', region); | ||
|
||
// Get the AWS account ID | ||
const sts = new aws.STS({ | ||
customUserAgent: 'configure-aws-credentials-for-github-actions' | ||
}); | ||
const identity = await sts.getCallerIdentity().promise(); | ||
const accountId = identity.Account; | ||
core.setOutput('aws-account-id', accountId); | ||
if (!maskAccountId || maskAccountId.toLowerCase() == 'true') { | ||
core.setSecret(accountId); | ||
} | ||
exportRegion(region); | ||
|
||
await exportAccountId(maskAccountId); | ||
} | ||
catch (error) { | ||
core.setFailed(error.message); | ||
|
||
const suppressStackTrace = process.env.DO_NOT_SUPPRESS_STACK_TRACE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: the variable name seems opposite to the env variable name: |
||
|
||
if (suppressStackTrace === 'true') { | ||
throw(error) | ||
} | ||
|
||
} | ||
} | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by
persisting the credentials directly
? I associate that with persisting to disk. Do you mean something like using the credentials for API calls directly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant by "persisting" was "passing the values along to later steps via outputs".
Maybe
Use the provided credentials to assume a Role and output the assumed credentials for that Role rather than the provided credentials.
?