Last updated: Jan 26, 2024
Reading timeยท9 min

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:
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.
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.
We referenced a managed IAM Policy using the ManagedPolicy construct. In this case, the managed policy grants simple logging permissions.
We created a Permissions Boundary using the
PermissionsBoundary
class. In this example, the permissions boundary denies any sqs related
actions on all resources.
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.
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.

To add additional permissions to a user after creation, we have to use the methods on the user object, for example:
Let's look at an example that uses the addManagedPolicy and
attachInlinePolicy methods.
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.
addManagedPolicy method.attachInlinePolicy
method.Let's deploy the changes.
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:

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.
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:
To delete the resources we've provisioned, issue the destroy command:
npx aws-cdk destroy
In order to import an existing IAM User in AWS CDK, we have to use the
fromUser* static methods on the
User
class:
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 inid - the id of the construct (must be unique within the scope)userName - the username of the existing IAM userIf 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.

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.
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.
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.
Let's start by creating a simple IAM group with a managed policy:
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:

Tokens are encoded values that get resolved at deployment time by CloudFormation.
Let's run the deploy command:
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:

We can attach a policy to a Group in CDK, by using the following methods:
Let's look at an example of all 3 methods:
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.
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.

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

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:
scope - the scope of the construct.id - an identifier for the construct (must be
unique within the scope).groupArn - the ARN of the group we want to import.You can learn more about the related topics by checking out the following tutorials: