From d3df40ff89c70b9243ec175747eb398368067095 Mon Sep 17 00:00:00 2001 From: Tietew Date: Wed, 8 Feb 2023 10:20:28 +0900 Subject: [PATCH] feat(iam): implement IGrantable to Policy and ManagedPolicy (#22712) Fixes #10308 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-iam/README.md | 2 +- .../@aws-cdk/aws-iam/lib/managed-policy.ts | 34 +++++- packages/@aws-cdk/aws-iam/lib/policy.ts | 34 +++++- packages/@aws-cdk/aws-iam/package.json | 1 + ...efaultTestDeployAssert27007DC6.assets.json | 19 +++ ...aultTestDeployAssert27007DC6.template.json | 36 ++++++ .../aws-cdk-iam-managed-policy.assets.json | 6 +- .../aws-cdk-iam-managed-policy.template.json | 52 ++++++++ .../integ.managed-policy.js.snapshot/cdk.out | 2 +- .../integ.json | 12 +- .../manifest.json | 57 ++++++++- .../tree.json | 112 +++++++++++++++++- .../aws-iam/test/integ.managed-policy.ts | 11 +- ...efaultTestDeployAssert274BB918.assets.json | 19 +++ ...aultTestDeployAssert274BB918.template.json | 36 ++++++ .../aws-cdk-iam-policy.assets.json | 6 +- .../aws-cdk-iam-policy.template.json | 52 ++++++++ .../test/integ.policy.js.snapshot/cdk.out | 2 +- .../test/integ.policy.js.snapshot/integ.json | 12 +- .../integ.policy.js.snapshot/manifest.json | 57 ++++++++- .../test/integ.policy.js.snapshot/tree.json | 112 +++++++++++++++++- .../@aws-cdk/aws-iam/test/integ.policy.ts | 11 +- .../aws-iam/test/managed-policy.test.ts | 81 ++++++++++++- packages/@aws-cdk/aws-iam/test/policy.test.ts | 81 ++++++++++++- 24 files changed, 805 insertions(+), 42 deletions(-) create mode 100644 packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json create mode 100644 packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json create mode 100644 packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json create mode 100644 packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.template.json diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index f300378bffc38..0b5f20cea3758 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -60,7 +60,7 @@ declare const table: dynamodb.Table; table.grant(fn, 'dynamodb:PutItem'); ``` -The `grant*` methods accept an `IGrantable` object. This interface is implemented by IAM principal resources (groups, users and roles) and resources that assume a role such as a Lambda function, EC2 instance or a Codebuild project. +The `grant*` methods accept an `IGrantable` object. This interface is implemented by IAM principal resources (groups, users and roles), policies, managed policies and resources that assume a role such as a Lambda function, EC2 instance or a Codebuild project. You can find which `grant*` methods exist for a resource in the [AWS CDK API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html). diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 0b6285cd56323..a2f3547e35e82 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -5,6 +5,7 @@ import { IGroup } from './group'; import { CfnManagedPolicy } from './iam.generated'; import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; +import { AddToPrincipalPolicyResult, IGrantable, IPrincipal, PrincipalPolicyFragment } from './principals'; import { undefinedIfEmpty } from './private/util'; import { IRole } from './role'; import { IUser } from './user'; @@ -100,7 +101,7 @@ export interface ManagedPolicyProps { * Managed policy * */ -export class ManagedPolicy extends Resource implements IManagedPolicy { +export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantable { /** * Import a customer managed policy from the managedPolicyName. * @@ -202,6 +203,8 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { */ public readonly path: string; + public readonly grantPrincipal: IPrincipal; + private readonly roles = new Array(); private readonly users = new Array(); private readonly groups = new Array(); @@ -263,6 +266,8 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { props.statements.forEach(p => this.addStatements(p)); } + this.grantPrincipal = new ManagedPolicyGrantPrincipal(this); + this.node.addValidation({ validate: () => this.validateManagedPolicy() }); } @@ -316,3 +321,30 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { return result; } } + +class ManagedPolicyGrantPrincipal implements IPrincipal { + public readonly assumeRoleAction = 'sts:AssumeRole'; + public readonly grantPrincipal: IPrincipal; + public readonly principalAccount?: string; + + constructor(private _managedPolicy: ManagedPolicy) { + this.grantPrincipal = this; + this.principalAccount = _managedPolicy.env.account; + } + + public get policyFragment(): PrincipalPolicyFragment { + // This property is referenced to add policy statements as a resource-based policy. + // We should fail because a managed policy cannot be used as a principal of a policy document. + // cf. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#Principal_specifying + throw new Error(`Cannot use a ManagedPolicy '${this._managedPolicy.node.path}' as the 'Principal' or 'NotPrincipal' in an IAM Policy`); + } + + public addToPolicy(statement: PolicyStatement): boolean { + return this.addToPrincipalPolicy(statement).statementAdded; + } + + public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { + this._managedPolicy.addStatements(statement); + return { statementAdded: true, policyDependable: this._managedPolicy }; + } +} diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index f46afbc151ec3..f881df8661ebe 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -4,6 +4,7 @@ import { IGroup } from './group'; import { CfnPolicy } from './iam.generated'; import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; +import { AddToPrincipalPolicyResult, IGrantable, IPrincipal, PrincipalPolicyFragment } from './principals'; import { generatePolicyName, undefinedIfEmpty } from './private/util'; import { IRole } from './role'; import { IUser } from './user'; @@ -100,7 +101,7 @@ export interface PolicyProps { * Policies](http://docs.aws.amazon.com/IAM/latest/UserGuide/policies_overview.html) * in the IAM User Guide guide. */ -export class Policy extends Resource implements IPolicy { +export class Policy extends Resource implements IPolicy, IGrantable { /** * Import a policy in this app based on its name @@ -118,6 +119,8 @@ export class Policy extends Resource implements IPolicy { */ public readonly document = new PolicyDocument(); + public readonly grantPrincipal: IPrincipal; + private readonly _policyName: string; private readonly roles = new Array(); private readonly users = new Array(); @@ -178,6 +181,8 @@ export class Policy extends Resource implements IPolicy { props.statements.forEach(p => this.addStatements(p)); } + this.grantPrincipal = new PolicyGrantPrincipal(this); + this.node.addValidation({ validate: () => this.validatePolicy() }); } @@ -260,3 +265,30 @@ export class Policy extends Resource implements IPolicy { return this.groups.length + this.users.length + this.roles.length > 0; } } + +class PolicyGrantPrincipal implements IPrincipal { + public readonly assumeRoleAction = 'sts:AssumeRole'; + public readonly grantPrincipal: IPrincipal; + public readonly principalAccount?: string; + + constructor(private _policy: Policy) { + this.grantPrincipal = this; + this.principalAccount = _policy.env.account; + } + + public get policyFragment(): PrincipalPolicyFragment { + // This property is referenced to add policy statements as a resource-based policy. + // We should fail because a policy cannot be used as a principal of a policy document. + // cf. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#Principal_specifying + throw new Error(`Cannot use a Policy '${this._policy.node.path}' as the 'Principal' or 'NotPrincipal' in an IAM Policy`); + } + + public addToPolicy(statement: PolicyStatement): boolean { + return this.addToPrincipalPolicy(statement).statementAdded; + } + + public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { + this._policy.addStatements(statement); + return { statementAdded: true, policyDependable: this._policy }; + } +} diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index f42f7619e20d2..3372f6f53fb5a 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -82,6 +82,7 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json new file mode 100644 index 0000000000000..f89489c6f3c67 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json index a596a54bf5272..01d6e9e7b41fe 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "368422c5e22c8b7707ddebb8f45bea20bd1c541adb0778d8e86c2e1854900523": { + "df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33": { "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": "368422c5e22c8b7707ddebb8f45bea20bd1c541adb0778d8e86c2e1854900523.json", + "objectKey": "df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json index f817978f06600..0ed3d9e61a60d 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/aws-cdk-iam-managed-policy.template.json @@ -31,6 +31,16 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -54,6 +64,16 @@ "Action": "lambda:InvokeFunction", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "iam:*", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -61,6 +81,38 @@ "Description": "", "Path": "/" } + }, + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/integ.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/integ.json index 7b99bf8392eaf..93febf67f8ef7 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.managed-policy": { + "ManagedPolicyInteg/DefaultTest": { "stacks": [ "aws-cdk-iam-managed-policy" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "ManagedPolicyInteg/DefaultTest/DeployAssert", + "assertionStackName": "ManagedPolicyIntegDefaultTestDeployAssert27007DC6" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json index a6533c2f27703..e3d71b2ae9d91 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "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}/368422c5e22c8b7707ddebb8f45bea20bd1c541adb0778d8e86c2e1854900523.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/df45fa697f19036987d5bfe10fdcfba6de6d5d93be8e406edfc43fcc13fedc33.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,6 +57,12 @@ "data": "TwoManagedPolicy7E701864" } ], + "/aws-cdk-iam-managed-policy/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], "/aws-cdk-iam-managed-policy/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -71,6 +77,53 @@ ] }, "displayName": "aws-cdk-iam-managed-policy" + }, + "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "ManagedPolicyIntegDefaultTestDeployAssert27007DC6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.template.json", + "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}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "ManagedPolicyIntegDefaultTestDeployAssert27007DC6.assets" + ], + "metadata": { + "/ManagedPolicyInteg/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/ManagedPolicyInteg/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "ManagedPolicyInteg/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/tree.json b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/tree.json index ca62fedfdf3ed..55254491202f6 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.js.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "aws-cdk-iam-managed-policy": { @@ -72,6 +72,16 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -113,6 +123,16 @@ "Action": "lambda:InvokeFunction", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "iam:*", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -131,17 +151,103 @@ "fqn": "@aws-cdk/aws-iam.ManagedPolicy", "version": "0.0.0" } + }, + "Role": { + "id": "Role", + "path": "aws-cdk-iam-managed-policy/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-managed-policy/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" + } + }, + "ManagedPolicyInteg": { + "id": "ManagedPolicyInteg", + "path": "ManagedPolicyInteg", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "ManagedPolicyInteg/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "ManagedPolicyInteg/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "ManagedPolicyInteg/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts index a250a0290d72c..26496c79e4b38 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.managed-policy.ts @@ -1,5 +1,6 @@ import { App, Stack } from '@aws-cdk/core'; -import { ManagedPolicy, PolicyStatement } from '../lib'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { AccountRootPrincipal, Grant, ManagedPolicy, PolicyStatement, Role } from '../lib'; import { User } from '../lib/user'; const app = new App(); @@ -23,4 +24,10 @@ user.addManagedPolicy(policy2); const policy3 = ManagedPolicy.fromAwsManagedPolicyName('SecurityAudit'); user.addManagedPolicy(policy3); -app.synth(); +const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); +role.grantAssumeRole(policy.grantPrincipal); +Grant.addToPrincipal({ actions: ['iam:*'], resourceArns: [role.roleArn], grantee: policy2 }); + +new IntegTest(app, 'ManagedPolicyInteg', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json new file mode 100644 index 0000000000000..e3f3a3767e2cc --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "PolicyIntegDefaultTestDeployAssert274BB918.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.template.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json index c36fdbef2e8c6..f758663a791a6 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "ba8f70654832696e6df0cd04b1ceb498833915cdb77e0b4e2749036c59851533": { + "d898a04332095cb0948a67a0182d64a7d0604bb19454a2ce9dcd09153e09bb59": { "source": { "path": "aws-cdk-iam-policy.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "ba8f70654832696e6df0cd04b1ceb498833915cdb77e0b4e2749036c59851533.json", + "objectKey": "d898a04332095cb0948a67a0182d64a7d0604bb19454a2ce9dcd09153e09bb59.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json index fe198c4b4b5e2..39d726b0bea03 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json @@ -12,6 +12,16 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -33,6 +43,16 @@ "Action": "lambda:InvokeFunction", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "iam:*", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -44,6 +64,38 @@ } ] } + }, + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/cdk.out b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/integ.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/integ.json index 4ea4d599d25d3..654d941e4fd37 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.policy": { + "PolicyInteg/DefaultTest": { "stacks": [ "aws-cdk-iam-policy" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "PolicyInteg/DefaultTest/DeployAssert", + "assertionStackName": "PolicyIntegDefaultTestDeployAssert274BB918" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/manifest.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/manifest.json index c58a44ec9d248..ad07206570a75 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "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}/ba8f70654832696e6df0cd04b1ceb498833915cdb77e0b4e2749036c59851533.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d898a04332095cb0948a67a0182d64a7d0604bb19454a2ce9dcd09153e09bb59.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,6 +57,12 @@ "data": "GoodbyePolicy739B8974" } ], + "/aws-cdk-iam-policy/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], "/aws-cdk-iam-policy/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -71,6 +77,53 @@ ] }, "displayName": "aws-cdk-iam-policy" + }, + "PolicyIntegDefaultTestDeployAssert274BB918.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PolicyIntegDefaultTestDeployAssert274BB918.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PolicyIntegDefaultTestDeployAssert274BB918": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PolicyIntegDefaultTestDeployAssert274BB918.template.json", + "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}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PolicyIntegDefaultTestDeployAssert274BB918.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PolicyIntegDefaultTestDeployAssert274BB918.assets" + ], + "metadata": { + "/PolicyInteg/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PolicyInteg/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PolicyInteg/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/tree.json b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/tree.json index 44f160e810b92..5bc1ca1cb8e4d 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.js.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "aws-cdk-iam-policy": { @@ -54,6 +54,16 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -93,6 +103,16 @@ "Action": "lambda:InvokeFunction", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "iam:*", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } } ], "Version": "2012-10-17" @@ -115,17 +135,103 @@ "fqn": "@aws-cdk/aws-iam.Policy", "version": "0.0.0" } + }, + "Role": { + "id": "Role", + "path": "aws-cdk-iam-policy/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-policy/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" + } + }, + "PolicyInteg": { + "id": "PolicyInteg", + "path": "PolicyInteg", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "PolicyInteg/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "PolicyInteg/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "PolicyInteg/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.140" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.ts b/packages/@aws-cdk/aws-iam/test/integ.policy.ts index ecb40dc55eb4e..058390178413d 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.ts @@ -1,5 +1,6 @@ import { App, Stack } from '@aws-cdk/core'; -import { Policy, PolicyStatement } from '../lib'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { AccountRootPrincipal, Grant, Policy, PolicyStatement, Role } from '../lib'; import { User } from '../lib/user'; const app = new App(); @@ -16,4 +17,10 @@ const policy2 = new Policy(stack, 'GoodbyePolicy'); policy2.addStatements(new PolicyStatement({ resources: ['*'], actions: ['lambda:InvokeFunction'] })); policy2.attachToUser(user); -app.synth(); +const role = new Role(stack, 'Role', { assumedBy: new AccountRootPrincipal() }); +role.grantAssumeRole(policy.grantPrincipal); +Grant.addToPrincipal({ actions: ['iam:*'], resourceArns: [role.roleArn], grantee: policy2 }); + +new IntegTest(app, 'PolicyInteg', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts b/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts index 9c12f248e6de8..8ea91b509a6ee 100644 --- a/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts @@ -1,6 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; -import { Group, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User, Effect } from '../lib'; +import { AddToPrincipalPolicyResult, Grant, Group, IResourceWithPolicy, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User, Effect } from '../lib'; describe('managed policy', () => { let app: cdk.App; @@ -614,6 +614,85 @@ describe('managed policy', () => { }); }); + test('Policies can be granted principal permissions', () => { + const mp = new ManagedPolicy(stack, 'Policy', { + managedPolicyName: 'MyManagedPolicyName', + }); + Grant.addToPrincipal({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'] }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::ManagedPolicy', { + ManagedPolicyName: 'MyManagedPolicyName', + PolicyDocument: { + Statement: [ + { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + ], + Version: '2012-10-17', + }, + Path: '/', + Description: '', + }); + }); + + test('addPrincipalOrResource() correctly grants Policies permissions', () => { + const mp = new ManagedPolicy(stack, 'Policy', { + managedPolicyName: 'MyManagedPolicyName', + }); + + class DummyResource extends cdk.Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy'); + Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'], resource }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::ManagedPolicy', { + ManagedPolicyName: 'MyManagedPolicyName', + PolicyDocument: { + Statement: [ + { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + ], + Version: '2012-10-17', + }, + Path: '/', + Description: '', + }); + }); + + test('Policies cannot be granted principal permissions across accounts', () => { + const mp = new ManagedPolicy(stack, 'Policy', { + managedPolicyName: 'MyManagedPolicyName', + }); + + class DummyResource extends cdk.Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy', { account: '5678' }); + + expect(() => { + Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'], resource }); + }).toThrow(/Cannot use a ManagedPolicy 'MyStack\/Policy'/); + }); + + test('Policies cannot be granted resource permissions', () => { + const mp = new ManagedPolicy(stack, 'Policy', { + managedPolicyName: 'MyManagedPolicyName', + }); + + class DummyResource extends cdk.Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy'); + + expect(() => { + Grant.addToPrincipalAndResource({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'], resource }); + }).toThrow(/Cannot use a ManagedPolicy 'MyStack\/Policy'/); + }); + test('prevent creation when customizeRoles is configured', () => { // GIVEN const otherStack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-iam/test/policy.test.ts b/packages/@aws-cdk/aws-iam/test/policy.test.ts index ea40450756935..8bc43abbc819b 100644 --- a/packages/@aws-cdk/aws-iam/test/policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy.test.ts @@ -1,6 +1,6 @@ import { Template } from '@aws-cdk/assertions'; -import { App, CfnResource, Stack } from '@aws-cdk/core'; -import { AnyPrincipal, CfnPolicy, Group, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; +import { App, CfnResource, Resource, Stack } from '@aws-cdk/core'; +import { AddToPrincipalPolicyResult, AnyPrincipal, CfnPolicy, Grant, Group, IResourceWithPolicy, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; /* eslint-disable quote-props */ @@ -440,6 +440,83 @@ describe('IAM policy', () => { expect(() => app.synth()).toThrow(/A PolicyStatement used in an identity-based policy cannot specify any IAM principals/); }); + + test('Policies can be granted principal permissions', () => { + const pol = new Policy(stack, 'Policy', { + policyName: 'MyPolicyName', + }); + Grant.addToPrincipal({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'] }); + pol.attachToUser(new User(stack, 'User')); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyPolicyName', + PolicyDocument: { + Statement: [ + { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('addPrincipalOrResource() correctly grants Policies permissions', () => { + const pol = new Policy(stack, 'Policy', { + policyName: 'MyPolicyName', + }); + pol.attachToUser(new User(stack, 'User')); + + class DummyResource extends Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy'); + Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: pol, resource, resourceArns: ['*'] }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyPolicyName', + PolicyDocument: { + Statement: [ + { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('Policies cannot be granted principal permissions across accounts', () => { + const pol = new Policy(stack, 'Policy', { + policyName: 'MyPolicyName', + }); + + class DummyResource extends Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy', { account: '5678' }); + + expect(() => { + Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'], resource }); + }).toThrow(/Cannot use a Policy 'MyStack\/Policy'/); + }); + + test('Policies cannot be granted resource permissions', () => { + const pol = new Policy(stack, 'Policy', { + policyName: 'MyPolicyName', + }); + + class DummyResource extends Resource implements IResourceWithPolicy { + addToResourcePolicy(_statement: PolicyStatement): AddToPrincipalPolicyResult { + throw new Error('should not be called.'); + } + }; + const resource = new DummyResource(stack, 'Dummy'); + + expect(() => { + Grant.addToPrincipalAndResource({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'], resource }); + }).toThrow(/Cannot use a Policy 'MyStack\/Policy'/); + }); }); function createPolicyWithLogicalId(stack: Stack, logicalId: string): void {