From 0bfce8965ee50ab79054e6f5a4c6bbecb0955e19 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Wed, 23 Nov 2022 00:16:50 -0800 Subject: [PATCH 1/2] fix(appsync): fully qualify service principal (#23054) Builds on top of @jpignata 's PR https://github.com/aws/aws-cdk/pull/23050. Updated integration tests. Closes https://github.com/aws/aws-cdk/pull/23050 Fixes https://github.com/aws/aws-cdk/issues/23035 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-appsync/lib/data-source.ts | 2 +- .../aws-appsync-integ.assets.json | 4 +- .../aws-appsync-integ.template.json | 2 +- .../manifest.json | 2 +- .../integ.auth-apikey.js.snapshot/tree.json | 12 +++++- .../appsync-elasticsearch.assets.json | 4 +- .../appsync-elasticsearch.template.json | 2 +- .../manifest.json | 2 +- .../tree.json | 12 +++++- .../aws-appsync-integ.assets.json | 4 +- .../aws-appsync-integ.template.json | 2 +- .../manifest.json | 2 +- .../integ.graphql-iam.js.snapshot/tree.json | 28 ++++++++++++- .../appsync-opensearch.assets.json | 4 +- .../appsync-opensearch.template.json | 2 +- .../manifest.json | 2 +- .../tree.json | 12 +++++- .../code-first-schema.assets.json | 4 +- .../code-first-schema.template.json | 2 +- .../manifest.json | 2 +- .../tree.json | 12 +++++- .../aws-appsync-integ.assets.json | 4 +- .../aws-appsync-integ.template.json | 8 ++-- .../integ.graphql.js.snapshot/manifest.json | 2 +- .../test/integ.graphql.js.snapshot/tree.json | 42 ++++++++++++++++--- 25 files changed, 131 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts index 3351c6b18c14a..05268cb469bbc 100644 --- a/packages/@aws-cdk/aws-appsync/lib/data-source.ts +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -113,7 +113,7 @@ export abstract class BaseDataSource extends Construct { super(scope, id); if (extended.type !== 'NONE') { - this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync') }); + this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com') }); } // Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*} const name = (props.name ?? id); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.assets.json index c9a98a28211a9..e1fc66d0072de 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "8af15bf3b17fb15e9d1b558caa4d5484d9b85fd19d3d939c866e805212d8d66a": { + "b0462850439179659920597f4327262b24073af4f4969622163b0a295fce1dda": { "source": { "path": "aws-appsync-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8af15bf3b17fb15e9d1b558caa4d5484d9b85fd19d3d939c866e805212d8d66a.json", + "objectKey": "b0462850439179659920597f4327262b24073af4f4969622163b0a295fce1dda.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.template.json b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.template.json index d7f6c20090494..08669bf3f0155 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.template.json @@ -42,7 +42,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/manifest.json index 3c965acbfe8d8..5092943d4ea3f 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/manifest.json @@ -17,7 +17,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}/8af15bf3b17fb15e9d1b558caa4d5484d9b85fd19d3d939c866e805212d8d66a.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b0462850439179659920597f4327262b24073af4f4969622163b0a295fce1dda.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/tree.json index 267130fb25b3f..6c8c0f98bc587 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/tree.json @@ -82,6 +82,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/testDataSource/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/testDataSource/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/testDataSource/ServiceRole/Resource", @@ -94,7 +102,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -363,7 +371,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.assets.json index 09834bad047fc..6396fedfa0262 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "677bc89625ae9e4bce11a3674b3575c54aa714db0cc253c6121311ab6a929305": { + "08fe8252ae99e2f46d03e04321cb848d70ee9c2656baeb387f3baae1575b1d87": { "source": { "path": "appsync-elasticsearch.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "677bc89625ae9e4bce11a3674b3575c54aa714db0cc253c6121311ab6a929305.json", + "objectKey": "08fe8252ae99e2f46d03e04321cb848d70ee9c2656baeb387f3baae1575b1d87.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.template.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.template.json index dcf0b78c8768e..028edef274be0 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/appsync-elasticsearch.template.json @@ -90,7 +90,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/manifest.json index 2192d65cd36a4..71d3fb6055567 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/manifest.json @@ -17,7 +17,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}/677bc89625ae9e4bce11a3674b3575c54aa714db0cc253c6121311ab6a929305.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/08fe8252ae99e2f46d03e04321cb848d70ee9c2656baeb387f3baae1575b1d87.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/tree.json index 1cf9c3478a563..90f8169797964 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-elasticsearch.js.snapshot/tree.json @@ -165,6 +165,14 @@ "id": "ServiceRole", "path": "appsync-elasticsearch/api/ds/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "appsync-elasticsearch/api/ds/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "appsync-elasticsearch/api/ds/ServiceRole/Resource", @@ -177,7 +185,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -383,7 +391,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.assets.json index a0f29ff660146..e0414901b9572 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.assets.json @@ -14,7 +14,7 @@ } } }, - "5adac1311d44e3f6eafd25a229c84d03d0e6281172ec45c266b80a5201176917": { + "8d15941ec2e2ee7e1551ec111288fbf5f90d3c8054ccd83a9d3f4995d2475536": { "source": { "path": "aws-appsync-integ.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "5adac1311d44e3f6eafd25a229c84d03d0e6281172ec45c266b80a5201176917.json", + "objectKey": "8d15941ec2e2ee7e1551ec111288fbf5f90d3c8054ccd83a9d3f4995d2475536.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.template.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.template.json index 1fca9f01ac741..1a4e761944b90 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/aws-appsync-integ.template.json @@ -74,7 +74,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/manifest.json index e6cdbcf5b4a5d..8b33b79f39dd0 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/manifest.json @@ -17,7 +17,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}/5adac1311d44e3f6eafd25a229c84d03d0e6281172ec45c266b80a5201176917.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8d15941ec2e2ee7e1551ec111288fbf5f90d3c8054ccd83a9d3f4995d2475536.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/tree.json index ac1075e5a5b5a..e349d8989099b 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.js.snapshot/tree.json @@ -125,6 +125,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/ds/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/ds/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/ds/ServiceRole/Resource", @@ -137,7 +145,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -418,6 +426,14 @@ "id": "LambdaIAM", "path": "aws-appsync-integ/LambdaIAM", "children": { + "ImportLambdaIAM": { + "id": "ImportLambdaIAM", + "path": "aws-appsync-integ/LambdaIAM/ImportLambdaIAM", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/LambdaIAM/Resource", @@ -655,6 +671,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/testFail/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/testFail/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/testFail/ServiceRole/Resource", @@ -796,7 +820,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.assets.json index 91dd6bac17eb3..1a4098ed90364 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "903ac542751f81532f9e013089671ab922c9965229f246a3850375eab1a3ea3e": { + "afad76ea31dfbff09b61eded5f1d5e5fd22e29130ce087d5f25b2a31f470128a": { "source": { "path": "appsync-opensearch.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "903ac542751f81532f9e013089671ab922c9965229f246a3850375eab1a3ea3e.json", + "objectKey": "afad76ea31dfbff09b61eded5f1d5e5fd22e29130ce087d5f25b2a31f470128a.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.template.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.template.json index 979d38093b0c9..b6e627fd979cb 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/appsync-opensearch.template.json @@ -87,7 +87,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/manifest.json index a678dbf47c8f0..c5b4953867628 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/manifest.json @@ -17,7 +17,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}/903ac542751f81532f9e013089671ab922c9965229f246a3850375eab1a3ea3e.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/afad76ea31dfbff09b61eded5f1d5e5fd22e29130ce087d5f25b2a31f470128a.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/tree.json index 855d95425a282..e1b3a8f2b034e 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-opensearch.js.snapshot/tree.json @@ -162,6 +162,14 @@ "id": "ServiceRole", "path": "appsync-opensearch/api/ds/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "appsync-opensearch/api/ds/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "appsync-opensearch/api/ds/ServiceRole/Resource", @@ -174,7 +182,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -380,7 +388,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.assets.json index 0e279b25a259d..2375be62da99f 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "f6f3bfb6532202fee41687862ae38f3c519fa381a9af9e41f6e780c306536912": { + "bfa90308faf90c034f02eb4ce506884c1ec79dec3a45f8ac7003fb57500a6ec4": { "source": { "path": "code-first-schema.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "f6f3bfb6532202fee41687862ae38f3c519fa381a9af9e41f6e780c306536912.json", + "objectKey": "bfa90308faf90c034f02eb4ce506884c1ec79dec3a45f8ac7003fb57500a6ec4.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.template.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.template.json index d7a5b5aea3152..efa6773bdc04b 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/code-first-schema.template.json @@ -42,7 +42,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/manifest.json index 644c38ee5aef8..f212abcb2d40b 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/manifest.json @@ -17,7 +17,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}/f6f3bfb6532202fee41687862ae38f3c519fa381a9af9e41f6e780c306536912.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/bfa90308faf90c034f02eb4ce506884c1ec79dec3a45f8ac7003fb57500a6ec4.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/tree.json index 909aef613b2f5..91fdd29a356a1 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.js.snapshot/tree.json @@ -82,6 +82,14 @@ "id": "ServiceRole", "path": "code-first-schema/code-first-api/planets/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "code-first-schema/code-first-api/planets/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "code-first-schema/code-first-api/planets/ServiceRole/Resource", @@ -94,7 +102,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -366,7 +374,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.assets.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.assets.json index 95dfba808c45a..c32e1bef4959b 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.assets.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "8a2c886055cfa9af5cf2737f50d275de725d06bbe9b0e0fc944b163c1082893b": { + "5965527058acb79443e765fbe78cf2b94e89646caa2a13566464ecfd01ab4f33": { "source": { "path": "aws-appsync-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8a2c886055cfa9af5cf2737f50d275de725d06bbe9b0e0fc944b163c1082893b.json", + "objectKey": "5965527058acb79443e765fbe78cf2b94e89646caa2a13566464ecfd01ab4f33.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.template.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.template.json index 9fb7d256f9660..e61ff4d7d2c7a 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.template.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/aws-appsync-integ.template.json @@ -122,7 +122,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -420,7 +420,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -1233,7 +1233,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -1396,7 +1396,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/manifest.json index 18078a4016035..c0fca1d7119a2 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/manifest.json @@ -17,7 +17,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}/8a2c886055cfa9af5cf2737f50d275de725d06bbe9b0e0fc944b163c1082893b.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/5965527058acb79443e765fbe78cf2b94e89646caa2a13566464ecfd01ab4f33.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/tree.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/tree.json index 6aa74eb3f57ae..b58920fcbfeb4 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.js.snapshot/tree.json @@ -210,6 +210,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/customerDs/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/customerDs/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/customerDs/ServiceRole/Resource", @@ -222,7 +230,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -702,6 +710,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/orderDs/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/orderDs/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/orderDs/ServiceRole/Resource", @@ -714,7 +730,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -2045,6 +2061,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/paymentDs/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/paymentDs/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/paymentDs/ServiceRole/Resource", @@ -2057,7 +2081,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -2304,6 +2328,14 @@ "id": "ServiceRole", "path": "aws-appsync-integ/Api/ds/ServiceRole", "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-appsync-integ/Api/ds/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-appsync-integ/Api/ds/ServiceRole/Resource", @@ -2316,7 +2348,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "appsync" + "Service": "appsync.amazonaws.com" } } ], @@ -2605,7 +2637,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.140" + "version": "10.1.161" } } }, From 6224b6d850ad1e019e60e905b1799baa071cf269 Mon Sep 17 00:00:00 2001 From: Naumel <104374999+Naumel@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:07:23 +0100 Subject: [PATCH 2/2] feat: bootstrap arguments for permissions boundary (#22792) https://github.com/aws/aws-cdk/issues/22744 Users can now specify in the CDK CLI a [(permissions boundary) policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to be applied on the Execution Role and all subsequent IAM users and roles of their app. If you want to try out the feature, a good starting point is having the`--example-permissions-boundary`(or `--epb`) parameter for the `cdk botstrap`: ``` cdk boostrap --epb ``` This achieves a couple of things: a new policy will be created (if not already present) in the account being bootstrapped (`cdk-${qualifier}-permissions-boundary`) and it will be referenced in the bootstrap template. In order for the bootstrap to be successful, the credentials use must include `iam:getPolicy` and `iam:createPolicy` permissions. This works pairs with #22913, as permissions boundary needs propagation. You can inspect the policy via the console, retrieve it via aws cli or sdk and you can copy the structure to use on your own from `packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml`: Resources.CdkBoostrapPermissionsBoundaryPolicy At this point you can edit the policy, add restrictions and see what scope would match your requirements. For non-dev work, the suggestion is to use `--custom-permissions-boundary` (or `--cpb`): ``` cdk bootstrap --cpb "custom-policy-name" ``` The policy must be created and accessible for the credentials used to perform the bootstrap. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/README.md | 12 +- packages/aws-cdk/lib/api/aws-auth/sdk.ts | 9 +- .../api/bootstrap/bootstrap-environment.ts | 144 +++++++++++++++++- .../lib/api/bootstrap/bootstrap-props.ts | 16 +- .../lib/api/bootstrap/bootstrap-template.yaml | 77 ++++++++++ packages/aws-cdk/lib/cli.ts | 4 + packages/aws-cdk/test/api/bootstrap2.test.ts | 47 +++++- .../test/integ/cli/bootstrapping.integtest.ts | 23 +++ packages/aws-cdk/test/integ/helpers/cdk.ts | 15 ++ packages/aws-cdk/test/util/mock-sdk.ts | 7 +- 10 files changed, 340 insertions(+), 14 deletions(-) diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 8ee36557dd51c..e46213b4d2c7e 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -568,8 +568,9 @@ $ cdk bootstrap --app='node bin/main.js' foo bar By default, bootstrap stack will be protected from stack termination. This can be disabled using `--termination-protection` argument. -If you have specific needs, policies, or requirements not met by the default template, you can customize it -to fit your own situation, by exporting the default one to a file and either deploying it yourself +If you have specific prerequisites not met by the example template, you can +[customize it](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-customizing) +to fit your requirements, by exporting the provided one to a file and either deploying it yourself using CloudFormation directly, or by telling the CLI to use a custom template. That looks as follows: ```console @@ -582,6 +583,13 @@ $ cdk bootstrap --show-template > bootstrap-template.yaml $ cdk bootstrap --template bootstrap-template.yaml ``` +Out of the box customization options are also available as arguments. To use a permissions boundary: + +- `--example-permissions-boundary` indicates the example permissions boundary, supplied by CDK +- `--custom-permissions-boundary` specifies, by name a predefined, customer maintained, boundary + +A few notes to add at this point. The CDK supplied permissions boundary policy should be regarded as an example. Edit the content and reference the example policy if you're testing out the feature, turn it into a new policy for actual deployments (if one does not already exist). The concern here is drift as, most likely, a permissions boundary is maintained and has dedicated conventions, naming included. + ### `cdk doctor` Inspect the current command-line environment and configurations, and collect information that can be useful for diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index aa3231247309e..4f01ae207ecbc 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -9,7 +9,7 @@ import { Account } from './sdk-provider'; // We need to map regions to domain suffixes, and the SDK already has a function to do this. // It's not part of the public API, but it's also unlikely to go away. // -// Reuse that function, and add a safety check so we don't accidentally break if they ever +// Reuse that function, and add a safety check, so we don't accidentally break if they ever // refactor that away. /* eslint-disable @typescript-eslint/no-require-imports */ @@ -53,6 +53,7 @@ export interface ISDK { lambda(): AWS.Lambda; cloudFormation(): AWS.CloudFormation; ec2(): AWS.EC2; + iam(): AWS.IAM; ssm(): AWS.SSM; s3(): AWS.S3; route53(): AWS.Route53; @@ -163,6 +164,10 @@ export class SDK implements ISDK { return this.wrapServiceErrorHandling(new AWS.EC2(this.config)); } + public iam(): AWS.IAM { + return this.wrapServiceErrorHandling(new AWS.IAM(this.config)); + } + public ssm(): AWS.SSM { return this.wrapServiceErrorHandling(new AWS.SSM(this.config)); } @@ -415,4 +420,4 @@ function allChainedExceptionMessages(e: Error | undefined) { */ export function isUnrecoverableAwsError(e: Error) { return (e as any).code === 'ExpiredToken'; -} \ No newline at end of file +} diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index f86a3d96b85b5..80b8c4fc7a10c 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -4,7 +4,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import { warning } from '../../logging'; import { loadStructuredFile, serializeStructure } from '../../serialize'; import { rootDir } from '../../util/directories'; -import { SdkProvider } from '../aws-auth'; +import { ISDK, Mode, SdkProvider } from '../aws-auth'; import { DeployStackResult } from '../deploy-stack'; import { BootstrapEnvironmentOptions, BootstrappingParameters } from './bootstrap-props'; import { BootstrapStack, bootstrapVersionFromTemplate } from './deploy-bootstrap'; @@ -79,6 +79,7 @@ export class Bootstrapper { const bootstrapTemplate = await this.loadTemplate(); const current = await BootstrapStack.lookup(sdkProvider, environment, options.toolkitStackName); + const partition = await current.partition(); if (params.createCustomerMasterKey !== undefined && params.kmsKeyId) { throw new Error('You cannot pass \'--bootstrap-kms-key-id\' and \'--bootstrap-customer-key\' together. Specify one or the other'); @@ -102,7 +103,7 @@ export class Bootstrapper { if (trustedAccounts.length === 0 && cloudFormationExecutionPolicies.length === 0) { // For self-trust it's okay to default to AdministratorAccess, and it improves the usability of bootstrapping a lot. // - // We don't actually make the implicity policy a physical parameter. The template will infer it instead, + // We don't actually make the implicitly policy a physical parameter. The template will infer it instead, // we simply do the UI advertising that behavior here. // // If we DID make it an explicit parameter, we wouldn't be able to tell the difference between whether @@ -113,7 +114,7 @@ export class Bootstrapper { // // Would leave AdministratorAccess policies with a trust relationship, without the user explicitly // approving the trust policy. - const implicitPolicy = `arn:${await current.partition()}:iam::aws:policy/AdministratorAccess`; + const implicitPolicy = `arn:${partition}:iam::aws:policy/AdministratorAccess`; warning(`Using default execution policy of '${implicitPolicy}'. Pass '--cloudformation-execution-policies' to customize.`); } else if (cloudFormationExecutionPolicies.length === 0) { throw new Error('Please pass \'--cloudformation-execution-policies\' when using \'--trust\' to specify deployment permissions. Try a managed policy of the form \'arn:aws:iam::aws:policy/\'.'); @@ -130,9 +131,25 @@ export class Bootstrapper { // * '-' if this is the first time we're deploying this stack (or upgrading from old to new bootstrap) const currentKmsKeyId = current.parameters.FileAssetsBucketKmsKeyId; const kmsKeyId = params.kmsKeyId ?? - (params.createCustomerMasterKey === true ? CREATE_NEW_KEY : - params.createCustomerMasterKey === false || currentKmsKeyId === undefined ? USE_AWS_MANAGED_KEY : - undefined); + (params.createCustomerMasterKey === true ? CREATE_NEW_KEY : + params.createCustomerMasterKey === false || currentKmsKeyId === undefined ? USE_AWS_MANAGED_KEY : undefined); + + /* A permissions boundary can be provided via: + * - the flag indicating the example one should be used + * - the name indicating the custom permissions boundary to be used + * Re-bootstrapping will NOT be blocked by either tightening or relaxing the permissions' boundary. + */ + const currentPermissionsBoundary = current.parameters.InputPermissionsBoundary; + const inputPolicyName = params.examplePermissionsBoundary ? CDK_BOOTSTRAP_PERMISSIONS_BOUNDARY : params.customPermissionsBoundary; + let policyName; + if (inputPolicyName) { + // If the example policy is not already in place, it must be created. + const sdk = (await sdkProvider.forEnvironment(environment, Mode.ForWriting)).sdk; + policyName = await this.getPolicyName(environment, sdk, inputPolicyName, partition, params); + } + if (currentPermissionsBoundary !== policyName) { + warning(`Switching from ${currentPermissionsBoundary} to ${policyName} as permissions boundary`); + } return current.update( bootstrapTemplate, @@ -145,12 +162,121 @@ export class Bootstrapper { CloudFormationExecutionPolicies: cloudFormationExecutionPolicies.join(','), Qualifier: params.qualifier, PublicAccessBlockConfiguration: params.publicAccessBlockConfiguration || params.publicAccessBlockConfiguration === undefined ? 'true' : 'false', + InputPermissionsBoundary: policyName, }, { ...options, terminationProtection: options.terminationProtection ?? current.terminationProtection, }); } + private async getPolicyName( + environment: cxapi.Environment, + sdk: ISDK, + permissionsBoundary: string, + partition: string, + params: BootstrappingParameters): Promise { + + if (permissionsBoundary !== CDK_BOOTSTRAP_PERMISSIONS_BOUNDARY) { + this.validatePolicyName(permissionsBoundary); + return Promise.resolve(permissionsBoundary); + } + // if no Qualifier is supplied, resort to the default one + const arn = await this.getExamplePermissionsBoundary(params.qualifier ?? 'hnb659fds', partition, environment.account, sdk); + const policyName = arn.split('/').pop(); + if (!policyName) { + throw new Error('Could not retrieve the example permission boundary!'); + } + return Promise.resolve(policyName); + } + + private async getExamplePermissionsBoundary(qualifier: string, partition: string, account: string, sdk: ISDK): Promise { + const iam = sdk.iam(); + + let policyName = `cdk-${qualifier}-permissions-boundary`; + const arn = `arn:${partition}:iam::${account}:policy/${policyName}`; + + try { + let getPolicyResp = await iam.getPolicy({ PolicyArn: arn }).promise(); + if (getPolicyResp.Policy) { + return arn; + } + } catch (e) { + // https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicy.html#API_GetPolicy_Errors + if (e.name === 'NoSuchEntity') { + //noop, proceed with creating the policy + } else { + throw e; + } + } + + const policyDoc = { + Version: '2012-10-17', + Statement: [ + { + Action: ['*'], + Resource: '*', + Effect: 'Allow', + Sid: 'ExplicitAllowAll', + }, + { + Condition: { + StringEquals: { + 'iam:PermissionsBoundary': `arn:${partition}:iam::${account}:policy/cdk-${qualifier}-permissions-boundary`, + }, + }, + Action: [ + 'iam:CreateUser', + 'iam:CreateRole', + 'iam:PutRolePermissionsBoundary', + 'iam:PutUserPermissionsBoundary', + ], + Resource: '*', + Effect: 'Allow', + Sid: 'DenyAccessIfRequiredPermBoundaryIsNotBeingApplied', + }, + { + Action: [ + 'iam:CreatePolicyVersion', + 'iam:DeletePolicy', + 'iam:DeletePolicyVersion', + 'iam:SetDefaultPolicyVersion', + ], + Resource: `arn:${partition}:iam::${account}:policy/cdk-${qualifier}-permissions-boundary`, + Effect: 'Deny', + Sid: 'DenyPermBoundaryIAMPolicyAlteration', + }, + { + Action: [ + 'iam:DeleteUserPermissionsBoundary', + 'iam:DeleteRolePermissionsBoundary', + ], + Resource: '*', + Effect: 'Deny', + Sid: 'DenyRemovalOfPermBoundaryFromAnyUserOrRole', + }, + ], + }; + const request = { + PolicyName: policyName, + PolicyDocument: JSON.stringify(policyDoc), + }; + const createPolicyResponse = await iam.createPolicy(request).promise(); + if (createPolicyResponse.Policy?.Arn) { + return createPolicyResponse.Policy.Arn; + } else { + throw new Error(`Could not retrieve the example permission boundary ${arn}!`); + } + } + + private validatePolicyName(permissionsBoundary: string) { + // https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreatePolicy.html + const regexp: RegExp = /[\w+=,.@-]+/; + const matches = regexp.exec(permissionsBoundary); + if (!(matches && matches.length === 1 && matches[0] === permissionsBoundary)) { + throw new Error(`The permissions boundary name ${permissionsBoundary} does not match the IAM conventions.`); + } + } + private async customBootstrap( environment: cxapi.Environment, sdkProvider: SdkProvider, @@ -179,7 +305,7 @@ export class Bootstrapper { } /** - * Magic parameter value that will cause the bootstrap-template.yml to NOT create a CMK but use the default keyo + * Magic parameter value that will cause the bootstrap-template.yml to NOT create a CMK but use the default key */ const USE_AWS_MANAGED_KEY = 'AWS_MANAGED_KEY'; @@ -187,6 +313,10 @@ const USE_AWS_MANAGED_KEY = 'AWS_MANAGED_KEY'; * Magic parameter value that will cause the bootstrap-template.yml to create a CMK */ const CREATE_NEW_KEY = ''; +/** + * Parameter value indicating the use of the default, CDK provided permissions boundary for bootstrap-template.yml + */ +const CDK_BOOTSTRAP_PERMISSIONS_BOUNDARY = 'CDK_BOOTSTRAP_PERMISSIONS_BOUNDARY'; /** * Split an array-like CloudFormation parameter on , diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index 10fc4fc8ff598..fee2deddfa26b 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -101,4 +101,18 @@ export interface BootstrappingParameters { */ readonly publicAccessBlockConfiguration?: boolean; -} \ No newline at end of file + /** + * Flag for using the default permissions boundary for bootstrapping + * + * @default - No value, optional argument + */ + readonly examplePermissionsBoundary?: boolean; + + /** + * Name for the customer's custom permissions boundary for bootstrapping + * + * @default - No value, optional argument + */ + readonly customPermissionsBoundary?: string; + +} diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index f1c8121cdc473..16ad213a62870 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -42,6 +42,14 @@ Parameters: Default: 'true' Type: 'String' AllowedValues: ['true', 'false'] + InputPermissionsBoundary: + Description: Whether or not to use either the CDK supplied or custom permissions boundary + Default: '' + Type: 'String' + UseExamplePermissionsBoundary: + Default: 'false' + AllowedValues: [ 'true', 'false' ] + Type: String Conditions: HasTrustedAccounts: Fn::Not: @@ -77,6 +85,15 @@ Conditions: Fn::Equals: - 'AWS_MANAGED_KEY' - Ref: FileAssetsBucketKmsKeyId + ShouldCreatePermissionsBoundary: + Fn::Equals: + - 'true' + - Ref: UseExamplePermissionsBoundary + PermissionsBoundarySet: + Fn::Not: + - Fn::Equals: + - '' + - Ref: InputPermissionsBoundary HasCustomContainerAssetsRepositoryName: Fn::Not: - Fn::Equals: @@ -500,6 +517,66 @@ Resources: - - Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess" RoleName: Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} + PermissionsBoundary: + Fn::If: + - PermissionsBoundarySet + - Fn::Sub: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}' + - Ref: AWS::NoValue + CdkBoostrapPermissionsBoundaryPolicy: + # Edit the template prior to boostrap in order to have this example policy created + Condition: ShouldCreatePermissionsBoundary + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Statement: + # If permission boundaries do not have an explicit `allow`, then the effect is `deny` + - Sid: ExplicitAllowAll + Action: + - "*" + Effect: Allow + Resource: "*" + # Default permissions to prevent privilege escalation + - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied + Action: + - iam:CreateUser + - iam:CreateRole + - iam:PutRolePermissionsBoundary + - iam:PutUserPermissionsBoundary + Condition: + StringNotEquals: + iam:PermissionsBoundary: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Effect: Deny + Resource: "*" + # Forbid the policy itself being edited + - Sid: DenyPermBoundaryIAMPolicyAlteration + Action: + - iam:CreatePolicyVersion + - iam:DeletePolicy + - iam:DeletePolicyVersion + - iam:SetDefaultPolicyVersion + Effect: Deny + Resource: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + # Forbid removing the permissions boundary from any user or role that has it associated + - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole + Action: + - iam:DeleteUserPermissionsBoundary + - iam:DeleteRolePermissionsBoundary + Effect: Deny + Resource: "*" + # Add your specific organizational security policy here + # Uncomment the example to deny access to AWS Config + #- Sid: OrganizationalSecurityPolicy + # Action: + # - "config:*" + # Effect: Deny + # Resource: "*" + Version: "2012-10-17" + Description: "Bootstrap Permission Boundary" + ManagedPolicyName: + Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Path: / # The SSM parameter is used in pipeline-deployed templates to verify the version # of the bootstrap resources. CdkBootstrapVersion: diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index 4192c795689cb..b41bbb1731a54 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -92,6 +92,8 @@ async function parseCommandLineArguments() { .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', (yargs: Argv) => yargs .option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined }) .option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined, conflicts: 'bootstrap-customer-key' }) + .option('example-permissions-boundary', { type: 'boolean', alias: ['epb', 'example-permissions-boundary'], desc: 'Use the example permissions boundary.', default: undefined, conflicts: 'custom-permissions-boundary' }) + .option('custom-permissions-boundary', { type: 'string', alias: ['cpb', 'custom-permissions-boundary'], desc: 'Use the permissions boundary specified by name.', default: undefined, conflicts: 'example-permissions-boundary' }) .option('bootstrap-customer-key', { type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', default: undefined, conflicts: 'bootstrap-kms-key-id' }) .option('qualifier', { type: 'string', desc: 'String which must be unique for each bootstrap stack. You must configure it on your CDK app if you change this from the default.', default: undefined }) .option('public-access-block-configuration', { type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', default: undefined }) @@ -462,6 +464,8 @@ async function initCommandLine() { createCustomerMasterKey: args.bootstrapCustomerKey, qualifier: args.qualifier, publicAccessBlockConfiguration: args.publicAccessBlockConfiguration, + examplePermissionsBoundary: argv.examplePermissionsBoundary, + customPermissionsBoundary: argv.customPermissionsBoundary, trustedAccounts: arrayFromYargs(args.trust), trustedAccountsForLookup: arrayFromYargs(args.trustForLookup), cloudFormationExecutionPolicies: arrayFromYargs(args.cloudformationExecutionPolicies), diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts index 42b4f24d0a771..5ca8b6e3f9929 100644 --- a/packages/aws-cdk/test/api/bootstrap2.test.ts +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -1,13 +1,18 @@ + const mockDeployStack = jest.fn(); jest.mock('../../lib/api/deploy-stack', () => ({ deployStack: mockDeployStack, })); +import { IAM } from 'aws-sdk'; import { Bootstrapper, DeployStackOptions, ToolkitInfo } from '../../lib/api'; import { mockBootstrapStack, MockSdk, MockSdkProvider } from '../util/mock-sdk'; let bootstrapper: Bootstrapper; +let mockGetPolicyIamCode: (params: IAM.Types.GetPolicyRequest) => IAM.Types.GetPolicyResponse; +let mockCreatePolicyIamCode: (params: IAM.Types.CreatePolicyRequest) => IAM.Types.CreatePolicyResponse; + beforeEach(() => { bootstrapper = new Bootstrapper({ source: 'default' }); }); @@ -29,6 +34,18 @@ describe('Bootstrapping v2', () => { sdk = new MockSdkProvider({ realSdk: false }); // By default, we'll return a non-found toolkit info (ToolkitInfo as any).lookup = jest.fn().mockResolvedValue(ToolkitInfo.bootstraplessDeploymentsOnly(sdk.sdk)); + const value = { + Policy: { + PolicyName: 'my-policy', + Arn: 'arn:aws:iam::0123456789012:policy/my-policy', + }, + }; + mockGetPolicyIamCode = jest.fn().mockReturnValue(value); + mockCreatePolicyIamCode = jest.fn().mockReturnValue(value); + sdk.stubIam({ + createPolicy: mockCreatePolicyIamCode, + getPolicy: mockGetPolicyIamCode, + }); }); afterEach(() => { @@ -82,6 +99,34 @@ describe('Bootstrapping v2', () => { })); }); + test('passes true to PermissionsBoundary', async () => { + await bootstrapper.bootstrapEnvironment(env, sdk, { + parameters: { + examplePermissionsBoundary: true, + }, + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: expect.objectContaining({ + InputPermissionsBoundary: 'cdk-hnb659fds-permissions-boundary', + }), + })); + }); + + test('passes value to PermissionsBoundary', async () => { + await bootstrapper.bootstrapEnvironment(env, sdk, { + parameters: { + customPermissionsBoundary: 'permissions-boundary-name', + }, + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: expect.objectContaining({ + InputPermissionsBoundary: 'permissions-boundary-name', + }), + })); + }); + test('passing trusted accounts without CFN managed policies results in an error', async () => { await expect(bootstrapper.bootstrapEnvironment(env, sdk, { parameters: { @@ -328,4 +373,4 @@ describe('Bootstrapping v2', () => { })); }); }); -}); \ No newline at end of file +}); diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index 95f98145a0a16..f1fa6a48873c0 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -193,6 +193,28 @@ integTest('can dump the template, modify and use it to deploy a custom bootstrap }); })); +integTest('can use the default permissions boundary to bootstrap', withDefaultFixture(async (fixture) => { + let template = await fixture.cdkBootstrapModern({ + // toolkitStackName doesn't matter for this particular invocation + toolkitStackName: fixture.bootstrapStackName, + showTemplate: true, + examplePermissionsBoundary: true, + }); + + expect(template).toContain(`arn:aws:iam::${await fixture.aws.account()}:policy/cdk-${fixture.qualifier}-permissions-boundary`); +})); + +integTest('can use the custom permissions boundary to bootstrap', withDefaultFixture(async (fixture) => { + let template = await fixture.cdkBootstrapModern({ + // toolkitStackName doesn't matter for this particular invocation + toolkitStackName: fixture.bootstrapStackName, + showTemplate: true, + customPermissionsBoundary: 'permission-boundary-name', + }); + + expect(template).toContain('permission-boundary-name'); +})); + integTest('switch on termination protection, switch is left alone on re-bootstrap', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.bootstrapStackName; @@ -276,3 +298,4 @@ integTest('create ECR with tag IMMUTABILITY to set on', withDefaultFixture(async expect(ecrResponse.repositories?.[0].imageTagMutability).toEqual('IMMUTABLE'); })); + diff --git a/packages/aws-cdk/test/integ/helpers/cdk.ts b/packages/aws-cdk/test/integ/helpers/cdk.ts index 1dae12156313e..6b95f2d5d44f1 100644 --- a/packages/aws-cdk/test/integ/helpers/cdk.ts +++ b/packages/aws-cdk/test/integ/helpers/cdk.ts @@ -303,6 +303,16 @@ export interface CdkModernBootstrapCommandOptions extends CommonCdkBootstrapComm * @default false */ readonly terminationProtection?: boolean; + + /** + * @default undefined + */ + readonly examplePermissionsBoundary?: boolean; + + /** + * @default undefined + */ + readonly customPermissionsBoundary?: string; } export class TestFixture { @@ -415,6 +425,11 @@ export class TestFixture { if (options.tags) { args.push('--tags', options.tags); } + if (options.customPermissionsBoundary !== undefined) { + args.push('--custom-permissions-boundary', options.customPermissionsBoundary); + } else if (options.examplePermissionsBoundary !== undefined) { + args.push('--example-permissions-boundary'); + } return this.cdk(args, { ...options.cliOptions, diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index ae3d590e70f7e..7a16fda09b132 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -26,7 +26,7 @@ export interface MockSdkProviderOptions { /** * An SDK that allows replacing (some of) the clients * - * Its the responsibility of the consumer to replace all calls that + * It's the responsibility of the consumer to replace all calls that * actually will be called. */ export class MockSdkProvider extends SdkProvider { @@ -114,6 +114,10 @@ export class MockSdkProvider extends SdkProvider { (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); } + public stubIam(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + (this.sdk as any).iam = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); + } + public stubStepFunctions(stubs: SyncHandlerSubsetOf) { (this.sdk as any).stepFunctions = jest.fn().mockReturnValue(partialAwsService(stubs)); } @@ -138,6 +142,7 @@ export class MockSdkProvider extends SdkProvider { export class MockSdk implements ISDK { public readonly currentRegion: string = 'bermuda-triangle-1337'; public readonly lambda = jest.fn(); + public readonly iam = jest.fn(); public readonly cloudFormation = jest.fn(); public readonly ec2 = jest.fn(); public readonly ssm = jest.fn();