Borislav Hadzhiev
Reading timeยท6 min
Photo from Unsplash
IAM Roles are collections of policies that grant specific permissions to access resources.
In order to create an IAM Role in AWS CDK we have to use the Role construct.
To demo using IAM Roles in CDK, let's provision a stack that consists of a single IAM role:
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐ Create ACM Permission Policy const describeAcmCertificates = new iam.PolicyDocument({ statements: [ new iam.PolicyStatement({ resources: ['arn:aws:acm:*:*:certificate/*'], actions: ['acm:DescribeCertificate'], }), ], }); // ๐ Create Role const role = new iam.Role(this, 'example-iam-role', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), description: 'An example IAM role in AWS CDK', inlinePolicies: { DescribeACMCerts: describeAcmCertificates, }, managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( 'AmazonAPIGatewayInvokeFullAccess', ), ], }); } }
Let's go over what we did in the code snippet:
We created an IAM Policy document, named describeAcmCertificates
. A
Policy Document
is a collection of
Policy Statements
In this case, the only Policy Statement we've passed to the document grants
DescribeCertificate
action permission for Amazon Certificate Manager
resources.
We created an IAM Role. The props we've passed to the role are:
assumedBy
- the IAM Principal, which can assume the role. In this case
this is the API Gateway service.
We can specify different types of principals, common ones include:
description
- a short description of the IAM role
inlinePolicies
- a map of policies that will be inlined into the role.
The name of the inline policy is taken from the key of the map, in our case
DescribeACMCerts
. The inline policies are created with the role, which can
sometimes cause circular dependencies. We will see how we can attach
policies after role creation later in the article.
managedPolicies
- a list of managed policies to associate with the role
inlinePolicies
and managedPolicies
props as these are optional. We can attach policies to the role after the role has been created Note that we didn't provide a role name, so a unique name will be automatically generated for us, based on the role's logical ID.
Let's deploy the CDK Stack:
npx aws-cdk deploy
The CloudFormation stack has provisioned a single IAM role. The role has 2 Permission policies (policies that describe the permissions of the role):
The role's trust policy (describes who can assume the role) includes the API
Gateway service, as we specified in the assumedBy
prop:
Policies we attach using the inlinePolicies
prop on the role are created
when the IAM role is created. Depending on the resources we have to reference
in the IAM role, this can lead to a circular dependency.
In order to add policies to a role by provisioning a separate CloudFormation resource we have to use the addToPolicy method.
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // ๐ add an Inline Policy to the Role role.addToPolicy( new iam.PolicyStatement({ actions: ['logs:CreateLogGroup', 'logs:CreateLogStream'], resources: ['*'], }), ); } }
Let's run the deploy
command:
npx aws-cdk deploy
After the resources have been provisioned, we can see that our stack now consists of 2 resources - the IAM Role and a separately provisioned IAM Policy:
The policy has also been applied to the IAM role:
In order to attach a Managed Policy to a role, after we've created the role, we have to use the addManagedPolicy method.
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // ๐ add a Managed Policy to role role.addManagedPolicy( iam.ManagedPolicy.fromAwsManagedPolicyName( 'service-role/AmazonAPIGatewayPushToCloudWatchLogs', ), ); } }
The addManagedPolicy
method attaches a managed policy to the role.
When using the fromAwsManagedPolicyName
method, we have to provide the name of
the managed policy.
However, some managed policy names have a prefix of service-role
,
job-function
, and other managed policy names have no prefix at all. We have to
include the managed policy prefix when invoking the fromAwsManagedPolicyName
method.
In this case we've included the service-role/
prefix. The easiest way to see
if the policy has a prefix is to look at the ARN of the policy:
In the screenshot, we can see that the service-role/
prefix is present in the
Policy ARN, therefore we should include it.
After I run the npx aws-cdk deploy
command, I can see that the
AmazonAPIGatewayPushToCloudWatchLogs
managed policy has been attached to the
role.
In order to attach an Inline Policy to an IAM Role after the role has been created, we need to use the attachInlinePolicy method.
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // ๐ attach an Inline Policy to role role.attachInlinePolicy( new iam.Policy(this, 'cw-logs', { statements: [ new iam.PolicyStatement({ actions: ['logs:PutLogEvents'], resources: ['*'], }), ], }), ); } }
We attached an inline IAM Policy that grants a single action to cloudwatch logs resources to the principal of the role.
Let's deploy the changes:
npx aws-cdk deploy
The inline policy has been created as a separate CloudFormation resource and it has been attached to the role.
In order to add a Principal to an IAM Role after the role has been created we have to modify the assumeRolePolicy property of the role.
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // ๐ Add the Lambda service as a Principal role.assumeRolePolicy?.addStatements( new iam.PolicyStatement({ actions: ['sts:AssumeRole'], effect: iam.Effect.ALLOW, principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], }), ); } }
We modified the assume role policy document of the role to allow the lambda service to assume the role.
Let's deploy the changes:
npx aws-cdk deploy
If we take a look at the Trust Relationship of the role, we can see that the lambda service has been added as a principal:
If multiple principals are added to a policy, they will be merged together.
To delete the resources we've provisioned, issue the destroy
command:
npx aws-cdk destroy
In order to import an existing IAM Role in CDK, we have to use the fromRoleArn static method on the Role construct.
import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐ import existing IAM Role const importedRole = iam.Role.fromRoleArn( this, 'imported-role', `arn:aws:iam::${cdk.Stack.of(this).account}:role/Existing-Role-Name`, {mutable: false}, ); console.log('importedRole ๐', importedRole.roleName); } }
We used the fromRoleArn
method to import an external IAM Role in our CDK
stack. The third parameter we passed to the method is the ARN of the IAM role
we want to import.
The
mutable
prop specifies whether the imported role can be modified by attaching policies
to it. By default, the mutable
prop is set to true
.
It doesn't make much sense to import a role and then modify its permissions, so most of the time it's best to avoid this behavior.
If I run the cdk synth
command to run the code from the snippet with a role
ARN that exists in my account, I can see that the role name is resolved at
synthesis time: