How to Create an IAM User or Group in AWS CDK

avatar
Borislav Hadzhiev

Last updated: Jan 26, 2024
9 min

banner

# Table of Contents

  1. Creating IAM Users in AWS CDK
  2. Importing an Existing IAM User in AWS CDK
  3. IAM Group Examples in AWS CDK
  4. Attaching Policies to a Group in AWS CDK
  5. Adding a User to a Group in AWS CDK
  6. Importing an existing IAM Group in AWS CDK

# Creating IAM Users in AWS CDK

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

To have a complete reference for how to create an IAM User, let's define a simple CDK stack, where we:

  • create an IAM user
  • add the User to a Group
  • attach permission policies to the User
  • set a permissions boundary on the User
The code for this article is available on GitHub
lib/cdk-starter-stack.ts
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 group const group = new iam.Group(this, 'example-group', { managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), ], }); // ๐Ÿ‘‡ Create Managed Policy const loggingManagedPolicy = iam.ManagedPolicy.fromAwsManagedPolicyName( 'CloudWatchReadOnlyAccess', ); // ๐Ÿ‘‡ Create Permissions Boundary const permissionsBoundary = new iam.ManagedPolicy( this, 'example-permissions-boundary', { statements: [ new iam.PolicyStatement({ effect: iam.Effect.DENY, actions: ['sqs:*'], resources: ['*'], }), ], }, ); // ๐Ÿ‘‡ Create User const user = new iam.User(this, 'example-user', { userName: 'example-user', managedPolicies: [loggingManagedPolicy], groups: [group], permissionsBoundary, }); } }

Let's go over what we did in the code sample.

  1. We created an IAM Group, using the Group construct. In this case, the group grants read-only access to the EC2 service via an AWS-managed policy.

  2. We referenced a managed IAM Policy using the ManagedPolicy construct. In this case, the managed policy grants simple logging permissions.

  3. We created a Permissions Boundary using the PermissionsBoundary class. In this example, the permissions boundary denies any sqs related actions on all resources.

  4. We created an IAM User, using the User construct. Let's go over the props we passed to the User construct:

    • userName - a name for the user. If we didn't specify one, CDK would automatically generate a unique name for us. If we specify a userName, we can't perform updates that require resource replacement, so it's best to omit this prop unless we really need it.
    • managedPolicies - a list of managed policies to associate with the user.
    • groups - a list of groups the user will be added to.
    • permissionsBoundary - use a permissions boundary to set the maximum permissions that an IAM policy could grant the user.

There is also a password prop - the password for the IAM user. You only need to set a password if the user will need to access the AWS Management console.

Let's run the deploy command.

shell
npx aws-cdk deploy

If we take a look at the User in the IAM console, we can see that the user has been created, added to a group and the policies and permissions boundary have all been set.

iam user console

# Adding Permissions to an IAM User after creation in AWS CDK

To add additional permissions to a user after creation, we have to use the methods on the user object, for example:

The code for this article is available on GitHub

Let's look at an example that uses the addManagedPolicy and attachInlinePolicy methods.

lib/cdk-starter-stack.ts
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 the user user.addManagedPolicy( iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'), ); // ๐Ÿ‘‡ create an inline policy const inlinePolicy = new iam.Policy(this, 'cloudwatch-logs-policy', { statements: [ new iam.PolicyStatement({ actions: ['logs:PutLogEvents'], resources: ['*'], }), ], }); // ๐Ÿ‘‡ attach the inline policy to the user user.attachInlinePolicy(inlinePolicy); } }

Let's go over what we did in the code sample.

  1. We referenced an AWS-managed policy and added it to the user with the addManagedPolicy method.
  2. We created an inline IAM policy with simple logging permissions.
  3. We attached the inline IAM policy to the user via the attachInlinePolicy method.

Let's deploy the changes.

shell
npx aws-cdk deploy

If we take a look at the IAM console, we can see that the user now has 4 permission policies applied to it:

iam user policies

The same approach applies if we want to add the IAM user to a group. Then, we would create the group and use the addToGroup method on the user object.

# Removal Policy of IAM Users in AWS CDK

When a CDK stack gets deleted, the IAM users provisioned by the stack also get deleted. If you need to override this behavior, you can use the applyRemovalPolicy method and set the policy of the user to RETAIN.

However, note that if the user references any resources created by the stack, i.e. groups, policies, permission boundaries, an attempt to delete these resources would fail.

For example, trying to delete a group that a user references results in an error:

Cannot delete the entity, must remove users from the group first.

# Clean up

To delete the resources we've provisioned, issue the destroy command:

shell
npx aws-cdk destroy

# Importing an Existing IAM User in AWS CDK

In order to import an existing IAM User in AWS CDK, we have to use the fromUser* static methods on the User class:

# Importing an Existing IAM User by Name in AWS CDK

The most common method to import an existing IAM User in CDK is by the username.

To import a user by the username in CDK, we have to use the fromUserName static method on the User class.

import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐Ÿ‘‡ User imported by username const userByName = iam.User.fromUserName( this, 'user-by-name', 'YOUR_USER_NAME', ); console.log('user name ๐Ÿ‘‰', userByName.userName); } }

We used the fromUserName static method on the User class to import an existing IAM user into our CDK stack.

The fromUserName method takes the following parameters:

  • scope - the scope the construct is instantiated in
  • id - the id of the construct (must be unique within the scope)
  • userName - the username of the existing IAM user

