This is a CDK credential plugin that assumes a specified role in the Stack account.
This plugin allows CDK Pipelines to perform context lookups.
There are two main use cases that this plugin addresses.
- You have a CDK application that deploys stacks to multiple AWS accounts.
- You have a CDK application that deploys a stack to an AWS account that is different than the current AWS account.
This plugin allows the CDK CLI to automatically obtain AWS credentials from a stack's target AWS account. This means that you can run a single command (i.e. cdk synth) with a set of AWS credentials, and the CLI will determine the target AWS account for each stack and automatically obtain temporary credentials for the target AWS account by assuming a role in the account.
For more details on the credential process see the How does it work section below.
In order to use the plugin in a CDK app you have to first perform a couple prerequisites
If you are running the CDK cli from a global install you'll need to install the plugin globally as well.
$ npm install -g cdk-assume-role-credential-plugin
If you are running from a locally installed version of the CDK cli (i.e. npm run cdk
or npx cdk
) you can
install the plugin locally
$ npm install cdk-assume-role-credential-plugin
I would recommend installing the plugin both locally and globally so that the plugin can be used both on a development machine as well as part of a CI/CD pipeline.
You must then tell the CDK app to use the plugin. This can be done in two ways.
- cdk.json file
example cdk.json
{
"app": "npx ts-node bin/my-app",
"plugin": ["cdk-assume-role-credential-plugin"]
}
- via the
--plugin
option on the cli
$ cdk synth --plugin cdk-assume-role-credential-plugin
This plugin needs to know the name of the IAM roles to assume in the target AWS account. By default it looks for IAM roles with the names:
cdk-readOnlyRole
(for read only operations)cdk-writeRole
(for write operations)
If you would like to provide your own custom values you can do so through setting context values.
The plugin will look for two context keys, which can either be set in the cdk.context.json
file or via the --context
option on the cli.
assume-role-credentials:readIamRoleName
: The role name of the role in the stack account that will be assumed to perform read only activities.assume-role-credentials:writeIamRoleName
: The role name of the role in the stack account that will be assumed to perform write activities.
example cdk.context.json
{
"assume-role-credentials:writeIamRoleName": "writeRole",
"assume-role-credentials:readIamRoleName": "readRole"
}
example cli
$ cdk synth --context assume-role-credentials:writeIamRoleName=writeRole --context assume-role-credentials:readIamRoleName=readRole
In addition, the role names can support a placeholder value for the target AWS account ID. This
is especially handy with the new CDK Bootstrap style because the new bootstrap already creates roles
that can be used with this plugin. Below is an example of using the CDK Bootstrap's deploy-role
.
example cdk.context.json
{
"context": {
"assume-role-credentials:readIamRoleName": "cdk-hnb659fds-deploy-role-{ACCOUNT_ID}-us-east-1"
}
}
So the {ACCOUNT_ID}
placeholder will be replaced with whatever AWS account ID you are attempted to
deploy into. Caution: the AWS region cannot be replaced, so all of your AWS accounts need to have a
common bootstrapped region.
Once the prerequisites are completed the CDK CLI will automatically attempt to use the credential plugin if the default credentials do not work for the stack's target AWS account.
For example, suppose I had a CDK application that deployed 2 stacks, each to a different AWS account. I am deploying these stacks from a 3rd AWS, so the CDK CLI will automatically attempt to use the plugin to obtain credentials for the target accounts.
My CDK app
const dev = { account: '2383838383', region: 'us-east-2' };
const prod = { account: '8373873873', region: 'us-east-2' };
new MyAppStack(app, 'dev', { env: dev });
new MyAppStack(app, 'prod', { env: prod });
I'll run a single command to synthesize the application.
$ cdk synth
Successfully synthesized to /myapp/cdk.out
Supply a stack id (dev, prod) to display its template.
If you want to see what is happening behind the scenes you can run the command with verbose logging enabled.
removing logs that aren't related to the plugin
$ cdk synth -v
...
AssumeRoleCredentialPlugin found value for readIamRole cdk-readOnlyRole. checking if we can obtain credentials
AssumeRoleCredentialPlugin found value for writeIamRole cdk-writeRole. checking if we can obtain credentials
canProvideCredentails for read role: true
canProvideCredentails for write role: true
Using AssumeRoleCredentialPlugin credentials for account 2383838383
AssumeRoleCredentialPlugin getting credentials for role arn:aws:iam::2383838383:role/cdk-readOnlyRole with mode 0
...
Successfully synthesized to /myapp/cdk.out
Supply a stack id (dev, prod) to display its template.
If you are using the new style synthesis by setting the context value @aws-cdk/core:newStyleStackSynthesis
to true
then this plugin will work a little differently.
When this setting is true
, the CLI switches to the new (post-1.46.0) bootstrapping resources
This new bootstrapping stack creates a bucket and several roles in your account, which the CDK CLI
use to deploy to it. In the future, the new bootstrapping resources will become the default, but as of now
they’re still opt-in.
When the new style synthesis is used, the CLI follows these high level steps when deploying your app
- Load
default
AWS credentials Then for each stack: - Check if credentials match the stack's environment
- If not, then try to find credentials by using the plugin Then for each of the next two steps it will get credentials using the plugin, but will not actually use them
- Publish Assets using bootstrapped
file-publishing-role
(assume this role usingdefault
credentials) - Create & execute CloudFormation Changeset using bootstrapped
deploy-role
&cfn-exec
roles (assumes thedeploy-role
usingdefault
credentials)
This means that the credentials retreived by the plugin are only used to:
- As a credential check (ensure we can get credentials for target account) (follow this issue for when this will no longer be needed)
- To perform context lookups (i.e.
ssm.StringParameter.fromLookupValue()
)
The CLI no longer needs the cdk-writeRole
for anything other than a credential check, so the plugin will treat the ForWriting
mode
a little differently.
-
If
@aws-cdk/core:newStyleStackSynthesis=true
&mode=ForWriting
& we are not bootstrapping: then The plugin will simply return thedefault
credentials (will not assume a role). This will satisfy the credential check. -
If
@aws-cdk/core:newStyleStackSynthesis=true
&mode=ForReading
: then The plugin will use thereadOnlyRole
. Since we don't know whether it is being used to fetch context or simply perform the credential check, we have to assume that it is fetching context. -
If
@aws-cdk/core:newStyleStackSynthesis=true
&mode=ForWriting
&bootstrap=true
: then The plugin will use thewriteRole
. See the next section for details on why. -
Otherwise assume this is using the old style syntesis and use both roles as normal.
This plugin can also be used while using the bootstrap
command while @aws-cdk/core:newStyleStackSynthesis
is set to true
.
For this to work you do need both the readOnlyRole
and writeRole
since the bootstrap process does not use
the bootstrap roles (chicken and egg problem).
Using the same example from the Using the plugin section above:
const dev = { account: '2383838383', region: 'us-east-2' };
const prod = { account: '8373873873', region: 'us-east-2' };
new MyAppStack(app, 'dev', { env: dev });
new MyAppStack(app, 'prod', { env: prod });
You can then bootstrap the target accounts by running the bootstrap command with an additional context
variable bootstrap=true
. The bootstrap context variable tells the plugin that we are running the bootstrap
command so it should use the writeRole
to perform write operations (i.e. create & execute changeset).
$ cdk bootstrap --trust REPLACE_WITH_TRUSTED_ACCOUNT_ID --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess --plugin cdk-assume-role-credential-plugin --context bootstrap=true
note I did not have to specify the environments in the bootstrap command because they are set on the stacks
If I am using CDK Pipelines and my stacks exist within a Stage the CLI can't determine the environments so you will need to specify, i.e.:
$ cdk bootstrap --trust REPLACE_WITH_TRUSTED_ACCOUNT_ID --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://2383838383/us-east-2 aws://8373873873/us-east-2 --plugin cdk-assume-role-credential-plugin --context bootstrap=true
This plugin can also be used to enable context lookups for CDK Pipelines.
When using CDK Pipelines you only need to create the readOnlyRole
in each
target account. See the section on newStyleStackSynthesis for
more details.
You will also need to update the synthAction
of your CdkPipeline construct
to add an IAM policy allowing the IAM role attached to the CodeBuild project
the ability to AssumeRole into the cdk-readOnlyRole
.
new pipelines.CdkPipeline(this, 'Pipeline', {
...
synthAction: pipelines.SimpleSynthAction.standardNpmSynth({
...
rolePolicyStatements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"sts:AssumeRole",
],
resources: [
"arn:aws:iam::*:role/cdk-readOnlyRole"
]
})
]
})
});
For a complete example checkout the sample application.
The CDK has the concept of environments An environment is a combination of the target AWS account and AWS region into which an individual stack is intended to be deployed.
A CDK app can contain multiple stacks the are deployed to multiple environments. A simple example of this
would be an application that deployed a dev
stack into a dev
AWS account and a prod
stack into a prod
AWS account. This would look something like:
const dev = { account: '2383838383', region: 'us-east-2' };
const prod = { account: '8373873873', region: 'us-east-2' };
new MyAppStack(app, 'dev', { env: dev });
new MyAppStack(app, 'prod', { env: prod });
When you run a cdk command such as synth
or deploy
the cli will need to perform actions against the AWS
account that is defined for the stack. It will attempt to use your default credentials, but what happens if you
need credentials for multiple accounts? This is where credential plugins come into play. The basic flow that the
cli will take when obtaining credentials is:
- Determine the
environment
for stack - Look for credentials that can be used against that environment.
- If it can find credentials in the DefaultCredentialChain then it will use those.
- If it can't find any, then it will load any credential plugins and attempt to fetch credentials for the environment using the credential plugins
Without using a credential plugin you would need to manually obtain credentials for each environment and then run the cli for that stack. A common script would be something like this:
#!/bin/bash
ASSUME_ROLE_ARN=$1
SESSION_NAME=$2
STACK=$3
creds=$(mktemp -d)/creds.json
echo "assuming role ${ASSUME_ROLE_ARN} with session-name ${SESSION_NAME}"
aws sts assume-role --role-arn $ASSUME_ROLE_ARN --role-session-name $SESSION_NAME > $creds
export AWS_ACCESS_KEY_ID=$(cat ${creds} | grep "AccessKeyId" | cut -d '"' -f 4)
export AWS_SECRET_ACCESS_KEY=$(cat ${creds} | grep "SecretAccessKey" | cut -d '"' -f 4)
export AWS_SESSION_TOKEN=$(cat ${creds} | grep "SessionToken" | cut -d '"' -f 4)
npm run cdk synth -- $STACK -o dist
Which you would then exexute for each stack:
$ ./assume_role_script.sh arn:aws:iam::2383838383:role/synthRole synth dev
$ ./assume_role_script.sh arn:aws:iam::8373873873:role/synthRole synth prod
This can become difficult to maintain, especially across multiple projects and with a CI/CD pipeline. Instead you can just install a credential plugin and execute a single command and the cli will obtain the appropriate credentials for each stack.
$ cdk synth
getting credentials for role arn:aws:iam::2383838383:role/synthRole with mode 0
getting credentials for role arn:aws:iam::8373873873:role/synthRole with mode 0
Successfully synthesized to cdk.out
Supply a stack id (dev, prod) to display its template.