Skip to content

Commit

Permalink
Support for instance profiles (#525)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-lohausen-consilica authored Jul 31, 2023
1 parent 6664bb3 commit 87ae4e2
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "Support to assume a role without ACCESS KEY ID/SECRET KEY"
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ To enable tasks to call AWS services when run as part of your build or release p
The AWS tasks support the following mechanisms for obtaining AWS credentials:

- One or more service endpoints, of type _AWS_, can be created and populated with AWS access and secret keys, and optionally data for _Assumed Role_ credentials.
- If only the _Assumed Role_ is defined but neither access key ID nor secret key, the role be assumed regardless. This is useful when using instance profices, and and profile only allows to assume a role.
- Tasks reference the configured service endpoint instances by name as part of their configuration and pull the required credentials from the endpoint when run.
- Variables defined on the task or build.
- If tasks are not configured with the name of a service endpoint they will attempt to obtain credentials, and optionally region, from variables defined in the build environment. The
Expand Down
78 changes: 66 additions & 12 deletions src/lib/awsConnectionParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ export function buildConnectionParameters(): AWSConnectionParameters {
return connectionParameters
}

function getEndpointAuthInfo(awsparams: AWSConnectionParameters, endpointName: string) {
awsparams.awsEndpointAuth = tl.getEndpointAuthorization(endpointName, false)
console.log(`...configuring AWS credentials from service endpoint '${endpointName}'`)
const accessKey = awsparams.awsEndpointAuth?.parameters?.username
const secretKey = awsparams.awsEndpointAuth?.parameters?.password
if ((accessKey && !secretKey) || (!accessKey && secretKey)) {
throw new Error('Need to define or omit both "Access Key ID" and "Secret Access Key", not just one.')
}
const token = awsparams.awsEndpointAuth?.parameters?.sessionToken
const assumeRoleArn = awsparams.awsEndpointAuth?.parameters?.assumeRoleArn
const externalId = awsparams.awsEndpointAuth?.parameters?.externalId
const roleSessionName = awsparams.awsEndpointAuth?.parameters?.roleSessionName
return {
accessKey: accessKey,
secretKey: secretKey,
token: token,
assumeRoleArn: assumeRoleArn,
externalId: externalId,
roleSessionName: roleSessionName
}
}

// Unpacks credentials from the specified endpoint configuration, if defined
function attemptEndpointCredentialConfiguration(
awsparams: AWSConnectionParameters,
Expand All @@ -102,18 +124,46 @@ function attemptEndpointCredentialConfiguration(
if (!endpointName) {
return undefined
}
const authInfo = getEndpointAuthInfo(awsparams, endpointName)
authInfo.accessKey = authInfo.accessKey ?? ''
authInfo.secretKey = authInfo.secretKey ?? ''

return createEndpointCredentials(
authInfo.accessKey,
authInfo.secretKey,
authInfo.token,
authInfo.assumeRoleArn,
authInfo.externalId,
authInfo.roleSessionName
)
}

awsparams.awsEndpointAuth = tl.getEndpointAuthorization(endpointName, false)
console.log(`...configuring AWS credentials from service endpoint '${endpointName}'`)

const accessKey = awsparams.awsEndpointAuth?.parameters?.username ?? ''
const secretKey = awsparams.awsEndpointAuth?.parameters?.password ?? ''
const token = awsparams.awsEndpointAuth?.parameters?.sessionToken
const assumeRoleArn = awsparams.awsEndpointAuth?.parameters?.assumeRoleArn
const externalId = awsparams.awsEndpointAuth?.parameters?.externalId
const roleSessionName = awsparams.awsEndpointAuth?.parameters?.roleSessionName

return createEndpointCredentials(accessKey, secretKey, token, assumeRoleArn, externalId, roleSessionName)
// If only the role name to assume is set but no credentials,
// we try to assume the role directly
async function assumeRoleFromInstanceProfile(
awsparams: AWSConnectionParameters,
endpointName: string | undefined
): Promise<AWS.Credentials | undefined> {
if (!endpointName) {
return undefined
}
const authInfo = getEndpointAuthInfo(awsparams, endpointName)
authInfo.roleSessionName = authInfo.roleSessionName ?? defaultRoleSessionName
if (!authInfo.accessKey && !authInfo.secretKey && authInfo.assumeRoleArn) {
console.log('Assuming role without credentials (via instance profile)...')
const params = {
RoleArn: authInfo.assumeRoleArn,
RoleSessionName: authInfo.roleSessionName
}
const sts = new STS()
const data = await sts.assumeRole(params).promise()
return new AWS.Credentials({
accessKeyId: data.Credentials!.AccessKeyId,
secretAccessKey: data.Credentials!.SecretAccessKey,
sessionToken: data.Credentials!.SessionToken
})
}
return undefined
}

// credentials can also be set, using their environment variable names,
Expand Down Expand Up @@ -196,7 +246,6 @@ function createEndpointCredentials(
if (externalId) {
options.ExternalId = externalId
}

return new AWS.TemporaryCredentials(options, masterCredentials)
}

Expand All @@ -209,6 +258,11 @@ function createEndpointCredentials(
export async function getCredentials(awsParams: AWSConnectionParameters): Promise<AWS.Credentials | undefined> {
console.log('Configuring credentials for task')

const role_credentials = await assumeRoleFromInstanceProfile(awsParams, tl.getInput('awsCredentials', false))
if (typeof role_credentials !== 'undefined') {
return role_credentials
}

const credentials =
attemptEndpointCredentialConfiguration(awsParams, tl.getInput('awsCredentials', false)) ||
attemptCredentialConfigurationFromVariables()
Expand Down
8 changes: 4 additions & 4 deletions vss-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,22 @@
{
"id": "username",
"name": "Access Key ID",
"description": "The AWS access key ID for signing programmatic requests.\nExample: AKIAIOSFODNN7EXAMPLE",
"description": "The AWS access key ID for signing programmatic requests.\nExample: AKIAIOSFODNN7EXAMPLE. Not needed when using instance profiles.",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"isRequired": false,
"dataType": "string"
}
},
{
"id": "password",
"name": "Secret Access Key",
"description": "The AWS secret access key for signing programmatic requests.\nExample: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"description": "The AWS secret access key for signing programmatic requests.\nExample: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY. Not needed when using instance profiles.",
"inputMode": "passwordbox",
"isConfidential": true,
"validation": {
"isRequired": true,
"isRequired": false,
"dataType": "string"
}
},
Expand Down

0 comments on commit 87ae4e2

Please sign in to comment.