Skip to content

Commit 661a95e

Browse files
Sander Knapemergify[bot]
authored andcommitted
feat(iam): support permissions boundary policy for User and Role (#3584)
* Add permissions boundary to IAM user and role L2 constructs * fix copy/paste typo * remove unnecessary quotes
1 parent ac77990 commit 661a95e

File tree

4 files changed

+101
-2
lines changed

4 files changed

+101
-2
lines changed

packages/@aws-cdk/aws-iam/lib/role.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@ export interface RoleProps {
5757
*/
5858
readonly path?: string;
5959

60+
/**
61+
* AWS supports permissions boundaries for IAM entities (users or roles).
62+
* A permissions boundary is an advanced feature for using a managed policy
63+
* to set the maximum permissions that an identity-based policy can grant to
64+
* an IAM entity. An entity's permissions boundary allows it to perform only
65+
* the actions that are allowed by both its identity-based policies and its
66+
* permissions boundaries.
67+
*
68+
* @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-permissionsboundary
69+
* @link https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
70+
*
71+
* @default - No permissions boundary.
72+
*/
73+
readonly permissionsBoundary?: IManagedPolicy;
74+
6075
/**
6176
* A name for the IAM role. For valid values, see the RoleName parameter for
6277
* the CreateRole action in the IAM API Reference.
@@ -196,6 +211,11 @@ export class Role extends Resource implements IRole {
196211
*/
197212
public readonly policyFragment: PrincipalPolicyFragment;
198213

214+
/**
215+
* Returns the permissions boundary attached to this role
216+
*/
217+
public readonly permissionsBoundary?: IManagedPolicy;
218+
199219
private defaultPolicy?: Policy;
200220
private readonly managedPolicies: IManagedPolicy[] = [];
201221
private readonly attachedPolicies = new AttachedPolicies();
@@ -207,7 +227,7 @@ export class Role extends Resource implements IRole {
207227

208228
this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, props.externalId);
209229
this.managedPolicies.push(...props.managedPolicies || []);
210-
230+
this.permissionsBoundary = props.permissionsBoundary;
211231
const maxSessionDuration = props.maxSessionDuration && props.maxSessionDuration.toSeconds();
212232
validateMaxSessionDuration(maxSessionDuration);
213233

@@ -216,6 +236,7 @@ export class Role extends Resource implements IRole {
216236
managedPolicyArns: Lazy.listValue({ produce: () => this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }),
217237
policies: _flatten(props.inlinePolicies),
218238
path: props.path,
239+
permissionsBoundary: this.permissionsBoundary ? this.permissionsBoundary.managedPolicyArn : undefined,
219240
roleName: this.physicalName,
220241
maxSessionDuration,
221242
});

packages/@aws-cdk/aws-iam/lib/user.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ export interface UserProps {
4141
*/
4242
readonly path?: string;
4343

44+
/**
45+
* AWS supports permissions boundaries for IAM entities (users or roles).
46+
* A permissions boundary is an advanced feature for using a managed policy
47+
* to set the maximum permissions that an identity-based policy can grant to
48+
* an IAM entity. An entity's permissions boundary allows it to perform only
49+
* the actions that are allowed by both its identity-based policies and its
50+
* permissions boundaries.
51+
*
52+
* @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-permissionsboundary
53+
* @link https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
54+
*
55+
* @default - No permissions boundary.
56+
*/
57+
readonly permissionsBoundary?: IManagedPolicy;
58+
4459
/**
4560
* A name for the IAM user. For valid values, see the UserName parameter for
4661
* the CreateUser action in the IAM API Reference. If you don't specify a
@@ -98,6 +113,11 @@ export class User extends Resource implements IIdentity {
98113
*/
99114
public readonly userArn: string;
100115

116+
/**
117+
* Returns the permissions boundary attached to this user
118+
*/
119+
public readonly permissionsBoundary?: IManagedPolicy;
120+
101121
public readonly policyFragment: PrincipalPolicyFragment;
102122

103123
private readonly groups = new Array<any>();
@@ -111,12 +131,14 @@ export class User extends Resource implements IIdentity {
111131
});
112132

113133
this.managedPolicies.push(...props.managedPolicies || []);
134+
this.permissionsBoundary = props.permissionsBoundary;
114135

115136
const user = new CfnUser(this, 'Resource', {
116137
userName: this.physicalName,
117138
groups: undefinedIfEmpty(() => this.groups),
118139
managedPolicyArns: Lazy.listValue({ produce: () => this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }),
119140
path: props.path,
141+
permissionsBoundary: this.permissionsBoundary ? this.permissionsBoundary.managedPolicyArn : undefined,
120142
loginProfile: this.parseLoginProfile(props)
121143
});
122144

packages/@aws-cdk/aws-iam/test/test.role.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
22
import { Duration, Stack } from '@aws-cdk/core';
33
import { Test } from 'nodeunit';
4-
import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PolicyStatement, Role, ServicePrincipal, User } from '../lib';
4+
import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User } from '../lib';
55

66
export = {
77
'default role'(test: Test) {
@@ -290,4 +290,32 @@ export = {
290290
test.done();
291291
},
292292

293+
'can supply permissions boundary managed policy'(test: Test) {
294+
// GIVEN
295+
const stack = new Stack();
296+
297+
const permissionsBoundary = ManagedPolicy.fromAwsManagedPolicyName('managed-policy');
298+
299+
new Role(stack, 'MyRole', {
300+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
301+
permissionsBoundary,
302+
});
303+
304+
expect(stack).to(haveResource('AWS::IAM::Role', {
305+
PermissionsBoundary: {
306+
"Fn::Join": [
307+
"",
308+
[
309+
"arn:",
310+
{
311+
Ref: "AWS::Partition"
312+
},
313+
":iam::aws:policy/managed-policy"
314+
]
315+
]
316+
}
317+
}));
318+
319+
test.done();
320+
}
293321
};

packages/@aws-cdk/aws-iam/test/test.user.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,34 @@ export = {
5252
]
5353
}));
5454

55+
test.done();
56+
},
57+
58+
'can supply permissions boundary managed policy'(test: Test) {
59+
// GIVEN
60+
const stack = new Stack();
61+
62+
const permissionsBoundary = ManagedPolicy.fromAwsManagedPolicyName('managed-policy');
63+
64+
new User(stack, 'MyUser', {
65+
permissionsBoundary,
66+
});
67+
68+
expect(stack).to(haveResource('AWS::IAM::User', {
69+
PermissionsBoundary: {
70+
"Fn::Join": [
71+
"",
72+
[
73+
"arn:",
74+
{
75+
Ref: "AWS::Partition"
76+
},
77+
":iam::aws:policy/managed-policy"
78+
]
79+
]
80+
}
81+
}));
82+
5583
test.done();
5684
}
5785
};

0 commit comments

Comments
 (0)