From d1f648fac8a4963e67706ad0c48cfc0c75ebdde8 Mon Sep 17 00:00:00 2001 From: Tietew Date: Fri, 17 Jan 2025 17:42:08 +0900 Subject: [PATCH 1/3] implement policyFragment like Group --- ...efaultTestDeployAssert27007DC6.assets.json | 2 +- .../aws-cdk-iam-managed-policy.assets.json | 6 +- .../aws-cdk-iam-managed-policy.template.json | 76 +++++++ .../integ.managed-policy.js.snapshot/cdk.out | 2 +- .../integ.json | 2 +- .../manifest.json | 25 ++- .../tree.json | 197 ++++++++++++++---- .../test/aws-iam/test/integ.managed-policy.ts | 9 + ...efaultTestDeployAssert274BB918.assets.json | 2 +- .../aws-cdk-iam-policy.assets.json | 6 +- .../aws-cdk-iam-policy.template.json | 76 +++++++ .../test/integ.policy.js.snapshot/cdk.out | 2 +- .../test/integ.policy.js.snapshot/integ.json | 2 +- .../integ.policy.js.snapshot/manifest.json | 16 +- .../test/integ.policy.js.snapshot/tree.json | 121 ++++++++++- .../test/aws-iam/test/integ.policy.ts | 9 + .../aws-cdk-lib/aws-iam/lib/managed-policy.ts | 53 ++--- .../aws-iam/lib/policy-statement.ts | 10 +- packages/aws-cdk-lib/aws-iam/lib/policy.ts | 56 ++--- .../aws-iam/test/managed-policy.test.ts | 82 +++++--- .../aws-cdk-lib/aws-iam/test/policy.test.ts | 85 +++++--- packages/aws-cdk-lib/awslint.json | 3 +- 22 files changed, 651 insertions(+), 191 deletions(-) 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 7bd221a9030e9..5f6ea2e6028f0 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": "36.0.0", + "version": "39.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 a8d7c77ea7ee9..ffaef2735fe2b 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": "36.0.0", + "version": "39.0.0", "files": { - "26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120": { + "3ccc26f333f67ff200e6308e54eb4b3614c564b5741602fb95bfc05b458430b6": { "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": "26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120.json", + "objectKey": "3ccc26f333f67ff200e6308e54eb4b3614c564b5741602fb95bfc05b458430b6.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 9ee516ff18224..600ca0a84204e 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 @@ -44,6 +44,32 @@ "Arn" ] } + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + ":*" + ] + ] + } + ] } ], "Version": "2012-10-17" @@ -118,6 +144,56 @@ "Version": "2012-10-17" } } + }, + "FunctionServiceRole675BB04A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Function76856677": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "export const handler = async () => null" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "Runtime": "nodejs18.x" + }, + "DependsOn": [ + "FunctionServiceRole675BB04A" + ] } }, "Parameters": { 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 1f0068d32659a..91e1a8b9901d5 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":"36.0.0"} \ No newline at end of file +{"version":"39.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 16f5dbc2709ee..b94ba88e352cb 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": "36.0.0", + "version": "39.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 6f96e4a674236..d79cfcebfd202 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": "36.0.0", + "version": "39.0.0", "artifacts": { "aws-cdk-iam-managed-policy.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,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}/26f1836028ead2829dce663ff9f4b0c71fd9db149cb19a6c54ed9128e3e09120.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3ccc26f333f67ff200e6308e54eb4b3614c564b5741602fb95bfc05b458430b6.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -58,25 +58,28 @@ "data": "Role1ABCC5F0" } ], - "/aws-cdk-iam-managed-policy/BootstrapVersion": [ + "/aws-cdk-iam-managed-policy/Function/ServiceRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "BootstrapVersion" + "data": "FunctionServiceRole675BB04A" } ], - "/aws-cdk-iam-managed-policy/CheckBootstrapVersion": [ + "/aws-cdk-iam-managed-policy/Function/Resource": [ { "type": "aws:cdk:logicalId", - "data": "CheckBootstrapVersion" + "data": "Function76856677" + } + ], + "/aws-cdk-iam-managed-policy/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" } ], - "MyUserDefaultPolicy7B897426": [ + "/aws-cdk-iam-managed-policy/CheckBootstrapVersion": [ { "type": "aws:cdk:logicalId", - "data": "MyUserDefaultPolicy7B897426", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] + "data": "CheckBootstrapVersion" } ] }, 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 08230dd699706..e18eb3b312840 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,14 +38,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.CfnUser", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.User", + "version": "0.0.0" } }, "OneManagedPolicy": { @@ -56,8 +56,8 @@ "id": "ImportedOneManagedPolicy", "path": "aws-cdk-iam-managed-policy/OneManagedPolicy/ImportedOneManagedPolicy", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } }, "Resource": { @@ -85,6 +85,32 @@ "Arn" ] } + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + ":*" + ] + ] + } + ] } ], "Version": "2012-10-17" @@ -102,14 +128,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.CfnManagedPolicy", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.ManagedPolicy", + "version": "0.0.0" } }, "TwoManagedPolicy": { @@ -120,8 +146,8 @@ "id": "ImportedTwoManagedPolicy", "path": "aws-cdk-iam-managed-policy/TwoManagedPolicy/ImportedTwoManagedPolicy", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } }, "Resource": { @@ -155,14 +181,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.CfnManagedPolicy", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.ManagedPolicy", + "version": "0.0.0" } }, "Role": { @@ -173,8 +199,8 @@ "id": "ImportRole", "path": "aws-cdk-iam-managed-policy/Role/ImportRole", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } }, "Resource": { @@ -213,44 +239,135 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" } }, "ImportedRole": { "id": "ImportedRole", "path": "aws-cdk-iam-managed-policy/ImportedRole", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Function": { + "id": "Function", + "path": "aws-cdk-iam-managed-policy/Function", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-cdk-iam-managed-policy/Function/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-cdk-iam-managed-policy/Function/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-managed-policy/Function/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-managed-policy/Function/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "export const handler = async () => null" + }, + "handler": "index.handler", + "role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "runtime": "nodejs18.x" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" } }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "aws-cdk-iam-managed-policy/BootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "aws-cdk-iam-managed-policy/CheckBootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" } }, "ManagedPolicyInteg": { @@ -266,7 +383,7 @@ "path": "ManagedPolicyInteg/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } }, "DeployAssert": { @@ -277,22 +394,22 @@ "id": "BootstrapVersion", "path": "ManagedPolicyInteg/DefaultTest/DeployAssert/BootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "ManagedPolicyInteg/DefaultTest/DeployAssert/CheckBootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" } } }, @@ -312,13 +429,13 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" } } } \ No newline at end of file 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 4633262a34cb7..afbb553b6650d 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 @@ -1,6 +1,7 @@ import { App, Stack } from 'aws-cdk-lib'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { AccountRootPrincipal, Grant, ManagedPolicy, PolicyStatement, Role, User } from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; const app = new App(); @@ -34,6 +35,14 @@ policy.attachToRole(role); const importedRole = Role.fromRoleArn(stack, 'ImportedRole', role.roleArn); policy.attachToRole(importedRole); +// Can be passed to grantInvoke, see https://github.com/aws/aws-cdk/issues/32980 +const func = new lambda.Function(stack, 'Function', { + runtime: lambda.Runtime.NODEJS_LATEST, + handler: 'index.handler', + code: lambda.Code.fromInline('export const handler = async () => null'), +}); +func.grantInvoke(policy); + new IntegTest(app, 'ManagedPolicyInteg', { testCases: [stack], }); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json index 3466d1580639c..f1cd387c17b29 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/PolicyIntegDefaultTestDeployAssert274BB918.assets.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "39.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json index 1eb3b6a6cab5f..f80bb5ff4e10f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.assets.json @@ -1,7 +1,7 @@ { - "version": "36.0.0", + "version": "39.0.0", "files": { - "372105a2bc65630c0068c39309addd787e89ace7da989e014b511bd9d462be0a": { + "43807a80b506916725744ad3a34ae380a231eaed5426618d67c280a960ffdf49": { "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": "372105a2bc65630c0068c39309addd787e89ace7da989e014b511bd9d462be0a.json", + "objectKey": "43807a80b506916725744ad3a34ae380a231eaed5426618d67c280a960ffdf49.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.policy.js.snapshot/aws-cdk-iam-policy.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json index 1621257587ce3..7879722db6e61 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/aws-cdk-iam-policy.template.json @@ -38,6 +38,32 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + ":*" + ] + ] + } + ] } ], "Version": "2012-10-17" @@ -112,6 +138,56 @@ "Version": "2012-10-17" } } + }, + "FunctionServiceRole675BB04A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Function76856677": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "export const handler = async () => null" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "Runtime": "nodejs18.x" + }, + "DependsOn": [ + "FunctionServiceRole675BB04A" + ] } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/cdk.out index 1f0068d32659a..91e1a8b9901d5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"36.0.0"} \ No newline at end of file +{"version":"39.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/integ.json index 3c0a370f43539..14bacc19bed71 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "39.0.0", "testCases": { "PolicyInteg/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/manifest.json index c3dd1773186d1..e462227a02dc7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "39.0.0", "artifacts": { "aws-cdk-iam-policy.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,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}/372105a2bc65630c0068c39309addd787e89ace7da989e014b511bd9d462be0a.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/43807a80b506916725744ad3a34ae380a231eaed5426618d67c280a960ffdf49.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -64,6 +64,18 @@ "data": "Role1ABCC5F0" } ], + "/aws-cdk-iam-policy/Function/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FunctionServiceRole675BB04A" + } + ], + "/aws-cdk-iam-policy/Function/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Function76856677" + } + ], "/aws-cdk-iam-policy/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/tree.json index 0fbd0593a5d5d..bffbbd85b41da 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.js.snapshot/tree.json @@ -90,6 +90,32 @@ "Action": "sqs:SendMessage", "Effect": "Allow", "Resource": "*" + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + ":*" + ] + ] + } + ] } ], "Version": "2012-10-17" @@ -220,6 +246,97 @@ "version": "0.0.0" } }, + "Function": { + "id": "Function", + "path": "aws-cdk-iam-policy/Function", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-cdk-iam-policy/Function/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-cdk-iam-policy/Function/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-policy/Function/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-iam-policy/Function/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "export const handler = async () => null" + }, + "handler": "index.handler", + "role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "runtime": "nodejs18.x" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "aws-cdk-iam-policy/BootstrapVersion", @@ -255,7 +372,7 @@ "path": "PolicyInteg/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } }, "DeployAssert": { @@ -301,7 +418,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.ts index 68974890aded5..93a6ec819788e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-iam/test/integ.policy.ts @@ -1,6 +1,7 @@ import { App, Stack } from 'aws-cdk-lib'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { AccountRootPrincipal, Grant, Policy, PolicyStatement, Role, User } from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; const app = new App(); @@ -21,6 +22,14 @@ role.grantAssumeRole(user); Grant.addToPrincipal({ actions: ['iam:*'], resourceArns: [role.roleArn], grantee: policy2 }); +// Can be passed to grantInvoke, see https://github.com/aws/aws-cdk/issues/32980 +const func = new lambda.Function(stack, 'Function', { + runtime: lambda.Runtime.NODEJS_LATEST, + handler: 'index.handler', + code: lambda.Code.fromInline('export const handler = async () => null'), +}); +func.grantInvoke(policy); + new IntegTest(app, 'PolicyInteg', { testCases: [stack], }); 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 4c335cf38b489..326413f31624a 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts @@ -3,7 +3,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 { AddToPrincipalPolicyResult, ArnPrincipal, IPrincipal, PrincipalPolicyFragment } from './principals'; import { undefinedIfEmpty } from './private/util'; import { IRole } from './role'; import { IUser } from './user'; @@ -101,7 +101,7 @@ export interface ManagedPolicyProps { * Managed policy * */ -export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantable { +export class ManagedPolicy extends Resource implements IManagedPolicy, IPrincipal { /** * Import a customer managed policy from the managedPolicyName. * @@ -203,7 +203,13 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl */ public readonly path: string; - public readonly grantPrincipal: IPrincipal; + public readonly grantPrincipal: IPrincipal = this; + public readonly principalAccount: string | undefined = this.env.account; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + + public get policyFragment(): PrincipalPolicyFragment { + return new ArnPrincipal(this.managedPolicyArn).policyFragment; + } private readonly roles = new Array(); private readonly users = new Array(); @@ -266,8 +272,6 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl props.statements.forEach(p => this.addStatements(p)); } - this.grantPrincipal = new ManagedPolicyGrantPrincipal(this); - this.node.addValidation({ validate: () => this.validateManagedPolicy() }); } @@ -302,6 +306,18 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl this.groups.push(group); } + /** + * Adds an IAM statement to the policy document. + */ + public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { + this.addStatements(statement); + return { statementAdded: true, policyDependable: this }; + } + + public addToPolicy(statement: PolicyStatement): boolean { + return this.addToPrincipalPolicy(statement).statementAdded; + } + private validateManagedPolicy(): string[] { const result = new Array(); @@ -321,30 +337,3 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl 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-lib/aws-iam/lib/policy-statement.ts b/packages/aws-cdk-lib/aws-iam/lib/policy-statement.ts index 0c7cdfd0e031a..cb25c3c880e04 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/policy-statement.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/policy-statement.ts @@ -1,5 +1,7 @@ import { IConstruct } from 'constructs'; import { Group } from './group'; +import { ManagedPolicy } from './managed-policy'; +import { Policy } from './policy'; import { AccountPrincipal, AccountRootPrincipal, AnyPrincipal, ArnPrincipal, CanonicalUserPrincipal, FederatedPrincipal, IPrincipal, PrincipalBase, PrincipalPolicyFragment, ServicePrincipal, ServicePrincipalOpts, validateConditionObject, @@ -239,7 +241,13 @@ export class PolicyStatement { private validatePolicyPrincipal(principal: IPrincipal) { if (principal instanceof Group) { - throw new Error('Cannot use an IAM Group as the \'Principal\' or \'NotPrincipal\' in an IAM Policy'); + throw new Error(`Cannot use an IAM Group '${principal.node.path}' as the \'Principal\' or \'NotPrincipal\' in an IAM Policy`); + } + if (principal instanceof Policy) { + throw new Error(`Cannot use a Policy '${principal.node.path}' as the \'Principal\' or \'NotPrincipal\' in an IAM Policy`); + } + if (principal instanceof ManagedPolicy) { + throw new Error(`Cannot use a ManagedPolicy '${principal.node.path}' as the \'Principal\' or \'NotPrincipal\' in an IAM Policy`); } } diff --git a/packages/aws-cdk-lib/aws-iam/lib/policy.ts b/packages/aws-cdk-lib/aws-iam/lib/policy.ts index a8dd67f5fecbf..370760ed2f0d7 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/policy.ts @@ -3,11 +3,11 @@ 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 { AddToPrincipalPolicyResult, ArnPrincipal, IPrincipal, PrincipalPolicyFragment } from './principals'; import { generatePolicyName, undefinedIfEmpty } from './private/util'; import { IRole } from './role'; import { IUser } from './user'; -import { IResource, Lazy, Resource } from '../../core'; +import { IResource, Lazy, Resource, Stack } from '../../core'; /** * Represents an IAM Policy @@ -102,7 +102,7 @@ export interface PolicyProps { * [Overview of IAM 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, IGrantable { +export class Policy extends Resource implements IPolicy, IPrincipal { /** * Import a policy in this app based on its name @@ -120,7 +120,14 @@ export class Policy extends Resource implements IPolicy, IGrantable { */ public readonly document = new PolicyDocument(); - public readonly grantPrincipal: IPrincipal; + public readonly grantPrincipal: IPrincipal = this; + public readonly principalAccount: string | undefined = this.env.account; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + + public get policyFragment(): PrincipalPolicyFragment { + const dummyArn = Stack.of(this).formatArn({ service: 'iam', resource: 'dummy-policy', resourceName: this.node.path }); + return new ArnPrincipal(dummyArn).policyFragment; + } private readonly _policyName: string; private readonly roles = new Array(); @@ -182,8 +189,6 @@ export class Policy extends Resource implements IPolicy, IGrantable { props.statements.forEach(p => this.addStatements(p)); } - this.grantPrincipal = new PolicyGrantPrincipal(this); - this.node.addValidation({ validate: () => this.validatePolicy() }); } @@ -231,6 +236,18 @@ export class Policy extends Resource implements IPolicy, IGrantable { return this._policyName; } + /** + * Adds an IAM statement to the policy document. + */ + public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { + this.addStatements(statement); + return { statementAdded: true, policyDependable: this }; + } + + public addToPolicy(statement: PolicyStatement): boolean { + return this.addToPrincipalPolicy(statement).statementAdded; + } + private validatePolicy(): string[] { const result = new Array(); @@ -266,30 +283,3 @@ export class Policy extends Resource implements IPolicy, IGrantable { 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-lib/aws-iam/test/managed-policy.test.ts b/packages/aws-cdk-lib/aws-iam/test/managed-policy.test.ts index 540edbf65eff3..26099b4ce978c 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 @@ -1,6 +1,8 @@ -import { Template } from '../../assertions'; +import { Match, Template } from '../../assertions'; +import * as lambda from '../../aws-lambda'; +import * as s3 from '../../aws-s3'; import * as cdk from '../../core'; -import { AddToPrincipalPolicyResult, Grant, Group, IResourceWithPolicy, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User, Effect } from '../lib'; +import { Grant, Group, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User, Effect } from '../lib'; describe('managed policy', () => { let app: cdk.App; @@ -689,24 +691,54 @@ describe('managed policy', () => { }); }); - test('addPrincipalOrResource() correctly grants Policies permissions', () => { + test('Policies can be granted on s3.Bucket', () => { 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 }); + const bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'mybucketname'); + bucket.grantRead(mp); Template.fromStack(stack).hasResourceProperties('AWS::IAM::ManagedPolicy', { ManagedPolicyName: 'MyManagedPolicyName', PolicyDocument: { Statement: [ - { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + { + Action: Match.anyValue(), + Effect: 'Allow', + Resource: [ + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::mybucketname']] }, + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::mybucketname/*']] }, + ], + }, + ], + Version: '2012-10-17', + }, + Path: '/', + Description: '', + }); + }); + + test('Policies can be granted on lambda.Function', () => { + const mp = new ManagedPolicy(stack, 'Policy', { + managedPolicyName: 'MyManagedPolicyName', + }); + + const func = lambda.Function.fromFunctionName(stack, 'Function', 'myfunctionname'); + func.grantInvoke(mp); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::ManagedPolicy', { + ManagedPolicyName: 'MyManagedPolicyName', + PolicyDocument: { + Statement: [ + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: [ + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':lambda:us-east-1:1234:function:myfunctionname']] }, + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':lambda:us-east-1:1234:function:myfunctionname:*']] }, + ], + }, ], Version: '2012-10-17', }, @@ -720,16 +752,11 @@ describe('managed 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' }); + const stack2 = new cdk.Stack(stack, 'CrossAccountStack', { env: { account: '5678' } }); + const bucket = new s3.Bucket(stack2, 'Bucket'); - expect(() => { - Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'], resource }); - }).toThrow(/Cannot use a ManagedPolicy 'MyStack\/Policy'/); + expect(() => bucket.grantRead(mp)) + .toThrow("Cannot use a ManagedPolicy 'MyStack/Policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); test('Policies cannot be granted resource permissions', () => { @@ -737,16 +764,15 @@ describe('managed 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'); + const bucket = new s3.Bucket(stack, 'Bucket'); expect(() => { - Grant.addToPrincipalAndResource({ actions: ['dummy:Action'], grantee: mp, resourceArns: ['*'], resource }); - }).toThrow(/Cannot use a ManagedPolicy 'MyStack\/Policy'/); + bucket.addToResourcePolicy(new PolicyStatement({ + principals: [mp], + actions: ['s3:GetObject'], + resources: ['*'], + })); + }).toThrow("Cannot use a ManagedPolicy 'MyStack/Policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); test('prevent creation when customizeRoles is configured', () => { 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 f70cccef24c7e..b842144392d25 100644 --- a/packages/aws-cdk-lib/aws-iam/test/policy.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/policy.test.ts @@ -1,4 +1,6 @@ -import { Template } from '../../assertions'; +import { Match, Template } from '../../assertions'; +import * as lambda from '../../aws-lambda'; +import * as s3 from '../../aws-s3'; import { App, CfnResource, Resource, Stack } from '../../core'; import { AddToPrincipalPolicyResult, AnyPrincipal, CfnPolicy, Grant, Group, IResourceWithPolicy, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -567,9 +569,9 @@ describe('IAM policy', () => { test('Policies can be granted principal permissions', () => { const pol = new Policy(stack, 'Policy', { policyName: 'MyPolicyName', + users: [new User(stack, 'User')], }); Grant.addToPrincipal({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'] }); - pol.attachToUser(new User(stack, 'User')); Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyPolicyName', @@ -582,25 +584,54 @@ describe('IAM policy', () => { }); }); - test('addPrincipalOrResource() correctly grants Policies permissions', () => { + test('Policies can be granted on s3.Bucket', () => { const pol = new Policy(stack, 'Policy', { policyName: 'MyPolicyName', + users: [new User(stack, 'User')], }); - 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: ['*'] }); + const bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'mybucketname'); + bucket.grantRead(pol); Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyName: 'MyPolicyName', PolicyDocument: { Statement: [ - { Action: 'dummy:Action', Effect: 'Allow', Resource: '*' }, + { + Action: Match.anyValue(), + Effect: 'Allow', + Resource: [ + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::mybucketname']] }, + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::mybucketname/*']] }, + ], + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('Policies can be granted on lambda.Function', () => { + const pol = new Policy(stack, 'Policy', { + policyName: 'MyPolicyName', + users: [new User(stack, 'User')], + }); + + const func = lambda.Function.fromFunctionName(stack, 'Function', 'myfunctionname'); + func.grantInvoke(pol); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyPolicyName', + PolicyDocument: { + Statement: [ + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: [ + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':lambda:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':function:myfunctionname']] }, + { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':lambda:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':function:myfunctionname:*']] }, + ], + }, ], Version: '2012-10-17', }, @@ -610,35 +641,31 @@ describe('IAM policy', () => { test('Policies cannot be granted principal permissions across accounts', () => { const pol = new Policy(stack, 'Policy', { policyName: 'MyPolicyName', + users: [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', { account: '5678' }); + const stack2 = new Stack(stack, 'CrossAccountStack', { env: { account: '5678' } }); + const bucket = new s3.Bucket(stack2, 'Bucket'); - expect(() => { - Grant.addToPrincipalOrResource({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'], resource }); - }).toThrow(/Cannot use a Policy 'MyStack\/Policy'/); + expect(() => bucket.grantRead(pol)) + .toThrow("Cannot use a Policy 'MyStack/Policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); test('Policies cannot be granted resource permissions', () => { const pol = new Policy(stack, 'Policy', { policyName: 'MyPolicyName', + users: [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'); + const bucket = new s3.Bucket(stack, 'Bucket'); expect(() => { - Grant.addToPrincipalAndResource({ actions: ['dummy:Action'], grantee: pol, resourceArns: ['*'], resource }); - }).toThrow(/Cannot use a Policy 'MyStack\/Policy'/); + bucket.addToResourcePolicy(new PolicyStatement({ + principals: [pol], + actions: ['s3:GetObject'], + resources: ['*'], + })); + }).toThrow("Cannot use a Policy 'MyStack/Policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); }); diff --git a/packages/aws-cdk-lib/awslint.json b/packages/aws-cdk-lib/awslint.json index a4c65ade312c5..1ee38a6474ba0 100644 --- a/packages/aws-cdk-lib/awslint.json +++ b/packages/aws-cdk-lib/awslint.json @@ -194,6 +194,7 @@ "from-method:aws-cdk-lib.aws_iam.AccessKey", "from-signature:aws-cdk-lib.aws_iam.Role.fromRoleArn", "from-signature:aws-cdk-lib.aws_iam.Role.fromRoleName", + "attribute-tag:aws-cdk-lib.aws_iam.Policy.policyFragment", "from-method:aws-cdk-lib.aws_lambda.FunctionUrl", "from-signature:aws-cdk-lib.aws_secretsmanager.Secret.fromSecretNameV2.params[2]", "attribute-tag:aws-cdk-lib.aws_apigateway.RequestAuthorizer.authorizerArn", @@ -997,4 +998,4 @@ "events-generic:aws-cdk-lib.aws_appconfig.HostedConfiguration", "events-generic:aws-cdk-lib.aws_appconfig.SourcedConfiguration" ] -} \ No newline at end of file +} From ef5f8fbbff63492ddfc224a5995c066df94cf0fd Mon Sep 17 00:00:00 2001 From: Tietew Date: Fri, 17 Jan 2025 18:13:38 +0900 Subject: [PATCH 2/3] fix test --- packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts b/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts index 09304a2a55a32..235b3fc055983 100644 --- a/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts @@ -210,9 +210,9 @@ describe('IAM policy statement', () => { const policyStatement = new PolicyStatement(); expect(() => policyStatement.addPrincipals(group)) - .toThrow(/Cannot use an IAM Group as the 'Principal' or 'NotPrincipal' in an IAM Policy/); + .toThrow("Cannot use an IAM Group 'Default/groupId' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); expect(() => policyStatement.addNotPrincipals(group)) - .toThrow(/Cannot use an IAM Group as the 'Principal' or 'NotPrincipal' in an IAM Policy/); + .toThrow("Cannot use an IAM Group 'Default/groupId' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); test('throws error when an invalid \'Action\' or \'NotAction\' is added', () => { From 476aa132c6d8dbb76bd8ee022f499eaa6985e9cc Mon Sep 17 00:00:00 2001 From: Tietew Date: Mon, 3 Feb 2025 10:45:03 +0900 Subject: [PATCH 3/3] add tests --- .../aws-iam/test/policy-statement.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts b/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts index d4bcd738e74af..8aed903f5acab 100644 --- a/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts +++ b/packages/aws-cdk-lib/aws-iam/test/policy-statement.test.ts @@ -1,5 +1,5 @@ import { Stack } from '../../core'; -import { AnyPrincipal, Group, PolicyDocument, PolicyStatement, Effect } from '../lib'; +import { AnyPrincipal, Group, PolicyDocument, PolicyStatement, Effect, Policy, ManagedPolicy } from '../lib'; describe('IAM policy statement', () => { describe('from JSON', () => { @@ -209,6 +209,28 @@ describe('IAM policy statement', () => { .toThrow("Cannot use an IAM Group 'Default/groupId' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); }); + test('throws error when Policy is specified for \'Principal\' or \'NotPrincipal\'', () => { + const stack = new Stack(); + const policy = new Policy(stack, 'policy'); + const policyStatement = new PolicyStatement(); + + expect(() => policyStatement.addPrincipals(policy)) + .toThrow("Cannot use a Policy 'Default/policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); + expect(() => policyStatement.addNotPrincipals(policy)) + .toThrow("Cannot use a Policy 'Default/policy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); + }); + + test('throws error when ManagedPolicy is specified for \'Principal\' or \'NotPrincipal\'', () => { + const stack = new Stack(); + const managedPolicy = new ManagedPolicy(stack, 'managedPolicy'); + const policyStatement = new PolicyStatement(); + + expect(() => policyStatement.addPrincipals(managedPolicy)) + .toThrow("Cannot use a ManagedPolicy 'Default/managedPolicy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); + expect(() => policyStatement.addNotPrincipals(managedPolicy)) + .toThrow("Cannot use a ManagedPolicy 'Default/managedPolicy' as the 'Principal' or 'NotPrincipal' in an IAM Policy"); + }); + test('throws error when an invalid \'Action\' or \'NotAction\' is added', () => { const policyStatement = new PolicyStatement(); const invalidAction = 'xyz';