IAM Role Examples in AWS CDK - Complete Guide

avatar

Borislav Hadzhiev

Mon Apr 26 20215 min read

banner

Photo by Simon Maage

In order to create an IAM role in AWS CDK we have to use the Role construct.

Table of Contents #

  1. Creating IAM Roles in AWS CDK
  2. Avoid Circular Dependency with inline Policies and IAM Roles
  3. Attach a Managed Policy to an IAM Role after Role Creation
  4. Attach an Inline Policy to an IAM Role after Role Creation
  5. Add a Principal to an IAM Role after Role Creation

Creating IAM Roles in AWS CDK #

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.

The code for this article is available on GitHub

To demo using IAM Roles in CDK, let's provision a stack, that consists of a single IAM role:

lib/cdk-starter-stack.ts
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

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:

  1. we created an IAM Policy document, named describeAcmCertificates. A Policy Document is a collection of Policy Statements

    In our case the only Policy Statement we've passed to the document grants DescribeCertificate action permission for Amazon Certificate Manager resources.

  2. We've created an IAM Role. The props we've passed to the role are:

  • assumedBy - the IAM Principal, which can assume the role, in our case this is the API Gateway service.

    We can specify different types of principals, common ones include:

    • ArnPrincipal - specify a principal by the ARN (users, roles, accounts)
    • AccountPrincipal - specify a principal by the AWS account ID (123456789)
    • ServicePrincipal - specify an AWS service as a principal (lambda.amazonaws.com)
  • 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

We don't have to provide 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:

shell
npx 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):

permission policies role

The role's trust policy (describes who can assume the role) includes the API Gateway service, as we specified in the assumedBy prop:

trust policy role

Avoid Circular Dependency with inline Policies and IAM Roles #

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.

The code for this article is available on GitHub

In order to add policies to a role by provisioning a separate CloudFormation resource we have to use the addToPolicy method.

lib/cdk-starter-stack.ts
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

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 execute a deployment:

shell
npx 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:

policy after role creation

The policy has also been applied to the IAM role:

permission policies role 2

Attach a Managed Policy to an IAM Role after Role Creation #

In order to attach a Managed Policy to a role, after we've created the role, we have to use the addManagedPolicy method.

lib/cdk-starter-stack.ts
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

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 our 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:

arn managed 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 cdk deploy command, I can see that the AmazonAPIGatewayPushToCloudWatchLogs managed policy has been attached to the role.

attached-managed-policy-role

Attach an Inline Policy to an IAM Role after Role Creation #

The code for this article is available on GitHub

In order to attach an Inline Policy to an IAM Role after the role has been created, we need to use the attachInlinePolicy method.

lib/cdk-starter-stack.ts
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

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: ['*'],
          }),
        ],
      }),
    );
  }
}

In the code snippet we've 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:

shell
npx cdk deploy

The inline policy has been created as a separate CloudFormation resource and it has been attached to the role.

inline policy role

Add a Principal to an IAM Role after Role Creation #

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.

lib/cdk-starter-stack.ts
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

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')],
      }),
    );
  }
}

In the code snippet we've modified the assume role policy document of the role to allow the lambda service to assume the role.

Let's deploy the changes:

shell
npx 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:

updated role trust policy

If multiple principals are added to a policy, they will be merged together.

Clean up #

To delete the resources we've provisioned execute the destroy command:

shell
npx cdk destroy

Further Reading #

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee