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: