diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 19421428ea300..a6cf25dd3dbcf 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -1 +1,3 @@ change-return-type:@aws-cdk/core.Fn.getAtt +new-argument:@aws-cdk/aws-iam.ManagedPolicy. +new-argument:@aws-cdk/aws-iam.ManagedPolicy. diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index 604e2cd908807..7a755b85cf26f 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -155,6 +155,7 @@ export class Group extends GroupBase { * @param policy The managed policy to attach. */ public addManagedPolicy(policy: IManagedPolicy) { + if (this.managedPolicies.find(mp => mp === policy)) { return; } this.managedPolicies.push(policy); } } diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 3d78663f85269..90a906bd54ef6 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -1,4 +1,11 @@ -import { IResolveContext, Lazy, Stack } from '@aws-cdk/core'; +import { Construct, IResolveContext, Lazy, Resource, Stack} from '@aws-cdk/core'; +import { IGroup } from './group'; +import { CfnManagedPolicy } from './iam.generated'; +import { PolicyDocument } from './policy-document'; +import { PolicyStatement } from './policy-statement'; +import { IRole } from './role'; +import { IUser } from './user'; +import { undefinedIfEmpty } from './util'; /** * A managed policy @@ -6,17 +13,99 @@ import { IResolveContext, Lazy, Stack } from '@aws-cdk/core'; export interface IManagedPolicy { /** * The ARN of the managed policy + * @attribute */ readonly managedPolicyArn: string; } +export interface ManagedPolicyProps { + /** + * The name of the managed policy. If you specify multiple policies for an entity, + * specify unique names. For example, if you specify a list of policies for + * an IAM role, each policy must have a unique name. + * + * @default - A name is automatically generated. + */ + readonly managedPolicyName?: string; + + /** + * A description of the managed policy. Typically used to store information about the + * permissions defined in the policy. For example, "Grants access to production DynamoDB tables." + * The policy description is immutable. After a value is assigned, it cannot be changed. + * + * @default - empty + */ + readonly description?: string; + + /** + * The path for the policy. This parameter allows (through its regex pattern) a string of characters + * consisting of either a forward slash (/) by itself or a string that must begin and end with forward slashes. + * In addition, it can contain any ASCII character from the ! (\u0021) through the DEL character (\u007F), + * including most punctuation characters, digits, and upper and lowercased letters. + * + * For more information about paths, see IAM Identifiers in the IAM User Guide. + * + * @default - "/" + */ + readonly path?: string; + + /** + * Users to attach this policy to. + * You can also use `attachToUser(user)` to attach this policy to a user. + * + * @default - No users. + */ + readonly users?: IUser[]; + + /** + * Roles to attach this policy to. + * You can also use `attachToRole(role)` to attach this policy to a role. + * + * @default - No roles. + */ + readonly roles?: IRole[]; + + /** + * Groups to attach this policy to. + * You can also use `attachToGroup(group)` to attach this policy to a group. + * + * @default - No groups. + */ + readonly groups?: IGroup[]; + + /** + * Initial set of permissions to add to this policy document. + * You can also use `addPermission(statement)` to add permissions later. + * + * @default - No statements. + */ + readonly statements?: PolicyStatement[]; +} + /** * Managed policy * - * This class is an incomplete placeholder class, and exists only to get access - * to AWS Managed policies. */ -export class ManagedPolicy { +export class ManagedPolicy extends Resource implements IManagedPolicy { + /** + * Construct a customer managed policy from the managedPolicyName + * + * For this managed policy, you only need to know the name to be able to use it. + * + */ + public static fromManagedPolicyName(scope: Construct, id: string, managedPolicyName: string): IManagedPolicy { + class Import extends Resource implements IManagedPolicy { + public readonly managedPolicyArn = Stack.of(scope).formatArn({ + service: "iam", + region: "", // no region for managed policy + account: Stack.of(scope).account, // Can this be something the user specifies? + resource: "policy", + resourceName: managedPolicyName + }); + } + return new Import(scope, id); + } + /** * Construct a managed policy from one of the policies that AWS manages * @@ -43,6 +132,125 @@ export class ManagedPolicy { return new AwsManagedPolicy(); } - protected constructor() { + /** + * Returns the ARN of this managed policy. + * + * @attribute + */ + public readonly managedPolicyArn: string; + + /** + * The policy document. + */ + public readonly document = new PolicyDocument(); + + /** + * The name of this policy. + * + * @attribute + */ + public readonly managedPolicyName: string; + + /** + * The description of this policy. + * + * @attribute + */ + public readonly description: string; + + /** + * The path of this policy. + * + * @attribute + */ + public readonly path: string; + + private readonly roles = new Array(); + private readonly users = new Array(); + private readonly groups = new Array(); + + constructor(scope: Construct, id: string, props: ManagedPolicyProps = {}) { + super(scope, id, { + physicalName: props.managedPolicyName + }); + + this.description = props.description || ''; + this.path = props.path || '/'; + + const resource = new CfnManagedPolicy(this, 'Resource', { + policyDocument: this.document, + managedPolicyName: this.physicalName, + description: this.description, + path: this.path, + roles: undefinedIfEmpty(() => this.roles.map(r => r.roleName)), + users: undefinedIfEmpty(() => this.users.map(u => u.userName)), + groups: undefinedIfEmpty(() => this.groups.map(g => g.groupName)), + }); + + if (props.users) { + props.users.forEach(u => this.attachToUser(u)); + } + + if (props.groups) { + props.groups.forEach(g => this.attachToGroup(g)); + } + + if (props.roles) { + props.roles.forEach(r => this.attachToRole(r)); + } + + if (props.statements) { + props.statements.forEach(p => this.addStatements(p)); + } + + this.managedPolicyName = this.getResourceNameAttribute(resource.ref); + this.managedPolicyArn = this.getResourceArnAttribute(resource.ref, { + region: '', // IAM is global in each partition + service: 'iam', + resource: 'role', + resourceName: this.physicalName, + }); + } + + /** + * Adds a statement to the policy document. + */ + public addStatements(...statement: PolicyStatement[]) { + this.document.addStatements(...statement); + } + + /** + * Attaches this policy to a user. + */ + public attachToUser(user: IUser) { + if (this.users.find(u => u === user)) { return; } + this.users.push(user); + } + + /** + * Attaches this policy to a role. + */ + public attachToRole(role: IRole) { + if (this.roles.find(r => r === role)) { return; } + this.roles.push(role); + } + + /** + * Attaches this policy to a group. + */ + public attachToGroup(group: IGroup) { + if (this.groups.find(g => g === group)) { return; } + this.groups.push(group); + } + + protected validate(): string[] { + const result = new Array(); + + // validate that the policy document is not empty + if (this.document.isEmpty) { + result.push('Managed Policy is empty. You must add statements to the policy'); + } + + return result; } } diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 0260fa8e14f64..54643646311fc 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -300,6 +300,7 @@ export class Role extends Resource implements IRole { * @param policy The the managed policy to attach. */ public addManagedPolicy(policy: IManagedPolicy) { + if (this.managedPolicies.find(mp => mp === policy)) { return; } this.managedPolicies.push(policy); } diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 11898197b2cdc..0b7eba87d62d6 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -169,6 +169,7 @@ export class User extends Resource implements IIdentity { * @param policy The managed policy to attach. */ public addManagedPolicy(policy: IManagedPolicy) { + if (this.managedPolicies.find(mp => mp === policy)) { return; } this.managedPolicies.push(policy); } diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index e2cc7ecd61dd0..3bd23068629e4 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -82,5 +82,11 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "construct-interface-extends-iconstruct:@aws-cdk/aws-iam.IManagedPolicy", + "resource-interface-extends-resource:@aws-cdk/aws-iam.IManagedPolicy" + ] + }, "stability": "stable" } diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.expected.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.expected.json new file mode 100644 index 0000000000000..e46e0b1fbde6f --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.expected.json @@ -0,0 +1,66 @@ +{ + "Resources": { + "MyUserDC45028B": { + "Type": "AWS::IAM::User", + "Properties": { + "ManagedPolicyArns": [ + { + "Ref": "TwoManagedPolicy7E701864" + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/SecurityAudit" + ] + ] + } + ] + } + }, + "OneManagedPolicy77F9185F": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "Description": "My Policy", + "ManagedPolicyName": "Default", + "Path": "/some/path/", + "Users": [ + { + "Ref": "MyUserDC45028B" + } + ] + } + }, + "TwoManagedPolicy7E701864": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "Description": "", + "Path": "/" + } + } + } +} diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts new file mode 100644 index 0000000000000..5c225d1bf5de8 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts @@ -0,0 +1,26 @@ +import { App, Stack } from "@aws-cdk/core"; +import { ManagedPolicy, PolicyStatement } from "../lib"; +import { User } from "../lib/user"; + +const app = new App(); + +const stack = new Stack(app, 'aws-cdk-iam-managed-policy'); + +const user = new User(stack, 'MyUser'); + +const policy = new ManagedPolicy(stack, 'OneManagedPolicy', { + managedPolicyName: 'Default', + description: 'My Policy', + path: '/some/path/', +}); +policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); +policy.attachToUser(user); + +const policy2 = new ManagedPolicy(stack, 'TwoManagedPolicy'); +policy2.addStatements(new PolicyStatement({ resources: ['*'], actions: ['lambda:InvokeFunction'] })); +user.addManagedPolicy(policy2); + +const policy3 = ManagedPolicy.fromAwsManagedPolicyName('SecurityAudit'); +user.addManagedPolicy(policy3); + +app.synth(); diff --git a/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts b/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts index c80947e8412a6..c7ade47a4ce2a 100644 --- a/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/test/test.managed-policy.ts @@ -1,9 +1,10 @@ +import { expect } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/core'); import { Test } from 'nodeunit'; -import { ManagedPolicy } from '../lib'; +import { Group, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; export = { - 'simple managed policy'(test: Test) { + 'simple AWS managed policy'(test: Test) { const stack = new cdk.Stack(); const mp = ManagedPolicy.fromAwsManagedPolicyName("service-role/SomePolicy"); @@ -17,4 +18,491 @@ export = { test.done(); }, + + 'simple customer managed policy'(test: Test) { + const stack = new cdk.Stack(); + const mp = ManagedPolicy.fromManagedPolicyName(stack, 'MyCustomerManagedPolicy', "SomeCustomerPolicy"); + + test.deepEqual(stack.resolve(mp.managedPolicyArn), { + "Fn::Join": ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::', + { Ref: 'AWS::AccountId' }, + ':policy/SomeCustomerPolicy' + ]] + }); + + test.done(); + }, + + 'managed policy with statements'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + + const policy = new ManagedPolicy(stack, 'MyManagedPolicy', { managedPolicyName: 'MyManagedPolicyName' }); + policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); + policy.addStatements(new PolicyStatement({ resources: ['arn'], actions: ['sns:Subscribe'] })); + + const group = new Group(stack, 'MyGroup'); + group.addManagedPolicy(policy); + + expect(stack).toMatch({ + Resources: { + MyManagedPolicy9F3720AE: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + ManagedPolicyName: 'MyManagedPolicyName', + PolicyDocument: { + Statement: + [{ Action: 'sqs:SendMessage', Effect: 'Allow', Resource: '*' }, + { Action: 'sns:Subscribe', Effect: 'Allow', Resource: 'arn' }], + Version: '2012-10-17' + }, + Path: '/', + Description: '' + } + }, + MyGroupCBA54B1B: { + Type: 'AWS::IAM::Group', + Properties: { + ManagedPolicyArns: [ + { Ref: 'MyManagedPolicy9F3720AE' } + ] + } + } + } + }); + test.done(); + }, + + 'policy name can be omitted, in which case the logical id will be used'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + + const policy = new ManagedPolicy(stack, 'MyManagedPolicy'); + policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); + policy.addStatements(new PolicyStatement({ resources: ['arn'], actions: ['sns:Subscribe'] })); + + const group = new Group(stack, 'MyGroup'); + group.addManagedPolicy(policy); + + expect(stack).toMatch({ + Resources: { + MyManagedPolicy9F3720AE: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + PolicyDocument: { + Statement: + [{ Action: 'sqs:SendMessage', Effect: 'Allow', Resource: '*' }, + { Action: 'sns:Subscribe', Effect: 'Allow', Resource: 'arn' }], + Version: '2012-10-17' + }, + Path: '/', + Description: '' + } + }, + MyGroupCBA54B1B: { + Type: 'AWS::IAM::Group', + Properties: { + ManagedPolicyArns: [ + { Ref: 'MyManagedPolicy9F3720AE' } + ] + } + } + } + }); + test.done(); + }, + + 'via props, managed policy can be attached to users, groups and roles and permissions, description and path can be added'(test: Test) { + const app = new cdk.App(); + + const stack = new cdk.Stack(app, 'MyStack'); + + const user1 = new User(stack, 'User1'); + const group1 = new Group(stack, 'Group1'); + const role1 = new Role(stack, 'Role1', { + assumedBy: new ServicePrincipal('test.service') + }); + + new ManagedPolicy(stack, 'MyTestManagedPolicy', { + managedPolicyName: 'Foo', + users: [ user1 ], + groups: [ group1 ], + roles: [ role1 ], + description: 'My Policy Description', + path: 'tahiti/is/a/magical/place', + statements: [ new PolicyStatement({ resources: ['*'], actions: ['dynamodb:PutItem'] }) ], + }); + + expect(stack).toMatch({ + Resources: { + User1E278A736: { Type: 'AWS::IAM::User' }, + Group1BEBD4686: { Type: 'AWS::IAM::Group' }, + Role13A5C70C1: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + }, + MyTestManagedPolicy6535D9F5: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + Groups: [{ Ref: 'Group1BEBD4686' }], + Description: 'My Policy Description', + Path: 'tahiti/is/a/magical/place', + PolicyDocument: { + Statement: + [{ Action: 'dynamodb:PutItem', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17' + }, + ManagedPolicyName: 'Foo', + Roles: [{ Ref: 'Role13A5C70C1' }], + Users: [{ Ref: 'User1E278A736' }] + } + } + } + }); + + test.done(); + }, + + 'idempotent if a principal (user/group/role) is attached twice'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + const p = new ManagedPolicy(stack, 'MyManagedPolicy'); + p.addStatements(new PolicyStatement({ actions: ['*'], resources: ['*'] })); + + const user = new User(stack, 'MyUser'); + p.attachToUser(user); + p.attachToUser(user); + + const group = new Group(stack, 'MyGroup'); + p.attachToGroup(group); + p.attachToGroup(group); + + const role = new Role(stack, 'MyRole', { + assumedBy: new ServicePrincipal('test.service') + }); + p.attachToRole(role); + p.attachToRole(role); + + expect(stack).toMatch({ + Resources: { + MyManagedPolicy9F3720AE: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + PolicyDocument: { + Statement: [{ Action: '*', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17' + }, + Description: '', + Path: '/', + Users: [{ Ref: 'MyUserDC45028B' }], + Groups: [{ Ref: 'MyGroupCBA54B1B' }], + Roles: [{ Ref: 'MyRoleF48FFE04' }] + } + }, + MyUserDC45028B: { Type: 'AWS::IAM::User' }, + MyGroupCBA54B1B: { Type: 'AWS::IAM::Group' }, + MyRoleF48FFE04: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + } + } + }); + test.done(); + }, + + 'users, groups, roles and permissions can be added using methods'(test: Test) { + const app = new cdk.App(); + + const stack = new cdk.Stack(app, 'MyStack'); + + const p = new ManagedPolicy(stack, 'MyManagedPolicy', { + managedPolicyName: 'Foo', + }); + + p.attachToUser(new User(stack, 'User1')); + p.attachToUser(new User(stack, 'User2')); + p.attachToGroup(new Group(stack, 'Group1')); + p.attachToRole(new Role(stack, 'Role1', { assumedBy: new ServicePrincipal('test.service') })); + p.addStatements(new PolicyStatement({ resources: ['*'], actions: ['dynamodb:GetItem'] })); + + expect(stack).toMatch({ + Resources: { + MyManagedPolicy9F3720AE: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + Groups: [{ Ref: 'Group1BEBD4686' }], + PolicyDocument: { + Statement: + [{ Action: 'dynamodb:GetItem', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17' + }, + ManagedPolicyName: 'Foo', + Description: '', + Path: '/', + Roles: [{ Ref: 'Role13A5C70C1' }], + Users: [{ Ref: 'User1E278A736' }, { Ref: 'User21F1486D1' }] + } + }, + User1E278A736: { Type: 'AWS::IAM::User' }, + User21F1486D1: { Type: 'AWS::IAM::User' }, + Group1BEBD4686: { Type: 'AWS::IAM::Group' }, + Role13A5C70C1: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + } + } + }); + test.done(); + }, + + 'policy can be attached to users, groups or role via methods on the principal'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + + const policy = new ManagedPolicy(stack, 'MyManagedPolicy'); + const user = new User(stack, 'MyUser'); + const group = new Group(stack, 'MyGroup'); + const role = new Role(stack, 'MyRole', { assumedBy: new ServicePrincipal('test.service') }); + + user.addManagedPolicy(policy); + group.addManagedPolicy(policy); + role.addManagedPolicy(policy); + + policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['*'] })); + + expect(stack).toMatch({ + Resources: { + MyManagedPolicy9F3720AE: { + Type: 'AWS::IAM::ManagedPolicy', + Properties: { + PolicyDocument: { + Statement: [{ Action: '*', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17' + }, + Description: '', + Path: '/' + } + }, + MyUserDC45028B: { Type: 'AWS::IAM::User', Properties: { ManagedPolicyArns: [{ Ref: 'MyManagedPolicy9F3720AE' }] } }, + MyGroupCBA54B1B: { Type: 'AWS::IAM::Group', Properties: { ManagedPolicyArns: [{ Ref: 'MyManagedPolicy9F3720AE' }] } }, + MyRoleF48FFE04: { + Type: 'AWS::IAM::Role', + Properties: { + ManagedPolicyArns: [{ Ref: 'MyManagedPolicy9F3720AE' }], + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + } + } + }); + test.done(); + }, + + 'policy from AWS managed policy lookup can be attached to users, groups or role via methods on the principal'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + + const policy = ManagedPolicy.fromAwsManagedPolicyName('AnAWSManagedPolicy'); + const user = new User(stack, 'MyUser'); + const group = new Group(stack, 'MyGroup'); + const role = new Role(stack, 'MyRole', { assumedBy: new ServicePrincipal('test.service') }); + + user.addManagedPolicy(policy); + group.addManagedPolicy(policy); + role.addManagedPolicy(policy); + + expect(stack).toMatch({ + Resources: { + MyUserDC45028B: { + Type: 'AWS::IAM::User', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::aws:policy/AnAWSManagedPolicy" + ] + ] + }] + } + }, + MyGroupCBA54B1B: { + Type: 'AWS::IAM::Group', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::aws:policy/AnAWSManagedPolicy" + ] + ] + }] + } + }, + MyRoleF48FFE04: { + Type: 'AWS::IAM::Role', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::aws:policy/AnAWSManagedPolicy" + ] + ] + }], + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + } + } + }); + test.done(); + }, + + 'policy from customer managed policy lookup can be attached to users, groups or role via methods'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + + const policy = ManagedPolicy.fromManagedPolicyName(stack, 'MyManagedPolicy', 'ACustomerManagedPolicyName'); + const user = new User(stack, 'MyUser'); + const group = new Group(stack, 'MyGroup'); + const role = new Role(stack, 'MyRole', { assumedBy: new ServicePrincipal('test.service') }); + + user.addManagedPolicy(policy); + group.addManagedPolicy(policy); + role.addManagedPolicy(policy); + + expect(stack).toMatch({ + Resources: { + MyUserDC45028B: { + Type: 'AWS::IAM::User', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::", + { Ref: "AWS::AccountId" }, + ":policy/ACustomerManagedPolicyName" + ] + ] + }] + } + }, + MyGroupCBA54B1B: { + Type: 'AWS::IAM::Group', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::", + { Ref: "AWS::AccountId" }, + ":policy/ACustomerManagedPolicyName" + ] + ] + }] + } + }, + MyRoleF48FFE04: { + Type: 'AWS::IAM::Role', + Properties: { + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { Ref: "AWS::Partition" }, + ":iam::", + { Ref: "AWS::AccountId" }, + ":policy/ACustomerManagedPolicyName" + ] + ] + }], + AssumeRolePolicyDocument: { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'test.service' } + }], + Version: '2012-10-17' + } + } + } + } + }); + test.done(); + }, + + 'fails if policy document is empty'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + new ManagedPolicy(stack, 'MyPolicy'); + test.throws(() => app.synth(), /Managed Policy is empty. You must add statements to the policy/); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-iam/test/test.role.ts b/packages/@aws-cdk/aws-iam/test/test.role.ts index 6b9eb1c721723..c67162410d94e 100644 --- a/packages/@aws-cdk/aws-iam/test/test.role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.role.ts @@ -378,4 +378,4 @@ export = { test.done(); } -}; \ No newline at end of file +};