diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json index 6cb98bc62c137..8b1082c6e9e3e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json @@ -1,5 +1,5 @@ { - "version": "30.0.0", + "version": "34.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json index cbfa28341b318..e3634a40bb81e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json @@ -1,7 +1,7 @@ { - "version": "30.0.0", + "version": "34.0.0", "files": { - "df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33": { + "26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120": { "source": { "path": "aws-cdk-iam-managed-policy.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33.json", + "objectKey": "26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json index 0ed3d9e61a60d..9ee516ff18224 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json @@ -25,6 +25,9 @@ "OneManagedPolicy77F9185F": { "Type": "AWS::IAM::ManagedPolicy", "Properties": { + "Description": "My Policy", + "ManagedPolicyName": "Default", + "Path": "/some/path/", "PolicyDocument": { "Statement": [ { @@ -45,9 +48,11 @@ ], "Version": "2012-10-17" }, - "Description": "My Policy", - "ManagedPolicyName": "Default", - "Path": "/some/path/", + "Roles": [ + { + "Ref": "Role1ABCC5F0" + } + ], "Users": [ { "Ref": "MyUserDC45028B" @@ -58,6 +63,8 @@ "TwoManagedPolicy7E701864": { "Type": "AWS::IAM::ManagedPolicy", "Properties": { + "Description": "", + "Path": "/", "PolicyDocument": { "Statement": [ { @@ -77,9 +84,7 @@ } ], "Version": "2012-10-17" - }, - "Description": "", - "Path": "/" + } } }, "Role1ABCC5F0": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out index ae4b03c54e770..2313ab5436501 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"30.0.0"} \ No newline at end of file +{"version":"34.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/integ.json index 07a2bb95c178f..f9969fc9eec0c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "30.0.0", + "version": "34.0.0", "testCases": { "ManagedPolicyInteg/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json index c26d78c144695..002f00e8212b3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "30.0.0", + "version": "34.0.0", "artifacts": { "aws-cdk-iam-managed-policy.assets": { "type": "cdk:asset-manifest", @@ -14,10 +14,11 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "aws-cdk-iam-managed-policy.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -85,6 +86,7 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/tree.json index df0dd5a3a5487..09406ac7403d9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.js.snapshot/tree.json @@ -38,13 +38,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnUser", + "fqn": "aws-cdk-lib.aws_iam.CfnUser", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.User", + "fqn": "aws-cdk-lib.aws_iam.User", "version": "0.0.0" } }, @@ -56,7 +56,7 @@ "id": "ImportedOneManagedPolicy", "path": "aws-cdk-iam-managed-policy/OneManagedPolicy/ImportedOneManagedPolicy", "constructInfo": { - "fqn": "@aws-cdk/core.Resource", + "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" } }, @@ -66,6 +66,9 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::IAM::ManagedPolicy", "aws:cdk:cloudformation:props": { + "description": "My Policy", + "managedPolicyName": "Default", + "path": "/some/path/", "policyDocument": { "Statement": [ { @@ -86,9 +89,11 @@ ], "Version": "2012-10-17" }, - "description": "My Policy", - "managedPolicyName": "Default", - "path": "/some/path/", + "roles": [ + { + "Ref": "Role1ABCC5F0" + } + ], "users": [ { "Ref": "MyUserDC45028B" @@ -97,13 +102,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnManagedPolicy", + "fqn": "aws-cdk-lib.aws_iam.CfnManagedPolicy", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.ManagedPolicy", + "fqn": "aws-cdk-lib.aws_iam.ManagedPolicy", "version": "0.0.0" } }, @@ -115,7 +120,7 @@ "id": "ImportedTwoManagedPolicy", "path": "aws-cdk-iam-managed-policy/TwoManagedPolicy/ImportedTwoManagedPolicy", "constructInfo": { - "fqn": "@aws-cdk/core.Resource", + "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" } }, @@ -125,6 +130,8 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::IAM::ManagedPolicy", "aws:cdk:cloudformation:props": { + "description": "", + "path": "/", "policyDocument": { "Statement": [ { @@ -144,19 +151,17 @@ } ], "Version": "2012-10-17" - }, - "description": "", - "path": "/" + } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnManagedPolicy", + "fqn": "aws-cdk-lib.aws_iam.CfnManagedPolicy", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.ManagedPolicy", + "fqn": "aws-cdk-lib.aws_iam.ManagedPolicy", "version": "0.0.0" } }, @@ -168,7 +173,7 @@ "id": "ImportRole", "path": "aws-cdk-iam-managed-policy/Role/ImportRole", "constructInfo": { - "fqn": "@aws-cdk/core.Resource", + "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" } }, @@ -208,13 +213,21 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnRole", + "fqn": "aws-cdk-lib.aws_iam.CfnRole", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-iam.Role", + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "ImportedRole": { + "id": "ImportedRole", + "path": "aws-cdk-iam-managed-policy/ImportedRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" } }, @@ -222,7 +235,7 @@ "id": "BootstrapVersion", "path": "aws-cdk-iam-managed-policy/BootstrapVersion", "constructInfo": { - "fqn": "@aws-cdk/core.CfnParameter", + "fqn": "aws-cdk-lib.CfnParameter", "version": "0.0.0" } }, @@ -230,13 +243,13 @@ "id": "CheckBootstrapVersion", "path": "aws-cdk-iam-managed-policy/CheckBootstrapVersion", "constructInfo": { - "fqn": "@aws-cdk/core.CfnRule", + "fqn": "aws-cdk-lib.CfnRule", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/core.Stack", + "fqn": "aws-cdk-lib.Stack", "version": "0.0.0" } }, @@ -253,7 +266,7 @@ "path": "ManagedPolicyInteg/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.249" + "version": "10.3.0" } }, "DeployAssert": { @@ -264,7 +277,7 @@ "id": "BootstrapVersion", "path": "ManagedPolicyInteg/DefaultTest/DeployAssert/BootstrapVersion", "constructInfo": { - "fqn": "@aws-cdk/core.CfnParameter", + "fqn": "aws-cdk-lib.CfnParameter", "version": "0.0.0" } }, @@ -272,25 +285,25 @@ "id": "CheckBootstrapVersion", "path": "ManagedPolicyInteg/DefaultTest/DeployAssert/CheckBootstrapVersion", "constructInfo": { - "fqn": "@aws-cdk/core.CfnRule", + "fqn": "aws-cdk-lib.CfnRule", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/core.Stack", + "fqn": "aws-cdk-lib.Stack", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/integ-tests.IntegTest", + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", "version": "0.0.0" } }, @@ -299,12 +312,12 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.249" + "version": "10.3.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/core.App", + "fqn": "aws-cdk-lib.App", "version": "0.0.0" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.ts index d30976dc491eb..09a466b8453b4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.managed-policy.ts @@ -27,6 +27,12 @@ const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); role.grantAssumeRole(policy.grantPrincipal); Grant.addToPrincipal({ actions: ['iam:*'], resourceArns: [role.roleArn], grantee: policy2 }); +policy.attachToRole(role); + +// Idempotent with imported roles, see https://github.com/aws/aws-cdk/issues/28101 +const importedRole = Role.fromRoleArn(stack, 'ImportedRole', role.roleArn); +policy.attachToRole(importedRole); + new IntegTest(app, 'ManagedPolicyInteg', { testCases: [stack], }); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.role.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.role.ts index 89fec30eac73f..ca3a161594ac5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.role.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.role.ts @@ -16,6 +16,10 @@ const policy = new Policy(stack, 'HelloPolicy', { policyName: 'Default' }); policy.addStatements(new PolicyStatement({ actions: ['ec2:*'], resources: ['*'] })); policy.attachToRole(role); +// Idempotent with imported roles, see https://github.com/aws/aws-cdk/issues/28101 +const importedRole = Role.fromRoleArn(stack, 'TestImportedRole', role.roleArn); +policy.attachToRole(importedRole); + // Role with an external ID new Role(stack, 'TestRole2', { assumedBy: new AccountRootPrincipal(), diff --git a/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts b/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts index 3c089262c4cd9..4c335cf38b489 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts @@ -282,7 +282,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl * Attaches this policy to a user. */ public attachToUser(user: IUser) { - if (this.users.find(u => u === user)) { return; } + if (this.users.find(u => u.userArn === user.userArn)) { return; } this.users.push(user); } @@ -290,7 +290,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl * Attaches this policy to a role. */ public attachToRole(role: IRole) { - if (this.roles.find(r => r === role)) { return; } + if (this.roles.find(r => r.roleArn === role.roleArn)) { return; } this.roles.push(role); } @@ -298,7 +298,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl * Attaches this policy to a group. */ public attachToGroup(group: IGroup) { - if (this.groups.find(g => g === group)) { return; } + if (this.groups.find(g => g.groupArn === group.groupArn)) { return; } this.groups.push(group); } diff --git a/packages/aws-cdk-lib/aws-iam/lib/policy.ts b/packages/aws-cdk-lib/aws-iam/lib/policy.ts index 763a7ae42a84e..43c3addd72b0f 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/policy.ts @@ -197,7 +197,7 @@ export class Policy extends Resource implements IPolicy, IGrantable { * Attaches this policy to a user. */ public attachToUser(user: IUser) { - if (this.users.find(u => u === user)) { return; } + if (this.users.find(u => u.userArn === user.userArn)) { return; } this.users.push(user); user.attachInlinePolicy(this); } @@ -206,7 +206,7 @@ export class Policy extends Resource implements IPolicy, IGrantable { * Attaches this policy to a role. */ public attachToRole(role: IRole) { - if (this.roles.find(r => r === role)) { return; } + if (this.roles.find(r => r.roleArn === role.roleArn)) { return; } this.roles.push(role); role.attachInlinePolicy(this); } @@ -215,7 +215,7 @@ export class Policy extends Resource implements IPolicy, IGrantable { * Attaches this policy to a group. */ public attachToGroup(group: IGroup) { - if (this.groups.find(g => g === group)) { return; } + if (this.groups.find(g => g.groupArn === group.groupArn)) { return; } this.groups.push(group); group.attachInlinePolicy(this); } diff --git a/packages/aws-cdk-lib/aws-iam/test/managed-policy.test.ts b/packages/aws-cdk-lib/aws-iam/test/managed-policy.test.ts index e9d3f5891dab2..540edbf65eff3 100644 --- a/packages/aws-cdk-lib/aws-iam/test/managed-policy.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/managed-policy.test.ts @@ -284,6 +284,63 @@ describe('managed policy', () => { }); }); + test('idempotent if an imported principal (user/group/role) is attached twice', () => { + const p = new ManagedPolicy(stack, 'MyManagedPolicy'); + p.addStatements(new PolicyStatement({ actions: ['*'], resources: ['*'] })); + + const user = new User(stack, 'MyUser'); + const importedUser = User.fromUserArn(stack, 'MyImportedUser', user.userArn); + p.attachToUser(user); + p.attachToUser(importedUser); + + const group = new Group(stack, 'MyGroup'); + const importedGroup = Group.fromGroupArn(stack, 'MyImportedGroup', group.groupArn); + p.attachToGroup(group); + p.attachToGroup(importedGroup); + + const role = new Role(stack, 'MyRole', { + assumedBy: new ServicePrincipal('test.service'), + }); + const importedRole = Role.fromRoleArn(stack, 'MyImportedRole', role.roleArn); + p.attachToRole(role); + p.attachToRole(importedRole); + + Template.fromStack(stack).templateMatches({ + 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('users, groups, roles and permissions can be added using methods', () => { const p = new ManagedPolicy(stack, 'MyManagedPolicy', { managedPolicyName: 'Foo', diff --git a/packages/aws-cdk-lib/aws-iam/test/policy.test.ts b/packages/aws-cdk-lib/aws-iam/test/policy.test.ts index 8ff963ca2c394..f70cccef24c7e 100644 --- a/packages/aws-cdk-lib/aws-iam/test/policy.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/policy.test.ts @@ -340,6 +340,129 @@ describe('IAM policy', () => { p3.attachToRole(role); }); + test('idempotent if a principal (user/group/role) is attached twice', () => { + const p = new Policy(stack, 'Policy'); + p.addStatements(new PolicyStatement({ resources: ['*'], actions: ['*'] })); + + 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); + + Template.fromStack(stack).templateMatches({ + Resources: + { + Policy23B91518: + { + Type: 'AWS::IAM::Policy', + Properties: + { + Groups: [{ Ref: 'MyGroupCBA54B1B' }], + PolicyDocument: + { + Statement: [{ Action: '*', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17', + }, + PolicyName: 'Policy23B91518', + Roles: [{ Ref: 'MyRoleF48FFE04' }], + Users: [{ Ref: 'MyUserDC45028B' }], + }, + }, + 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('idempotent if an imported principal (user/group/role) is attached twice', () => { + const p = new Policy(stack, 'Policy'); + p.addStatements(new PolicyStatement({ resources: ['*'], actions: ['*'] })); + + const user = new User(stack, 'MyUser'); + const importedUser = User.fromUserArn(stack, 'MyImportedUser', user.userArn); + p.attachToUser(user); + p.attachToUser(importedUser); + + const group = new Group(stack, 'MyGroup'); + const importedGroup = Group.fromGroupArn(stack, 'MyImportedGroup', group.groupArn); + p.attachToGroup(group); + p.attachToGroup(importedGroup); + + const role = new Role(stack, 'MyRole', { + assumedBy: new ServicePrincipal('test.service'), + }); + const importedRole = Role.fromRoleArn(stack, 'MyImportedRole', role.roleArn); + p.attachToRole(role); + p.attachToRole(importedRole); + + Template.fromStack(stack).templateMatches({ + Resources: + { + Policy23B91518: + { + Type: 'AWS::IAM::Policy', + Properties: + { + Groups: [{ Ref: 'MyGroupCBA54B1B' }], + PolicyDocument: + { + Statement: [{ Action: '*', Effect: 'Allow', Resource: '*' }], + Version: '2012-10-17', + }, + PolicyName: 'Policy23B91518', + Roles: [{ Ref: 'MyRoleF48FFE04' }], + Users: [{ Ref: 'MyUserDC45028B' }], + }, + }, + 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('fails if "forced" policy is not attached to a principal', () => { new Policy(stack, 'MyPolicy', { force: true }); expect(() => app.synth()).toThrow(/attached to at least one principal: user, group or role/);