From 2fb8a79d38e4ddd4c5f1de06a0880444a6239d59 Mon Sep 17 00:00:00 2001 From: yuanhaoz Date: Wed, 9 Oct 2024 15:37:06 -0700 Subject: [PATCH] fix(core): fix policy synthesizer logic for precreated roles --- ...cdk-table-with-customized-role.assets.json | 19 ++ ...k-table-with-customized-role.template.json | 83 +++++ ...dk-table-with-customized-role2.assets.json | 19 ++ ...-table-with-customized-role2.template.json | 61 ++++ .../cdk.out | 1 + ...efaultTestDeployAssertD6C925FC.assets.json | 19 ++ ...aultTestDeployAssertD6C925FC.template.json | 36 ++ .../iam-policy-report.json | 90 +++++ .../iam-policy-report.txt | 101 ++++++ .../integ.json | 14 + .../manifest.json | 170 ++++++++++ .../tree.json | 319 ++++++++++++++++++ .../test/integ.table-with-customized-role.ts | 70 ++++ packages/aws-cdk-lib/aws-iam/README.md | 66 ++++ .../lib/helpers-internal/customize-roles.ts | 23 +- packages/aws-cdk-lib/core/lib/resolvable.ts | 41 +++ 16 files changed, 1127 insertions(+), 5 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.txt create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.assets.json new file mode 100644 index 0000000000000..23b584c2e6591 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "70267cb4d71000f9402304c37f8f7f27be51a2639cc15153bf93abab53fc60cd": { + "source": { + "path": "cdk-table-with-customized-role.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "70267cb4d71000f9402304c37f8f7f27be51a2639cc15153bf93abab53fc60cd.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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.template.json new file mode 100644 index 0000000000000..4a512f0150bc8 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role.template.json @@ -0,0 +1,83 @@ +{ + "Resources": { + "TableCD117FA1": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "pk", + "AttributeType": "S" + }, + { + "AttributeName": "gsi-pk", + "AttributeType": "S" + } + ], + "GlobalSecondaryIndexes": [ + { + "IndexName": "gsi", + "KeySchema": [ + { + "AttributeName": "gsi-pk", + "KeyType": "HASH" + } + ], + "Projection": { + "ProjectionType": "ALL" + }, + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + } + ], + "KeySchema": [ + { + "AttributeName": "pk", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.assets.json new file mode 100644 index 0000000000000..05d955ca02cea --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "75d2a3c3208960a28b88f27cd9f94f89a8126de261c69dc422537395c5c29fbe": { + "source": { + "path": "cdk-table-with-customized-role2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "75d2a3c3208960a28b88f27cd9f94f89a8126de261c69dc422537395c5c29fbe.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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.template.json new file mode 100644 index 0000000000000..9b673891b73cb --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk-table-with-customized-role2.template.json @@ -0,0 +1,61 @@ +{ + "Resources": { + "TableCD117FA1": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "pk", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "pk", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk.out new file mode 100644 index 0000000000000..c6e612584e352 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets.json new file mode 100644 index 0000000000000..374f439045d06 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.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-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.json new file mode 100644 index 0000000000000..aadc926f1bee0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.json @@ -0,0 +1,90 @@ +{ + "roles": [ + { + "roleConstructPath": "cdk-table-with-customized-role/Table/ScalingRole", + "roleName": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable", + "assumeRolePolicy": [], + "managedPolicyArns": [], + "managedPolicyStatements": [], + "identityPolicyStatements": [] + }, + { + "roleConstructPath": "cdk-table-with-customized-role/Role", + "roleName": "my-precreated-role-name", + "missing": false, + "assumeRolePolicy": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "dynamodb.amazonaws.com" + } + } + ], + "managedPolicyArns": [], + "managedPolicyStatements": [], + "identityPolicyStatements": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + "Effect": "Allow", + "Resource": [ + "(cdk-table-with-customized-role/Table/Resource.Arn)", + "(cdk-table-with-customized-role/Table/Resource.Arn)/index/*" + ] + } + ] + }, + { + "roleConstructPath": "cdk-table-with-customized-role2/Table/ScalingRole", + "roleName": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable", + "assumeRolePolicy": [], + "managedPolicyArns": [], + "managedPolicyStatements": [], + "identityPolicyStatements": [] + }, + { + "roleConstructPath": "cdk-table-with-customized-role2/Role", + "roleName": "my-precreated-role-name", + "missing": false, + "assumeRolePolicy": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "dynamodb.amazonaws.com" + } + } + ], + "managedPolicyArns": [], + "managedPolicyStatements": [], + "identityPolicyStatements": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + "Effect": "Allow", + "Resource": [ + "(cdk-table-with-customized-role2/Table/Resource.Arn)", + "(NOVALUE)" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.txt b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.txt new file mode 100644 index 0000000000000..30c4bdd499491 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/iam-policy-report.txt @@ -0,0 +1,101 @@ + (cdk-table-with-customized-role/Table/ScalingRole) + +AssumeRole Policy: +NONE + +Managed Policy ARNs: +NONE + +Managed Policies Statements: +NONE + +Identity Policy Statements: +NONE (cdk-table-with-customized-role/Role) + +AssumeRole Policy: +[ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "dynamodb.amazonaws.com" + } + } +] + +Managed Policy ARNs: +NONE + +Managed Policies Statements: +NONE + +Identity Policy Statements: +[ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + "Effect": "Allow", + "Resource": [ + "(cdk-table-with-customized-role/Table/Resource.Arn)", + "(cdk-table-with-customized-role/Table/Resource.Arn)/index/*" + ] + } +] (cdk-table-with-customized-role2/Table/ScalingRole) + +AssumeRole Policy: +NONE + +Managed Policy ARNs: +NONE + +Managed Policies Statements: +NONE + +Identity Policy Statements: +NONE (cdk-table-with-customized-role2/Role) + +AssumeRole Policy: +[ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "dynamodb.amazonaws.com" + } + } +] + +Managed Policy ARNs: +NONE + +Managed Policies Statements: +NONE + +Identity Policy Statements: +[ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + "Effect": "Allow", + "Resource": [ + "(cdk-table-with-customized-role2/Table/Resource.Arn)", + "(NOVALUE)" + ] + } +] \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/integ.json new file mode 100644 index 0000000000000..05d41e8b269ff --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "38.0.1", + "testCases": { + "cdk-dynamodb-customized-role-integ/DefaultTest": { + "stacks": [ + "cdk-table-with-customized-role", + "cdk-table-with-customized-role2" + ], + "diffAssets": true, + "assertionStack": "cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert", + "assertionStackName": "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/manifest.json new file mode 100644 index 0000000000000..66aa906b31622 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/manifest.json @@ -0,0 +1,170 @@ +{ + "version": "38.0.1", + "artifacts": { + "cdk-table-with-customized-role.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdk-table-with-customized-role.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdk-table-with-customized-role": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdk-table-with-customized-role.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "notificationArns": [], + "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}/70267cb4d71000f9402304c37f8f7f27be51a2639cc15153bf93abab53fc60cd.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdk-table-with-customized-role.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": [ + "cdk-table-with-customized-role.assets" + ], + "metadata": { + "/cdk-table-with-customized-role/Table/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableCD117FA1" + } + ], + "/cdk-table-with-customized-role/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-table-with-customized-role/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-table-with-customized-role" + }, + "cdk-table-with-customized-role2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdk-table-with-customized-role2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdk-table-with-customized-role2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdk-table-with-customized-role2.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "notificationArns": [], + "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}/75d2a3c3208960a28b88f27cd9f94f89a8126de261c69dc422537395c5c29fbe.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdk-table-with-customized-role2.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": [ + "cdk-table-with-customized-role2.assets" + ], + "metadata": { + "/cdk-table-with-customized-role2/Table/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TableCD117FA1" + } + ], + "/cdk-table-with-customized-role2/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-table-with-customized-role2/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-table-with-customized-role2" + }, + "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "notificationArns": [], + "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": [ + "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.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": [ + "cdkdynamodbcustomizedroleintegDefaultTestDeployAssertD6C925FC.assets" + ], + "metadata": { + "/cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/tree.json new file mode 100644 index 0000000000000..8cb209f416f3b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.js.snapshot/tree.json @@ -0,0 +1,319 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "cdk-table-with-customized-role": { + "id": "cdk-table-with-customized-role", + "path": "cdk-table-with-customized-role", + "children": { + "Table": { + "id": "Table", + "path": "cdk-table-with-customized-role/Table", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-table-with-customized-role/Table/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "pk", + "attributeType": "S" + }, + { + "attributeName": "gsi-pk", + "attributeType": "S" + } + ], + "globalSecondaryIndexes": [ + { + "indexName": "gsi", + "keySchema": [ + { + "attributeName": "gsi-pk", + "keyType": "HASH" + } + ], + "projection": { + "projectionType": "ALL" + }, + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + ], + "keySchema": [ + { + "attributeName": "pk", + "keyType": "HASH" + } + ], + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ImportScalingRole": { + "id": "ImportScalingRole", + "path": "cdk-table-with-customized-role/Table/ImportScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "cdk-table-with-customized-role/Table/ScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "cdk-table-with-customized-role/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "cdk-table-with-customized-role/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "PrecreatedRoleRole": { + "id": "PrecreatedRoleRole", + "path": "cdk-table-with-customized-role/Role/PrecreatedRoleRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-table-with-customized-role/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-table-with-customized-role/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "PolicySynthesizer": { + "id": "PolicySynthesizer", + "path": "PolicySynthesizer", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "cdk-table-with-customized-role2": { + "id": "cdk-table-with-customized-role2", + "path": "cdk-table-with-customized-role2", + "children": { + "Table": { + "id": "Table", + "path": "cdk-table-with-customized-role2/Table", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-table-with-customized-role2/Table/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "pk", + "attributeType": "S" + } + ], + "keySchema": [ + { + "attributeName": "pk", + "keyType": "HASH" + } + ], + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ImportScalingRole": { + "id": "ImportScalingRole", + "path": "cdk-table-with-customized-role2/Table/ImportScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "cdk-table-with-customized-role2/Table/ScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "cdk-table-with-customized-role2/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "cdk-table-with-customized-role2/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "PrecreatedRoleRole": { + "id": "PrecreatedRoleRole", + "path": "cdk-table-with-customized-role2/Role/PrecreatedRoleRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-table-with-customized-role2/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-table-with-customized-role2/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "cdk-dynamodb-customized-role-integ": { + "id": "cdk-dynamodb-customized-role-integ", + "path": "cdk-dynamodb-customized-role-integ", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-dynamodb-customized-role-integ/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-dynamodb-customized-role-integ/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-dynamodb-customized-role-integ/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "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-dynamodb/test/integ.table-with-customized-role.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.ts new file mode 100644 index 0000000000000..f57a789aac8af --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.table-with-customized-role.ts @@ -0,0 +1,70 @@ +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { App, Stack, StackProps } from 'aws-cdk-lib'; +import { AttributeType, Table } from 'aws-cdk-lib/aws-dynamodb'; +import { Construct } from 'constructs'; +import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; + +class TestStack extends Stack { + public constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props); + + Role.customizeRoles(this, { + usePrecreatedRoles: { + 'cdk-table-with-customized-role/Role': 'my-precreated-role-name', + }, + }); + + const table = new Table(this, 'Table', { + partitionKey: { + name: 'pk', + type: AttributeType.STRING, + }, + }); + + // Add GSI will add a new Resource under IAM Policy + // i.e. `${this.tableArn}/index/*`. Test the Policy + // Synthesizer generation with concatenated value. + table.addGlobalSecondaryIndex({ + indexName: 'gsi', + partitionKey: { + name: 'gsi-pk', + type: AttributeType.STRING, + }, + }); + const role = new Role(this, 'Role', { + assumedBy: new ServicePrincipal('dynamodb.amazonaws.com'), + }); + table.grantReadData(role); + } +} + +class TestStack2 extends Stack { + public constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props); + + Role.customizeRoles(this, { + usePrecreatedRoles: { + 'cdk-table-with-customized-role2/Role': 'my-precreated-role-name', + }, + }); + + const table = new Table(this, 'Table', { + partitionKey: { + name: 'pk', + type: AttributeType.STRING, + }, + }); + const role = new Role(this, 'Role', { + assumedBy: new ServicePrincipal('dynamodb.amazonaws.com'), + }); + table.grantReadData(role); + } +} + +const app = new App(); +const stack = new TestStack(app, 'cdk-table-with-customized-role', {}); +const stack2 = new TestStack2(app, 'cdk-table-with-customized-role2', {}); +new IntegTest(app, 'cdk-dynamodb-customized-role-integ', { + testCases: [stack, stack2], + diffAssets: true, +}); diff --git a/packages/aws-cdk-lib/aws-iam/README.md b/packages/aws-cdk-lib/aws-iam/README.md index b020dc7c55063..16301d68f25ea 100644 --- a/packages/aws-cdk-lib/aws-iam/README.md +++ b/packages/aws-cdk-lib/aws-iam/README.md @@ -238,6 +238,72 @@ iam.Role.customizeRoles(this, { For more information on configuring permissions see the [Security And Safety Dev Guide](https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide) +#### Policy report generation + +When `customizeRoles` is used, the `iam-policy-report.txt` report will contain a list +of IAM roles and associated permissions that would have been created. This report is +generated so that it attempts to resolve any references and replace with a more user +friendly value. + +The following are some examples of the value that will appear in the report: + +```json +"Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/Role" + ] + ] +} +``` + +The policy report will instead get: + +```json +"Resource": "arn:(PARTITION):iam::(ACCOUNT):role/Role" +``` + +If IAM policy is referencing a resource attribute: + +```json +"Resource": [ + { + "Fn::GetAtt": [ + "SomeResource", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue", + } +] +``` + +The policy report will instead get: + +```json +"Resource": [ + "(Path/To/SomeResource.Arn)" + "(NoValue)" +] +``` + +The following pseudo parameters will be converted: + +1. `{ 'Ref': 'AWS::AccountId' }` -> `(ACCOUNT) +2. `{ 'Ref': 'AWS::Partition' }` -> `(PARTITION) +3. `{ 'Ref': 'AWS::Region' }` -> `(REGION) +4. `{ 'Ref': 'AWS::NoValue' }` -> `(NOVALUE) + #### Generating a permissions report It is also possible to generate the report _without_ preventing the role/policy creation. diff --git a/packages/aws-cdk-lib/core/lib/helpers-internal/customize-roles.ts b/packages/aws-cdk-lib/core/lib/helpers-internal/customize-roles.ts index 67d81b5d85727..d1354f8773f24 100644 --- a/packages/aws-cdk-lib/core/lib/helpers-internal/customize-roles.ts +++ b/packages/aws-cdk-lib/core/lib/helpers-internal/customize-roles.ts @@ -4,7 +4,7 @@ import { Construct } from 'constructs'; import { Annotations } from '../annotations'; import { attachCustomSynthesis } from '../app'; import { Reference } from '../reference'; -import { IResolvable, DefaultTokenResolver, StringConcat } from '../resolvable'; +import { IResolvable, StringConcat, PolicySynthesizerTokenResolver } from '../resolvable'; import { ISynthesisSession } from '../stack-synthesizers'; import { Token, Tokenization } from '../token'; @@ -139,14 +139,17 @@ export class PolicySynthesizer extends Construct { if (synthesizer) { return synthesizer as PolicySynthesizer; } - return new PolicySynthesizer(scope.node.root); + return new PolicySynthesizer(scope); } + private readonly _scope: Construct; private readonly roleReport: { [rolePath: string]: RoleReportOptions } = {}; private readonly managedPolicyReport: { [policyPath: string]: ManagedPolicyReportOptions } = {}; constructor(scope: Construct) { - super(scope, POLICY_SYNTHESIZER_ID); + // PolicySynthesizer should be created under the `App` scope + super(scope.node.root, POLICY_SYNTHESIZER_ID); + this._scope = scope; attachCustomSynthesis(this, { onSynthesize: (session: ISynthesisSession) => { const report = this.createJsonReport(); @@ -316,9 +319,11 @@ export class PolicySynthesizer extends Construct { if (Reference.isReference(r)) { return `(${r.target.node.path}.${r.displayName})`; } + // Token resolution requires a stack scope. We can't directly use this + // because PolicySynthesizer is always created in the App scope. const resolved = Tokenization.resolve(r, { - scope: this, - resolver: new DefaultTokenResolver(new StringConcat()), + scope: this._scope, + resolver: new PolicySynthesizerTokenResolver(new StringConcat()), }); if (typeof resolved === 'object' && resolved.hasOwnProperty('Ref')) { switch (resolved.Ref) { @@ -328,10 +333,18 @@ export class PolicySynthesizer extends Construct { return '(PARTITION)'; case 'AWS::Region': return '(REGION)'; + case 'AWS::NoValue': + return '(NOVALUE)'; default: return r; } } + // If the original value is an unresolved Token and we have successfully + // resolve it through the above Token resolution process, we should + // return the resolved token instead. + if (Token.isUnresolved(r) && typeof resolved === 'string' && resolved) { + return resolved; + } return r; }, }); diff --git a/packages/aws-cdk-lib/core/lib/resolvable.ts b/packages/aws-cdk-lib/core/lib/resolvable.ts index fe41ee78d20a1..51d489d7bb0ba 100644 --- a/packages/aws-cdk-lib/core/lib/resolvable.ts +++ b/packages/aws-cdk-lib/core/lib/resolvable.ts @@ -1,7 +1,9 @@ import { IConstruct } from 'constructs'; import { TokenString } from './private/encoding'; import { TokenMap } from './private/token-map'; +import { Reference } from './reference'; import { TokenizedStringFragments } from './string-fragments'; +import { Tokenization } from './token'; import { ResolutionTypeHint } from './type-hints'; /** @@ -200,3 +202,42 @@ export class DefaultTokenResolver implements ITokenResolver { return fragments.mapTokens({ mapToken: context.resolve }).firstValue; } } + +/** + * PolicySynthesizer token resolver implementation + * + */ +export class PolicySynthesizerTokenResolver extends DefaultTokenResolver { + constructor(concat: IFragmentConcatenator) { + super(concat); + } + + /** + * PolicySynthesizer Token resolution + * + * Resolve the Token, recurse into whatever it returns, + * then finally post-process it. + */ + public resolveToken(t: IResolvable, context: IResolveContext, postProcessor: IPostProcessor) { + try { + let resolved = t.resolve(context); + + // The token might have returned more values that need resolving, recurse + const resolvable = Tokenization.reverseString(resolved); + if (resolvable.length === 1 && Reference.isReference(resolvable.firstToken)) { + return `(${resolvable.firstToken.target.node.path}.${resolvable.firstToken.displayName})`; + } + resolved = context.resolve(resolved); + resolved = postProcessor.postProcess(resolved, context); + return resolved; + } catch (e: any) { + let message = `Resolution error: ${e.message}.`; + if (t.creationStack && t.creationStack.length > 0) { + message += `\nObject creation stack:\n at ${t.creationStack.join('\n at ')}`; + } + + e.message = message; + throw e; + } + } +}