If I run the synth command, having replaced the placeholder with an existing IAM username, I can see that the user is successfully imported and the userName is resolved at synthesis time.

user imported success

# Importing an Existing IAM User by ARN in AWS CDK

In order to import an existing IAM User by ARN in CDK, we have to use the fromUserArn static method on the User class.

import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const userByArn = iam.User.fromUserArn( this, 'user-by-arn', `arn:aws:iam::${cdk.Stack.of(this).account}:user/YOUR_USER_NAME`, ); console.log('user name ๐Ÿ‘‰', userByArn.userName); } }

The third parameter the fromUserArn method takes is the ARN of the existing IAM user.

# Importing an Existing IAM User by User Attributes in AWS CDK

In order to import an existing IAM User by user attributes in CDK, we have to use the fromUserAttributes method on the User class.

At the time of writing the only supported user attribute is userArn - docs.

There is no good reason to use the fromUserAttributes method over fromUserArn, but I'm including this snippet for completeness' sake.

import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const userByAttributes = iam.User.fromUserAttributes( this, 'user-by-attributes', { userArn: `arn:aws:iam::${ cdk.Stack.of(this).account }:user/YOUR_USER_NAME`, }, ); console.log('user name ๐Ÿ‘‰', userByAttributes.userName); } }

The third parameter the fromUserAttributes takes is a map of user attributes names and values. However, the only supported user attribute is the userArn.

It's better to use the fromUserArn method over fromUserAttributes because it more clearly conveys the intent of our code.

# IAM Group Examples in AWS CDK

An IAM Group is a collection of users. Groups enable us to reuse common permissions by attaching permissions on the group, rather than on each individual user.

In order to create groups in AWS CDK, we have to instantiate the Group class.

The code for this article is available on GitHub

Let's start by creating a simple IAM group with a managed policy:

lib/cdk-starter-stack.ts
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 an IAM group const group = new iam.Group(this, 'group-id', { managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), ], }); console.log('group name ๐Ÿ‘‰', group.groupName); console.log('group arn ๐Ÿ‘‰', group.groupArn); } }

We instantiated the Group class. The constructor method of the class takes 3 parameters:

  • groupName - a name for the group. We didn't specify one in the example, because by default CDK will generate a unique group name for us.
  • managedPolicies - a list of managed policies to associate with the group. We used an AWS-managed policy that grants EC2 read permissions.
  • path - the path to the group - defaults to /.

The values for the groupName and groupArn are tokens at synthesis time, so we can't use them in conditionals:

group tokens

Tokens are encoded values that get resolved at deployment time by CloudFormation.

Let's run the deploy command:

shell
npx aws-cdk deploy

If we look at the group in the IAM console, we can see that the managed policy has been attached to it:

group policy attached

# Attaching Policies to a Group in AWS CDK

We can attach a policy to a Group in CDK, by using the following methods:

The code for this article is available on GitHub

Let's look at an example of all 3 methods:

lib/cdk-starter-stack.ts
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 a managed policy to the group group.addManagedPolicy( iam.ManagedPolicy.fromAwsManagedPolicyName( 'service-role/AWSLambdaBasicExecutionRole', ), ); // ๐Ÿ‘‡ add an inline policy to the group group.addToPolicy( new iam.PolicyStatement({ actions: ['logs:CreateLogGroup', 'logs:CreateLogStream'], resources: ['*'], }), ); // ๐Ÿ‘‡ attach an inline policy on the group group.attachInlinePolicy( new iam.Policy(this, 'cw-logs', { statements: [ new iam.PolicyStatement({ effect: iam.Effect.DENY, actions: ['logs:PutLogEvents'], resources: ['*'], }), ], }), ); } }

We used the addManagedPolicy, addToPolicy and attachInlinePolicy methods on an instance of the Group class.

  • addManagedPolicy - takes a single parameter - an IAM-managed policy.

  • addToPolicy - takes a PolicyStatement instance as a parameter.

  • attachInlinePolicy takes a Policy instance as a parameter.

Let's issue the deploy command.

shell
npx aws-cdk deploy

If we take a look at the group in the IAM console, we can see that it now has 4 permission policies attached.

group policies attached

# Adding a User to a Group in AWS CDK

In order to add a user to a group in AWS CDK, we have to use the addUser method on an instance of the Group class.

The code for this article is available on GitHub
lib/cdk-starter-stack.ts
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 // ๐Ÿ‘‡ create IAM User const user = new iam.User(this, 'user-id'); // ๐Ÿ‘‡ add the User to the group group.addUser(user); } }

We created an IAM user and added it to a group.

Let's deploy the changes.

shell
npx aws-cdk deploy

If we look at the Users section of our group, we can see that the user we created has been added:

user in group

# Importing an existing IAM Group in AWS CDK

In order to import an external IAM Group in an AWS CDK stack, we have to use the fromGroupArn static method on the Group class.

import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐Ÿ‘‡ import existing Group const importedGroup = iam.Group.fromGroupArn( this, 'existing-group-id', `arn:aws:iam::${cdk.Stack.of(this).account}:group/YOUR_GROUP_NAME`, ); console.log('imported group name ๐Ÿ‘‰', importedGroup.groupName); console.log('imported group arn ๐Ÿ‘‰', importedGroup.groupArn); } }

We used the fromGroupArn static method on the Group class to import an external group. The method takes 3 parameters:

  1. scope - the scope of the construct.
  2. id - an identifier for the construct (must be unique within the scope).
  3. groupArn - the ARN of the group we want to import.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev