From ae681a2ab06836e8889bbdcd5d23f1eec3d3ecf1 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Mon, 12 Feb 2024 22:47:23 -0600 Subject: [PATCH 01/13] Adding L2 construct support for setting the AWS::ECS::TaskDefinition ContainerDefinitions.CredentialSpecs property --- .../aws-cdk-lib/aws-ecs/lib/container-definition.ts | 10 ++++++++++ .../aws-ecs/test/container-definition.test.ts | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index aecbc52b59fa4..eb91223bf257e 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -126,6 +126,15 @@ export interface ContainerDefinitionOptions { */ readonly command?: string[]; + /** + * A list of ARNs in SSM or Amazon S3 to a credential spec (`CredSpec`) file that configures the container for Active Directory authentication. + * + * We recommend that you use this parameter instead of the `dockerSecurityOptions`. The maximum number of ARNs is 1. + * + * @default - No credential specs. + */ + readonly credentialSpecs?: string[]; + /** * The minimum number of CPU units to reserve for the container. * @@ -794,6 +803,7 @@ export class ContainerDefinition extends Construct { public renderContainerDefinition(_taskDefinition?: TaskDefinition): CfnTaskDefinition.ContainerDefinitionProperty { return { command: this.props.command, + credentialSpecs: this.props.credentialSpecs, cpu: this.props.cpu, disableNetworking: this.props.disableNetworking, dependsOn: cdk.Lazy.any({ produce: () => this.containerDependencies.map(renderContainerDependency) }, { omitEmptyArray: true }), diff --git a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts index 8a5c5ea165249..079ece15de59d 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts @@ -465,6 +465,7 @@ describe('container definition', () => { memoryReservationMiB: 512, containerName: 'Example Container', command: ['CMD-SHELL'], + credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], cpu: 128, disableNetworking: true, dnsSearchDomains: ['example.com'], @@ -514,6 +515,9 @@ describe('container definition', () => { 'CMD-SHELL', ], Cpu: 128, + CredentialSpecs: [ + 'credentialspecdomainless:arn:aws:s3:::bucket_name/key_name', + ], DisableNetworking: true, DnsSearchDomains: [ 'example.com', From 6a216744dfffd3c3cce5af3eee15488ae6a371dd Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Mon, 12 Feb 2024 22:47:23 -0600 Subject: [PATCH 02/13] Added integration test for previous commit --- ...sk-definition-container-credentialspecs.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts new file mode 100644 index 0000000000000..92d3b2e922898 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts @@ -0,0 +1,31 @@ +import * as cdk from 'aws-cdk-lib'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-task-definition-container-credentialspecs'); + +const taskExecutionRole = new iam.Role(stack, 'task-execution-role', { + roleName: 'aws-ecs-task-definition-container-credentialspecs-task-exec-role', + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), +}); +taskExecutionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')); +taskExecutionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')); + +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', { + executionRole: taskExecutionRole, +}); + +taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), + memoryReservationMiB: 32, + memoryLimitMiB: 512, + credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], +}); + +new IntegTest(app, 'TaskDefinitionContainerCredSpecs', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file From 2b49d5ce99b82a60027d691deb7e8df0e1e0ce58 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:27:42 -0600 Subject: [PATCH 03/13] Added Readme update --- packages/aws-cdk-lib/aws-ecs/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 322e5b0635f03..6516694a5161e 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -623,6 +623,26 @@ taskDefinition.addContainer('windowsservercore', { }); ``` +### Using Windows authentication with gMSA + +Amazon ECS supports Active Directory authentication for Linux containers through a special kind of service account called a group Managed Service Account (gMSA). For more details, please see the [product documentation on how to implement on Windows containers](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/windows-gmsa.html), or this [blog post on how to implement on Linux containers](https://aws.amazon.com/blogs/containers/using-windows-authentication-with-gmsa-on-linux-containers-on-amazon-ecs/). + +```ts +declare const taskExecutionRole: iam.Role; +declare const taskDefinition: ecs.TaskDefinition; + +// A task execution role is needed, and it should have permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. +taskDefinition.executionRole = taskExecutionRole + +taskDefinition.addContainer('gmsa-container', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + cpu: 128, + memoryLimitMiB: 256, + credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], + // Valid values are: 'credentialspecdomainless:ARN' or 'credentialspec:ARN' +}); +``` + ### Using Graviton2 with Fargate AWS Graviton2 supports AWS Fargate. For more details, please see this [blog post](https://aws.amazon.com/blogs/aws/announcing-aws-graviton2-support-for-aws-fargate-get-up-to-40-better-price-performance-for-your-serverless-containers/) From e0b3e1e679e71dbd59d77204afcb193349fbb806 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:31:18 -0600 Subject: [PATCH 04/13] Added integration test snapshot --- ...efaultTestDeployAssertF6677424.assets.json | 19 ++ ...aultTestDeployAssertF6677424.template.json | 36 +++ ...tion-container-credentialSpecs.assets.json | 19 ++ ...on-container-credentialSpecs.template.json | 133 +++++++++ .../cdk.out | 1 + .../integ.json | 12 + .../manifest.json | 125 ++++++++ .../tree.json | 274 ++++++++++++++++++ 8 files changed, 619 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json new file mode 100644 index 0000000000000..05f160b14fd82 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.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-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.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-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json new file mode 100644 index 0000000000000..46bf390078390 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46": { + "source": { + "path": "aws-ecs-task-definition-container-credentialSpecs.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46.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-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json new file mode 100644 index 0000000000000..c702b319dfdac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "taskexecutionrole7BB27090": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonS3ReadOnlyAccess" + ] + ] + } + ], + "RoleName": "aws-ecs-task-definition-container-credentialSpecs-task-exec-role" + } + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "CredentialSpecs": [ + "credentialspecdomainless:arn:aws:s3:::bucket_name/key_name" + ], + "Essential": true, + "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "Memory": 512, + "MemoryReservation": 32, + "Name": "Container" + } + ], + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "taskexecutionrole7BB27090", + "Arn" + ] + }, + "Family": "awsecstaskdefinitioncontainercredentialSpecsTaskDef72FC8474", + "NetworkMode": "bridge", + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + } + }, + "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-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json new file mode 100644 index 0000000000000..7ec17f3799133 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "TaskDefinitionContainerCredSpecs/DefaultTest": { + "stacks": [ + "aws-ecs-task-definition-container-credentialSpecs" + ], + "assertionStack": "TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert", + "assertionStackName": "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json new file mode 100644 index 0000000000000..407dd121c4008 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json @@ -0,0 +1,125 @@ +{ + "version": "36.0.0", + "artifacts": { + "aws-ecs-task-definition-container-credentialSpecs.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-ecs-task-definition-container-credentialSpecs.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-ecs-task-definition-container-credentialSpecs": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-ecs-task-definition-container-credentialSpecs.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-ecs-task-definition-container-credentialSpecs.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": [ + "aws-ecs-task-definition-container-credentialSpecs.assets" + ], + "metadata": { + "/aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "taskexecutionrole7BB27090" + } + ], + "/aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDefTaskRole1EDB4A67" + } + ], + "/aws-ecs-task-definition-container-credentialSpecs/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef54694570" + } + ], + "/aws-ecs-task-definition-container-credentialSpecs/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-task-definition-container-credentialSpecs/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-task-definition-container-credentialSpecs" + }, + "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.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": [ + "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets" + ], + "metadata": { + "/TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "TaskDefinitionContainerCredSpecs/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-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json new file mode 100644 index 0000000000000..97cabc3e480dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json @@ -0,0 +1,274 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-ecs-task-definition-container-credentialSpecs": { + "id": "aws-ecs-task-definition-container-credentialSpecs", + "path": "aws-ecs-task-definition-container-credentialSpecs", + "children": { + "task-execution-role": { + "id": "task-execution-role", + "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role", + "children": { + "Importtask-execution-role": { + "id": "Importtask-execution-role", + "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Importtask-execution-role", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonS3ReadOnlyAccess" + ] + ] + } + ], + "roleName": "aws-ecs-task-definition-container-credentialSpecs-task-exec-role" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "TaskDef": { + "id": "TaskDef", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole", + "children": { + "ImportTaskRole": { + "id": "ImportTaskRole", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/ImportTaskRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "credentialSpecs": [ + "credentialspecdomainless:arn:aws:s3:::bucket_name/key_name" + ], + "essential": true, + "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "memory": 512, + "memoryReservation": 32, + "name": "Container" + } + ], + "executionRoleArn": { + "Fn::GetAtt": [ + "taskexecutionrole7BB27090", + "Arn" + ] + }, + "family": "awsecstaskdefinitioncontainercredentialSpecsTaskDef72FC8474", + "networkMode": "bridge", + "requiresCompatibilities": [ + "EC2" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "Container": { + "id": "Container", + "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/Container", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.Ec2TaskDefinition", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-ecs-task-definition-container-credentialSpecs/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-ecs-task-definition-container-credentialSpecs/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "TaskDefinitionContainerCredSpecs": { + "id": "TaskDefinitionContainerCredSpecs", + "path": "TaskDefinitionContainerCredSpecs", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "TaskDefinitionContainerCredSpecs/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "TaskDefinitionContainerCredSpecs/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "TaskDefinitionContainerCredSpecs/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 From 7edc7550c7cc2fb2fa33a01abfc8ee531c1ac84c Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:06:27 -0600 Subject: [PATCH 05/13] Update packages/aws-cdk-lib/aws-ecs/README.md Missing semicolon in doc Co-authored-by: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> --- packages/aws-cdk-lib/aws-ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 6516694a5161e..d7a31f1577400 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -632,7 +632,7 @@ declare const taskExecutionRole: iam.Role; declare const taskDefinition: ecs.TaskDefinition; // A task execution role is needed, and it should have permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. -taskDefinition.executionRole = taskExecutionRole +taskDefinition.executionRole = taskExecutionRole; taskDefinition.addContainer('gmsa-container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), From cde5c1a3f289cce00148e4e8871d6551bfbadf4b Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Wed, 14 Feb 2024 22:58:37 +0000 Subject: [PATCH 06/13] Fixes error in name for integration tests --- ...efaultTestDeployAssertF6677424.assets.json | 0 ...aultTestDeployAssertF6677424.template.json | 0 ...ion-container-credentialspecs.assets.json} | 6 ++-- ...n-container-credentialspecs.template.json} | 4 +-- .../cdk.out | 0 .../integ.json | 2 +- .../manifest.json | 26 +++++++-------- .../tree.json | 32 +++++++++---------- 8 files changed, 35 insertions(+), 35 deletions(-) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json => integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json} (66%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json => integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json} (94%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/cdk.out (100%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/integ.json (83%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/manifest.json (85%) rename packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/{integ.task-definition-container-credentialSpecs.js.snapshot => integ.task-definition-container-credentialspecs.js.snapshot}/tree.json (93%) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.template.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json similarity index 66% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json index 46bf390078390..ffb7062170cfd 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json @@ -1,15 +1,15 @@ { "version": "36.0.0", "files": { - "184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46": { + "e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152": { "source": { - "path": "aws-ecs-task-definition-container-credentialSpecs.template.json", + "path": "aws-ecs-task-definition-container-credentialspecs.template.json", "packaging": "file" }, "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46.json", + "objectKey": "e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json similarity index 94% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json index c702b319dfdac..57f7af4d57125 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/aws-ecs-task-definition-container-credentialSpecs.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json @@ -41,7 +41,7 @@ ] } ], - "RoleName": "aws-ecs-task-definition-container-credentialSpecs-task-exec-role" + "RoleName": "aws-ecs-task-definition-container-credentialspecs-task-exec-role" } }, "TaskDefTaskRole1EDB4A67": { @@ -82,7 +82,7 @@ "Arn" ] }, - "Family": "awsecstaskdefinitioncontainercredentialSpecsTaskDef72FC8474", + "Family": "awsecstaskdefinitioncontainercredentialspecsTaskDefE15276BC", "NetworkMode": "bridge", "RequiresCompatibilities": [ "EC2" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/cdk.out similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/cdk.out rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/cdk.out diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/integ.json similarity index 83% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/integ.json index 7ec17f3799133..250fe9811366a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/integ.json @@ -3,7 +3,7 @@ "testCases": { "TaskDefinitionContainerCredSpecs/DefaultTest": { "stacks": [ - "aws-ecs-task-definition-container-credentialSpecs" + "aws-ecs-task-definition-container-credentialspecs" ], "assertionStack": "TaskDefinitionContainerCredSpecs/DefaultTest/DeployAssert", "assertionStackName": "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json similarity index 85% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json index 407dd121c4008..608cf8a509182 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json @@ -1,28 +1,28 @@ { "version": "36.0.0", "artifacts": { - "aws-ecs-task-definition-container-credentialSpecs.assets": { + "aws-ecs-task-definition-container-credentialspecs.assets": { "type": "cdk:asset-manifest", "properties": { - "file": "aws-ecs-task-definition-container-credentialSpecs.assets.json", + "file": "aws-ecs-task-definition-container-credentialspecs.assets.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" } }, - "aws-ecs-task-definition-container-credentialSpecs": { + "aws-ecs-task-definition-container-credentialspecs": { "type": "aws:cloudformation:stack", "environment": "aws://unknown-account/unknown-region", "properties": { - "templateFile": "aws-ecs-task-definition-container-credentialSpecs.template.json", + "templateFile": "aws-ecs-task-definition-container-credentialspecs.template.json", "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/184e4ea1d959ae4cd3df56b95db3401fb40a42c0d7c9c6593626cae0383c2e46.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ - "aws-ecs-task-definition-container-credentialSpecs.assets" + "aws-ecs-task-definition-container-credentialspecs.assets" ], "lookupRole": { "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", @@ -31,41 +31,41 @@ } }, "dependencies": [ - "aws-ecs-task-definition-container-credentialSpecs.assets" + "aws-ecs-task-definition-container-credentialspecs.assets" ], "metadata": { - "/aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Resource": [ + "/aws-ecs-task-definition-container-credentialspecs/task-execution-role/Resource": [ { "type": "aws:cdk:logicalId", "data": "taskexecutionrole7BB27090" } ], - "/aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/Resource": [ + "/aws-ecs-task-definition-container-credentialspecs/TaskDef/TaskRole/Resource": [ { "type": "aws:cdk:logicalId", "data": "TaskDefTaskRole1EDB4A67" } ], - "/aws-ecs-task-definition-container-credentialSpecs/TaskDef/Resource": [ + "/aws-ecs-task-definition-container-credentialspecs/TaskDef/Resource": [ { "type": "aws:cdk:logicalId", "data": "TaskDef54694570" } ], - "/aws-ecs-task-definition-container-credentialSpecs/BootstrapVersion": [ + "/aws-ecs-task-definition-container-credentialspecs/BootstrapVersion": [ { "type": "aws:cdk:logicalId", "data": "BootstrapVersion" } ], - "/aws-ecs-task-definition-container-credentialSpecs/CheckBootstrapVersion": [ + "/aws-ecs-task-definition-container-credentialspecs/CheckBootstrapVersion": [ { "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } ] }, - "displayName": "aws-ecs-task-definition-container-credentialSpecs" + "displayName": "aws-ecs-task-definition-container-credentialspecs" }, "TaskDefinitionContainerCredSpecsDefaultTestDeployAssertF6677424.assets": { "type": "cdk:asset-manifest", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json similarity index 93% rename from packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json rename to packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json index 97cabc3e480dd..3b6950930d2c8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialSpecs.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json @@ -4,17 +4,17 @@ "id": "App", "path": "", "children": { - "aws-ecs-task-definition-container-credentialSpecs": { - "id": "aws-ecs-task-definition-container-credentialSpecs", - "path": "aws-ecs-task-definition-container-credentialSpecs", + "aws-ecs-task-definition-container-credentialspecs": { + "id": "aws-ecs-task-definition-container-credentialspecs", + "path": "aws-ecs-task-definition-container-credentialspecs", "children": { "task-execution-role": { "id": "task-execution-role", - "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role", + "path": "aws-ecs-task-definition-container-credentialspecs/task-execution-role", "children": { "Importtask-execution-role": { "id": "Importtask-execution-role", - "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Importtask-execution-role", + "path": "aws-ecs-task-definition-container-credentialspecs/task-execution-role/Importtask-execution-role", "constructInfo": { "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" @@ -22,7 +22,7 @@ }, "Resource": { "id": "Resource", - "path": "aws-ecs-task-definition-container-credentialSpecs/task-execution-role/Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/task-execution-role/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::IAM::Role", "aws:cdk:cloudformation:props": { @@ -64,7 +64,7 @@ ] } ], - "roleName": "aws-ecs-task-definition-container-credentialSpecs-task-exec-role" + "roleName": "aws-ecs-task-definition-container-credentialspecs-task-exec-role" } }, "constructInfo": { @@ -80,15 +80,15 @@ }, "TaskDef": { "id": "TaskDef", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef", "children": { "TaskRole": { "id": "TaskRole", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/TaskRole", "children": { "ImportTaskRole": { "id": "ImportTaskRole", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/ImportTaskRole", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/TaskRole/ImportTaskRole", "constructInfo": { "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" @@ -96,7 +96,7 @@ }, "Resource": { "id": "Resource", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/TaskRole/Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/TaskRole/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::IAM::Role", "aws:cdk:cloudformation:props": { @@ -127,7 +127,7 @@ }, "Resource": { "id": "Resource", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", "aws:cdk:cloudformation:props": { @@ -149,7 +149,7 @@ "Arn" ] }, - "family": "awsecstaskdefinitioncontainercredentialSpecsTaskDef72FC8474", + "family": "awsecstaskdefinitioncontainercredentialspecsTaskDefE15276BC", "networkMode": "bridge", "requiresCompatibilities": [ "EC2" @@ -169,7 +169,7 @@ }, "Container": { "id": "Container", - "path": "aws-ecs-task-definition-container-credentialSpecs/TaskDef/Container", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/Container", "constructInfo": { "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", "version": "0.0.0" @@ -183,7 +183,7 @@ }, "BootstrapVersion": { "id": "BootstrapVersion", - "path": "aws-ecs-task-definition-container-credentialSpecs/BootstrapVersion", + "path": "aws-ecs-task-definition-container-credentialspecs/BootstrapVersion", "constructInfo": { "fqn": "aws-cdk-lib.CfnParameter", "version": "0.0.0" @@ -191,7 +191,7 @@ }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", - "path": "aws-ecs-task-definition-container-credentialSpecs/CheckBootstrapVersion", + "path": "aws-ecs-task-definition-container-credentialspecs/CheckBootstrapVersion", "constructInfo": { "fqn": "aws-cdk-lib.CfnRule", "version": "0.0.0" From 077781cb56c7ab06a5d1cc68840e4a1b408992b6 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa <4583012+cresvi@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:32:03 -0600 Subject: [PATCH 07/13] Fixe example on Readme --- packages/aws-cdk-lib/aws-ecs/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index d7a31f1577400..e18e8e7e751fb 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -628,12 +628,9 @@ taskDefinition.addContainer('windowsservercore', { Amazon ECS supports Active Directory authentication for Linux containers through a special kind of service account called a group Managed Service Account (gMSA). For more details, please see the [product documentation on how to implement on Windows containers](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/windows-gmsa.html), or this [blog post on how to implement on Linux containers](https://aws.amazon.com/blogs/containers/using-windows-authentication-with-gmsa-on-linux-containers-on-amazon-ecs/). ```ts -declare const taskExecutionRole: iam.Role; +// Make sure the task definition's execution role has permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. declare const taskDefinition: ecs.TaskDefinition; -// A task execution role is needed, and it should have permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. -taskDefinition.executionRole = taskExecutionRole; - taskDefinition.addContainer('gmsa-container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), cpu: 128, From b6a4cc0e6c1c5d824d8a70fc460bfa5a7efd855c Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 01:15:59 +0000 Subject: [PATCH 08/13] Updated credential spec implementation. Untested --- ...sk-definition-container-credentialspecs.ts | 11 +- .../aws-ecs/lib/container-definition.ts | 26 +++- .../aws-ecs/lib/credential-spec.ts | 143 ++++++++++++++++++ packages/aws-cdk-lib/aws-ecs/lib/index.ts | 1 + .../aws-ecs/test/credential-spec.test.ts | 61 ++++++++ 5 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts create mode 100644 packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts index 92d3b2e922898..64f16a048baa9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts @@ -1,11 +1,20 @@ import * as cdk from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { DomainlessCredentialSpec } from 'aws-cdk-lib/aws-ecs/lib/credential-spec'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-ecs-task-definition-container-credentialspecs'); +const bucket = new s3.Bucket(app, 's3-bucket', { + encryption: s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: cdk.RemovalPolicy.DESTROY, + enforceSSL: true, +}); + const taskExecutionRole = new iam.Role(stack, 'task-execution-role', { roleName: 'aws-ecs-task-definition-container-credentialspecs-task-exec-role', assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), @@ -21,7 +30,7 @@ taskDefinition.addContainer('Container', { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), memoryReservationMiB: 32, memoryLimitMiB: 512, - credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], + credentialSpecs: [DomainlessCredentialSpec.fromS3Bucket(bucket, 'credSpec')], }); new IntegTest(app, 'TaskDefinitionContainerCredSpecs', { diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index eb91223bf257e..59cd494f7ed7c 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -1,6 +1,7 @@ import { Construct } from 'constructs'; import { NetworkMode, TaskDefinition } from './base/task-definition'; import { ContainerImage, ContainerImageConfig } from './container-image'; +import { CredentialSpec, CredentialSpecConfig } from './credential-spec'; import { CfnTaskDefinition } from './ecs.generated'; import { EnvironmentFile, EnvironmentFileConfig } from './environment-file'; import { LinuxParameters } from './linux-parameters'; @@ -133,7 +134,7 @@ export interface ContainerDefinitionOptions { * * @default - No credential specs. */ - readonly credentialSpecs?: string[]; + readonly credentialSpecs?: CredentialSpec[]; /** * The minimum number of CPU units to reserve for the container. @@ -469,6 +470,11 @@ export class ContainerDefinition extends Construct { */ public readonly logDriverConfig?: LogDriverConfig; + /** + * The crdential specifications for this container. + */ + public readonly credentialSpecs?: CredentialSpecConfig[]; + /** * The name of the image referenced by this container. */ @@ -547,6 +553,14 @@ export class ContainerDefinition extends Construct { } } + if (props.credentialSpecs) { + this.credentialSpecs = []; + + for (const credSpec of props.credentialSpecs) { + this.credentialSpecs.push(credSpec.bind(this)); + } + } + if (props.cpu) { this.cpu = props.cpu; } @@ -803,7 +817,7 @@ export class ContainerDefinition extends Construct { public renderContainerDefinition(_taskDefinition?: TaskDefinition): CfnTaskDefinition.ContainerDefinitionProperty { return { command: this.props.command, - credentialSpecs: this.props.credentialSpecs, + credentialSpecs: this.credentialSpecs && this.credentialSpecs.map(renderCredentialSpec), cpu: this.props.cpu, disableNetworking: this.props.disableNetworking, dependsOn: cdk.Lazy.any({ produce: () => this.containerDependencies.map(renderContainerDependency) }, { omitEmptyArray: true }), @@ -922,6 +936,14 @@ function renderEnvironmentFiles(partition: string, environmentFiles: Environment return ret; } +function renderCredentialSpec(credSpec: CredentialSpecConfig): string { + if (!credSpec.location) { + throw Error('CredentialSpec must specify a valid location or ARN'); + } + + return `${credSpec.typePrefix}:${credSpec.location}`; +} + function renderHealthCheck(hc: HealthCheck): CfnTaskDefinition.HealthCheckProperty { if (hc.interval?.toSeconds() !== undefined) { if (5 > hc.interval?.toSeconds() || hc.interval?.toSeconds() > 300) { diff --git a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts new file mode 100644 index 0000000000000..3b54a7af1110f --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts @@ -0,0 +1,143 @@ +import { Construct } from 'constructs'; +import { IBucket } from '../../aws-s3'; +import { IParameter } from '../../aws-ssm'; + +/** + * Constructs for (CredSpec) files. + */ +export abstract class CredentialSpec { + /** + * Get the ARN for an S3 object. + */ + protected static getArnFromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { + if (!bucket.bucketName) { + throw new Error('bucketName is undefined for the provided bucket'); + } + + return bucket.arnForObjects(`${key}/${objectVersion}`); + } + + /** + * Get the ARN for a SSM parameter. + */ + protected static getArnFromSsmParameter(parameter: IParameter) { + return parameter.parameterArn; + } + + /** + * Location or ARN from where to retrieve the CredSpec file. + */ + protected readonly credSpecLocation: string; + + /** + * @param credSpecFileLocation Location or ARN from where to retrieve the CredSpec file + */ + constructor(credSpecFileLocation: string) { + this.credSpecLocation = credSpecFileLocation; + } + + /** + * Get the prefix string based on the type of CredSpec. + * + * @returns Prefix string. + */ + protected abstract getCredSpecTypePrefix(): string; + + /** + * Called when the container is initialized to allow this object to bind + * to the stack. + * + * @param scope The binding scope + */ + public bind(scope: Construct): CredentialSpecConfig { + return { + typePrefix: this.getCredSpecTypePrefix(), + location: this.credSpecLocation, + }; + } +} + +/** + * Credential specification (CredSpec) file. + */ +export class DomainJoinedCredentialSpec extends CredentialSpec { + /** + * Loads the CredSpec from a S3 bucket object. + * + * @param bucket The S3 bucket + * @param key The object key + * @param objectVersion Optional S3 object version + * @returns CredSpec with it's locations set to the S3 object's ARN. + */ + public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { + return new DomainJoinedCredentialSpec(CredentialSpec.getArnFromS3Bucket(bucket, key, objectVersion)); + } + + /** + * Loads the CredSpec from a SSM parameter. + * + * @param parameter The SSM parameter + * @returns CredSpec with it's locations set to the SSM parameter's ARN. + */ + public static fromSsmParameter(parameter: IParameter) { + return new DomainJoinedCredentialSpec(CredentialSpec.getArnFromSsmParameter(parameter)); + } + + constructor(credSpecFileLocation: string) { + super(credSpecFileLocation); + } + + protected override getCredSpecTypePrefix() { + return 'credentialspec'; + } +} + +/** + * Credential specification for domainless gMSA. + */ +export class DomainlessCredentialSpec extends CredentialSpec { + /** + * Loads the CredSpec from a S3 bucket object. + * + * @param bucket The S3 bucket + * @param key The object key + * @param objectVersion Optional S3 object version + * @returns CredSpec with it's locations set to the S3 object's ARN. + */ + public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { + return new DomainlessCredentialSpec(CredentialSpec.getArnFromS3Bucket(bucket, key, objectVersion)); + } + + /** + * Loads the CredSpec from a SSM parameter. + * + * @param parameter The SSM parameter + * @returns CredSpec with it's locations set to the SSM parameter's ARN. + */ + public static fromSsmParameter(parameter: IParameter) { + return new DomainlessCredentialSpec(CredentialSpec.getArnFromSsmParameter(parameter)); + } + + constructor(credSpecFileLocation: string) { + super(credSpecFileLocation); + } + + protected override getCredSpecTypePrefix() { + return 'credentialspecdomainless'; + } +} + +/** + * Configuration for a credential specification (CredSpec) used for a ECS container. + */ +export interface CredentialSpecConfig { + /** + * Prefix used for the CredSpec string. + */ + typePrefix: string; + + /** + * Location of the CredSpec file. + */ + location: string; +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-ecs/lib/index.ts b/packages/aws-cdk-lib/aws-ecs/lib/index.ts index e3e34f236dd4d..13931980ba3e5 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/index.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/index.ts @@ -8,6 +8,7 @@ export * from './container-image'; export * from './amis'; export * from './cluster'; export * from './environment-file'; +export * from './credential-spec'; export * from './firelens-log-router'; export * from './placement'; diff --git a/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts new file mode 100644 index 0000000000000..2bddf86be5301 --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts @@ -0,0 +1,61 @@ +import * as path from 'path'; +import * as s3 from '../../aws-s3'; +import * as cdk from '../../core'; +import * as cxapi from '../../cx-api'; +import * as ecs from '../lib'; + +/* eslint-disable dot-notation */ + +describe('credential spec', () => { + describe('ecs.DomainJoinedCredentialSpec.fromS3Bucket', () => { + test('fails if bucket name is empty', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'bucket'); + const credentialSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, 'credSpec'); + + // THEN + expect(() => defineContainerDefinition(stack, credentialSpec)).toThrow(/bucketName is undefined for the provided bucket/); + + }); + + // test('only one environment file asset object is created even if multiple container definitions use the same file', () => { + // // GIVEN + // const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + // const stack = new cdk.Stack(app); + // const fileAsset = ecs.EnvironmentFile.fromAsset(path.join(__dirname, 'demo-envfiles', 'test-envfile.env')); + + // // WHEN + // const image = ecs.ContainerImage.fromRegistry('/aws/aws-example-app'); + // const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + // const containerDefinitionProps: ecs.ContainerDefinitionProps = { + // environmentFiles: [fileAsset], + // image, + // memoryLimitMiB: 512, + // taskDefinition, + // }; + + // new ecs.ContainerDefinition(stack, 'ContainerOne', containerDefinitionProps); + // new ecs.ContainerDefinition(stack, 'ContainerTwo', containerDefinitionProps); + + // // THEN + // const assembly = app.synth(); + // const synthesized = assembly.stacks[0]; + + // // container one has an asset, container two does not + // expect(synthesized.assets.length).toEqual(1); + + // }); + }); +}); + +function defineContainerDefinition(stack: cdk.Stack, credentialSpec: ecs.CredentialSpec) { + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + return new ecs.ContainerDefinition(stack, 'Container', { + credentialSpecs: [credentialSpec], + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + memoryLimitMiB: 512, + taskDefinition, + }); +} From 55dd28a85ba10605757f161b5fe0c1141caa8900 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 04:46:39 +0000 Subject: [PATCH 09/13] UPdated credential spec code and added additional unit tests --- packages/aws-cdk-lib/aws-ecs/README.md | 4 +- .../aws-ecs/lib/container-definition.ts | 8 +- .../aws-ecs/lib/credential-spec.ts | 88 +++++----- .../aws-ecs/test/container-definition.test.ts | 2 +- .../aws-ecs/test/credential-spec.test.ts | 158 ++++++++++++++---- 5 files changed, 177 insertions(+), 83 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index e18e8e7e751fb..f95c946ed12ef 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -629,14 +629,14 @@ Amazon ECS supports Active Directory authentication for Linux containers through ```ts // Make sure the task definition's execution role has permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. +declare const bucket: s3.Bucket; declare const taskDefinition: ecs.TaskDefinition; taskDefinition.addContainer('gmsa-container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), cpu: 128, memoryLimitMiB: 256, - credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], - // Valid values are: 'credentialspecdomainless:ARN' or 'credentialspec:ARN' + credentialSpecs: [ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, 'credSpec')], }); ``` diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index 59cd494f7ed7c..b8128bb6c5c77 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { NetworkMode, TaskDefinition } from './base/task-definition'; import { ContainerImage, ContainerImageConfig } from './container-image'; -import { CredentialSpec, CredentialSpecConfig } from './credential-spec'; +import { CredentialSpec, ICredentialSpecConfig } from './credential-spec'; import { CfnTaskDefinition } from './ecs.generated'; import { EnvironmentFile, EnvironmentFileConfig } from './environment-file'; import { LinuxParameters } from './linux-parameters'; @@ -473,7 +473,7 @@ export class ContainerDefinition extends Construct { /** * The crdential specifications for this container. */ - public readonly credentialSpecs?: CredentialSpecConfig[]; + public readonly credentialSpecs?: ICredentialSpecConfig[]; /** * The name of the image referenced by this container. @@ -557,7 +557,7 @@ export class ContainerDefinition extends Construct { this.credentialSpecs = []; for (const credSpec of props.credentialSpecs) { - this.credentialSpecs.push(credSpec.bind(this)); + this.credentialSpecs.push(credSpec.bind()); } } @@ -936,7 +936,7 @@ function renderEnvironmentFiles(partition: string, environmentFiles: Environment return ret; } -function renderCredentialSpec(credSpec: CredentialSpecConfig): string { +function renderCredentialSpec(credSpec: ICredentialSpecConfig): string { if (!credSpec.location) { throw Error('CredentialSpec must specify a valid location or ARN'); } diff --git a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts index 3b54a7af1110f..99c2841ee51a8 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts @@ -1,58 +1,60 @@ -import { Construct } from 'constructs'; import { IBucket } from '../../aws-s3'; import { IParameter } from '../../aws-ssm'; /** - * Constructs for (CredSpec) files. + * Base construct for a credential specification (CredSpec). */ -export abstract class CredentialSpec { +export class CredentialSpec { /** * Get the ARN for an S3 object. */ - protected static getArnFromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { - if (!bucket.bucketName) { - throw new Error('bucketName is undefined for the provided bucket'); + protected static arnForS3Object(bucket: IBucket, key: string, objectVersion?: string) { + let keyPattern = key; + + if (!key) { + throw new Error('key is undefined'); + } + + if (objectVersion) { + keyPattern += `/${objectVersion}`; } - return bucket.arnForObjects(`${key}/${objectVersion}`); + return bucket.arnForObjects(keyPattern); } /** * Get the ARN for a SSM parameter. */ - protected static getArnFromSsmParameter(parameter: IParameter) { + protected static arnForSsmParameter(parameter: IParameter) { return parameter.parameterArn; } /** - * Location or ARN from where to retrieve the CredSpec file. + * Prefix string based on the type of CredSpec. */ - protected readonly credSpecLocation: string; + public prefixId: string; /** - * @param credSpecFileLocation Location or ARN from where to retrieve the CredSpec file + * Location or ARN from where to retrieve the CredSpec file. */ - constructor(credSpecFileLocation: string) { - this.credSpecLocation = credSpecFileLocation; - } + public fileLocation: string; /** - * Get the prefix string based on the type of CredSpec. - * - * @returns Prefix string. + * @param fileLocation Location or ARN from where to retrieve the CredSpec file */ - protected abstract getCredSpecTypePrefix(): string; + constructor(prefixId: string, fileLocation: string) { + this.prefixId = prefixId; + this.fileLocation = fileLocation; + } /** * Called when the container is initialized to allow this object to bind * to the stack. - * - * @param scope The binding scope */ - public bind(scope: Construct): CredentialSpecConfig { + public bind(): ICredentialSpecConfig { return { - typePrefix: this.getCredSpecTypePrefix(), - location: this.credSpecLocation, + typePrefix: this.prefixId, + location: this.fileLocation, }; } } @@ -61,6 +63,11 @@ export abstract class CredentialSpec { * Credential specification (CredSpec) file. */ export class DomainJoinedCredentialSpec extends CredentialSpec { + /** + * Prefix Id for this type of CredSpec. + */ + public static readonly PrefixId = 'credentialspec'; + /** * Loads the CredSpec from a S3 bucket object. * @@ -70,7 +77,7 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { * @returns CredSpec with it's locations set to the S3 object's ARN. */ public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { - return new DomainJoinedCredentialSpec(CredentialSpec.getArnFromS3Bucket(bucket, key, objectVersion)); + return new DomainJoinedCredentialSpec(CredentialSpec.arnForS3Object(bucket, key, objectVersion)); } /** @@ -80,15 +87,11 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { * @returns CredSpec with it's locations set to the SSM parameter's ARN. */ public static fromSsmParameter(parameter: IParameter) { - return new DomainJoinedCredentialSpec(CredentialSpec.getArnFromSsmParameter(parameter)); + return new DomainJoinedCredentialSpec(CredentialSpec.arnForSsmParameter(parameter)); } - constructor(credSpecFileLocation: string) { - super(credSpecFileLocation); - } - - protected override getCredSpecTypePrefix() { - return 'credentialspec'; + constructor(fileLocation: string) { + super(DomainJoinedCredentialSpec.PrefixId, fileLocation); } } @@ -96,6 +99,11 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { * Credential specification for domainless gMSA. */ export class DomainlessCredentialSpec extends CredentialSpec { + /** + * Prefix Id for this type of CredSpec. + */ + public static readonly PrefixId = 'credentialspecdomainless'; + /** * Loads the CredSpec from a S3 bucket object. * @@ -105,7 +113,7 @@ export class DomainlessCredentialSpec extends CredentialSpec { * @returns CredSpec with it's locations set to the S3 object's ARN. */ public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { - return new DomainlessCredentialSpec(CredentialSpec.getArnFromS3Bucket(bucket, key, objectVersion)); + return new DomainlessCredentialSpec(CredentialSpec.arnForS3Object(bucket, key, objectVersion)); } /** @@ -115,29 +123,25 @@ export class DomainlessCredentialSpec extends CredentialSpec { * @returns CredSpec with it's locations set to the SSM parameter's ARN. */ public static fromSsmParameter(parameter: IParameter) { - return new DomainlessCredentialSpec(CredentialSpec.getArnFromSsmParameter(parameter)); - } - - constructor(credSpecFileLocation: string) { - super(credSpecFileLocation); + return new DomainlessCredentialSpec(CredentialSpec.arnForSsmParameter(parameter)); } - protected override getCredSpecTypePrefix() { - return 'credentialspecdomainless'; + constructor(fileLocation: string) { + super(DomainlessCredentialSpec.PrefixId, fileLocation); } } /** * Configuration for a credential specification (CredSpec) used for a ECS container. */ -export interface CredentialSpecConfig { +export interface ICredentialSpecConfig { /** * Prefix used for the CredSpec string. */ - typePrefix: string; + readonly typePrefix: string; /** * Location of the CredSpec file. */ - location: string; + readonly location: string; } \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts index 079ece15de59d..dd65c599a8594 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts @@ -465,7 +465,7 @@ describe('container definition', () => { memoryReservationMiB: 512, containerName: 'Example Container', command: ['CMD-SHELL'], - credentialSpecs: ['credentialspecdomainless:arn:aws:s3:::bucket_name/key_name'], + credentialSpecs: [new ecs.DomainlessCredentialSpec('arn:aws:s3:::bucket_name/key_name')], cpu: 128, disableNetworking: true, dnsSearchDomains: ['example.com'], diff --git a/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts index 2bddf86be5301..e9bc15c5c6b21 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts @@ -1,51 +1,141 @@ -import * as path from 'path'; import * as s3 from '../../aws-s3'; +import * as ssm from '../../aws-ssm'; import * as cdk from '../../core'; -import * as cxapi from '../../cx-api'; import * as ecs from '../lib'; /* eslint-disable dot-notation */ describe('credential spec', () => { - describe('ecs.DomainJoinedCredentialSpec.fromS3Bucket', () => { - test('fails if bucket name is empty', () => { + describe('ecs.DomainJoinedCredentialSpec', () => { + test('returns the correct prefixId and location', () => { // GIVEN const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'bucket'); - const credentialSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, 'credSpec'); + const credSpecLocation = 'credSpecLocation'; + const credSpec = new ecs.DomainJoinedCredentialSpec(credSpecLocation); + const containerDefinition = defineContainerDefinition(stack, credSpec); // THEN - expect(() => defineContainerDefinition(stack, credentialSpec)).toThrow(/bucketName is undefined for the provided bucket/); + expect(containerDefinition.credentialSpecs?.length == 1); + expect(containerDefinition.credentialSpecs?.at(0)?.typePrefix).toEqual('credentialspec'); + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(credSpecLocation); + }); + + describe('fromS3Bucket', () => { + test('fails if key name is empty', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'bucket'); + + // THEN + expect(() => ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, '')).toThrow(/key is undefined/); + }); + + test('returns a valid version-less S3 object ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const objectKey = 'credSpec'; + const bucket = new s3.Bucket(stack, 'bucket'); + const credSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, objectKey); + const containerDefinition = defineContainerDefinition(stack, credSpec); + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(objectKey)); + }); + + test('returns a valid versioned S3 object ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const objectKey = 'credSpec'; + const objectVersion = 'xwghdvg2672'; + const bucket = new s3.Bucket(stack, 'bucket'); + const credSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, objectKey, objectVersion); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(`${objectKey}/${objectVersion}`)); + }); }); - // test('only one environment file asset object is created even if multiple container definitions use the same file', () => { - // // GIVEN - // const app = new cdk.App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); - // const stack = new cdk.Stack(app); - // const fileAsset = ecs.EnvironmentFile.fromAsset(path.join(__dirname, 'demo-envfiles', 'test-envfile.env')); - - // // WHEN - // const image = ecs.ContainerImage.fromRegistry('/aws/aws-example-app'); - // const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); - // const containerDefinitionProps: ecs.ContainerDefinitionProps = { - // environmentFiles: [fileAsset], - // image, - // memoryLimitMiB: 512, - // taskDefinition, - // }; - - // new ecs.ContainerDefinition(stack, 'ContainerOne', containerDefinitionProps); - // new ecs.ContainerDefinition(stack, 'ContainerTwo', containerDefinitionProps); - - // // THEN - // const assembly = app.synth(); - // const synthesized = assembly.stacks[0]; - - // // container one has an asset, container two does not - // expect(synthesized.assets.length).toEqual(1); - - // }); + describe('fromSsmParameter', () => { + test('returns a valid SSM parameter ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const parameter = new ssm.StringParameter(stack, 'parameter', { + stringValue: 'value', + }); + const credSpec = ecs.DomainJoinedCredentialSpec.fromSsmParameter(parameter); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(parameter.parameterArn); + }); + }); + }); + + describe('ecs.DomainlessCredentialSpec', () => { + test('returns the correct prefixId and location', () => { + // GIVEN + const stack = new cdk.Stack(); + const credSpecLocation = 'credSpecLocation'; + const credSpec = new ecs.DomainlessCredentialSpec(credSpecLocation); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.length == 1); + expect(containerDefinition.credentialSpecs?.at(0)?.typePrefix).toEqual('credentialspecdomainless'); + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(credSpecLocation); + }); + + describe('fromS3Bucket', () => { + test('fails if key name is empty', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'bucket'); + + // THEN + expect(() => ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, '')).toThrow(/key is undefined/); + }); + + test('returns a valid version-less S3 object ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const objectKey = 'credSpec'; + const bucket = new s3.Bucket(stack, 'bucket'); + const credSpec = ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, objectKey); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(objectKey)); + }); + + test('returns a valid versioned S3 object ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const objectKey = 'credSpec'; + const objectVersion = 'xwghdvg2672'; + const bucket = new s3.Bucket(stack, 'bucket'); + const credSpec = ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, objectKey, objectVersion); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(`${objectKey}/${objectVersion}`)); + }); + }); + + describe('fromSsmParameter', () => { + test('returns a valid SSM parameter ARN as location', () => { + // GIVEN + const stack = new cdk.Stack(); + const parameter = new ssm.StringParameter(stack, 'parameter', { + stringValue: 'value', + }); + const credSpec = ecs.DomainlessCredentialSpec.fromSsmParameter(parameter); + const containerDefinition = defineContainerDefinition(stack, credSpec); + + // THEN + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(parameter.parameterArn); + }); + }); }); }); From b1c92a6605eedf4270935f54901fc2d709388f6d Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 05:45:32 +0000 Subject: [PATCH 10/13] Updated integration test with new credSpec API --- ...tion-container-credentialspecs.assets.json | 4 +- ...on-container-credentialspecs.template.json | 111 +++++++++++- .../manifest.json | 19 ++- .../tree.json | 159 +++++++++++++++++- ...sk-definition-container-credentialspecs.ts | 14 +- .../aws-ecs/lib/credential-spec.ts | 8 +- 6 files changed, 296 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json index ffb7062170cfd..f0efcbe3852a4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152": { + "57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b": { "source": { "path": "aws-ecs-task-definition-container-credentialspecs.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152.json", + "objectKey": "57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json index 57f7af4d57125..c826dfc829711 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json @@ -1,5 +1,74 @@ { "Resources": { + "s3bucket64CB25AF": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "s3bucketPolicyF7E91061": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "s3bucket64CB25AF" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, "taskexecutionrole7BB27090": { "Type": "AWS::IAM::Role", "Properties": { @@ -67,13 +136,51 @@ "ContainerDefinitions": [ { "CredentialSpecs": [ - "credentialspecdomainless:arn:aws:s3:::bucket_name/key_name" + { + "Fn::Join": [ + "", + [ + "credentialspec:", + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/credSpec" + ] + ] + } + ], + "Essential": true, + "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "Memory": 512, + "MemoryReservation": 32, + "Name": "DomainJoinedContainer" + }, + { + "CredentialSpecs": [ + { + "Fn::Join": [ + "", + [ + "credentialspecdomainless:", + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/credSpecDomainless" + ] + ] + } ], "Essential": true, "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", "Memory": 512, "MemoryReservation": 32, - "Name": "Container" + "Name": "DomainlessContainer" } ], "ExecutionRoleArn": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json index 608cf8a509182..ab6d2b39dfefe 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e13ec2c5e8f5dccc5e12ed71f0309c9db23435ce2e86e278d4f8770ad33f6152.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -34,6 +34,18 @@ "aws-ecs-task-definition-container-credentialspecs.assets" ], "metadata": { + "/aws-ecs-task-definition-container-credentialspecs/s3-bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "s3bucket64CB25AF" + } + ], + "/aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "s3bucketPolicyF7E91061" + } + ], "/aws-ecs-task-definition-container-credentialspecs/task-execution-role/Resource": [ { "type": "aws:cdk:logicalId", @@ -49,7 +61,10 @@ "/aws-ecs-task-definition-container-credentialspecs/TaskDef/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TaskDef54694570" + "data": "TaskDef54694570", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" + ] } ], "/aws-ecs-task-definition-container-credentialspecs/BootstrapVersion": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json index 3b6950930d2c8..1e90d5e3359a6 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json @@ -8,6 +8,109 @@ "id": "aws-ecs-task-definition-container-credentialspecs", "path": "aws-ecs-task-definition-container-credentialspecs", "children": { + "s3-bucket": { + "id": "s3-bucket", + "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "AES256" + } + } + ] + }, + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "s3bucket64CB25AF" + }, + "policyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, "task-execution-role": { "id": "task-execution-role", "path": "aws-ecs-task-definition-container-credentialspecs/task-execution-role", @@ -134,13 +237,51 @@ "containerDefinitions": [ { "credentialSpecs": [ - "credentialspecdomainless:arn:aws:s3:::bucket_name/key_name" + { + "Fn::Join": [ + "", + [ + "credentialspec:", + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/credSpec" + ] + ] + } ], "essential": true, "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", "memory": 512, "memoryReservation": 32, - "name": "Container" + "name": "DomainJoinedContainer" + }, + { + "credentialSpecs": [ + { + "Fn::Join": [ + "", + [ + "credentialspecdomainless:", + { + "Fn::GetAtt": [ + "s3bucket64CB25AF", + "Arn" + ] + }, + "/credSpecDomainless" + ] + ] + } + ], + "essential": true, + "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "memory": 512, + "memoryReservation": 32, + "name": "DomainlessContainer" } ], "executionRoleArn": { @@ -167,9 +308,17 @@ "version": "0.0.0" } }, - "Container": { - "id": "Container", - "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/Container", + "DomainJoinedContainer": { + "id": "DomainJoinedContainer", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/DomainJoinedContainer", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", + "version": "0.0.0" + } + }, + "DomainlessContainer": { + "id": "DomainlessContainer", + "path": "aws-ecs-task-definition-container-credentialspecs/TaskDef/DomainlessContainer", "constructInfo": { "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", "version": "0.0.0" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts index 64f16a048baa9..79d7b1b61240f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts @@ -3,12 +3,11 @@ import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { DomainlessCredentialSpec } from 'aws-cdk-lib/aws-ecs/lib/credential-spec'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-ecs-task-definition-container-credentialspecs'); -const bucket = new s3.Bucket(app, 's3-bucket', { +const bucket = new s3.Bucket(stack, 's3-bucket', { encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.DESTROY, @@ -26,11 +25,18 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', { executionRole: taskExecutionRole, }); -taskDefinition.addContainer('Container', { +taskDefinition.addContainer('DomainJoinedContainer', { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), memoryReservationMiB: 32, memoryLimitMiB: 512, - credentialSpecs: [DomainlessCredentialSpec.fromS3Bucket(bucket, 'credSpec')], + credentialSpecs: [ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, 'credSpec')], +}); + +taskDefinition.addContainer('DomainlessContainer', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), + memoryReservationMiB: 32, + memoryLimitMiB: 512, + credentialSpecs: [ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, 'credSpecDomainless')], }); new IntegTest(app, 'TaskDefinitionContainerCredSpecs', { diff --git a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts index 99c2841ee51a8..1b2f6a52a5428 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts @@ -66,7 +66,7 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { /** * Prefix Id for this type of CredSpec. */ - public static readonly PrefixId = 'credentialspec'; + public static readonly PREFIX_ID = 'credentialspec'; /** * Loads the CredSpec from a S3 bucket object. @@ -91,7 +91,7 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { } constructor(fileLocation: string) { - super(DomainJoinedCredentialSpec.PrefixId, fileLocation); + super(DomainJoinedCredentialSpec.PREFIX_ID, fileLocation); } } @@ -102,7 +102,7 @@ export class DomainlessCredentialSpec extends CredentialSpec { /** * Prefix Id for this type of CredSpec. */ - public static readonly PrefixId = 'credentialspecdomainless'; + public static readonly PREFIX_ID = 'credentialspecdomainless'; /** * Loads the CredSpec from a S3 bucket object. @@ -127,7 +127,7 @@ export class DomainlessCredentialSpec extends CredentialSpec { } constructor(fileLocation: string) { - super(DomainlessCredentialSpec.PrefixId, fileLocation); + super(DomainlessCredentialSpec.PREFIX_ID, fileLocation); } } From 8f565ba528644616d22fac5756c85ca6501a1547 Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 06:44:42 +0000 Subject: [PATCH 11/13] Removed versioning from s3 objects --- .../aws-ecs/lib/container-definition.ts | 6 ++--- .../aws-ecs/lib/credential-spec.ts | 24 +++++++------------ .../aws-ecs/test/credential-spec.test.ts | 19 ++++----------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index b8128bb6c5c77..4a891e6f7053f 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { NetworkMode, TaskDefinition } from './base/task-definition'; import { ContainerImage, ContainerImageConfig } from './container-image'; -import { CredentialSpec, ICredentialSpecConfig } from './credential-spec'; +import { CredentialSpec, CredentialSpecConfig } from './credential-spec'; import { CfnTaskDefinition } from './ecs.generated'; import { EnvironmentFile, EnvironmentFileConfig } from './environment-file'; import { LinuxParameters } from './linux-parameters'; @@ -473,7 +473,7 @@ export class ContainerDefinition extends Construct { /** * The crdential specifications for this container. */ - public readonly credentialSpecs?: ICredentialSpecConfig[]; + public readonly credentialSpecs?: CredentialSpecConfig[]; /** * The name of the image referenced by this container. @@ -936,7 +936,7 @@ function renderEnvironmentFiles(partition: string, environmentFiles: Environment return ret; } -function renderCredentialSpec(credSpec: ICredentialSpecConfig): string { +function renderCredentialSpec(credSpec: CredentialSpecConfig): string { if (!credSpec.location) { throw Error('CredentialSpec must specify a valid location or ARN'); } diff --git a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts index 1b2f6a52a5428..2cc5d5ca812e5 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts @@ -8,18 +8,12 @@ export class CredentialSpec { /** * Get the ARN for an S3 object. */ - protected static arnForS3Object(bucket: IBucket, key: string, objectVersion?: string) { - let keyPattern = key; - + protected static arnForS3Object(bucket: IBucket, key: string) { if (!key) { throw new Error('key is undefined'); } - if (objectVersion) { - keyPattern += `/${objectVersion}`; - } - - return bucket.arnForObjects(keyPattern); + return bucket.arnForObjects(key); } /** @@ -51,7 +45,7 @@ export class CredentialSpec { * Called when the container is initialized to allow this object to bind * to the stack. */ - public bind(): ICredentialSpecConfig { + public bind(): CredentialSpecConfig { return { typePrefix: this.prefixId, location: this.fileLocation, @@ -73,11 +67,10 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { * * @param bucket The S3 bucket * @param key The object key - * @param objectVersion Optional S3 object version * @returns CredSpec with it's locations set to the S3 object's ARN. */ - public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { - return new DomainJoinedCredentialSpec(CredentialSpec.arnForS3Object(bucket, key, objectVersion)); + public static fromS3Bucket(bucket: IBucket, key: string) { + return new DomainJoinedCredentialSpec(CredentialSpec.arnForS3Object(bucket, key)); } /** @@ -109,11 +102,10 @@ export class DomainlessCredentialSpec extends CredentialSpec { * * @param bucket The S3 bucket * @param key The object key - * @param objectVersion Optional S3 object version * @returns CredSpec with it's locations set to the S3 object's ARN. */ - public static fromS3Bucket(bucket: IBucket, key: string, objectVersion?: string) { - return new DomainlessCredentialSpec(CredentialSpec.arnForS3Object(bucket, key, objectVersion)); + public static fromS3Bucket(bucket: IBucket, key: string) { + return new DomainlessCredentialSpec(CredentialSpec.arnForS3Object(bucket, key)); } /** @@ -134,7 +126,7 @@ export class DomainlessCredentialSpec extends CredentialSpec { /** * Configuration for a credential specification (CredSpec) used for a ECS container. */ -export interface ICredentialSpecConfig { +export interface CredentialSpecConfig { /** * Prefix used for the CredSpec string. */ diff --git a/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts index e9bc15c5c6b21..158e08a2f06d5 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/credential-spec.test.ts @@ -21,15 +21,6 @@ describe('credential spec', () => { }); describe('fromS3Bucket', () => { - test('fails if key name is empty', () => { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'bucket'); - - // THEN - expect(() => ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, '')).toThrow(/key is undefined/); - }); - test('returns a valid version-less S3 object ARN as location', () => { // GIVEN const stack = new cdk.Stack(); @@ -46,13 +37,12 @@ describe('credential spec', () => { // GIVEN const stack = new cdk.Stack(); const objectKey = 'credSpec'; - const objectVersion = 'xwghdvg2672'; const bucket = new s3.Bucket(stack, 'bucket'); - const credSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, objectKey, objectVersion); + const credSpec = ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, objectKey); const containerDefinition = defineContainerDefinition(stack, credSpec); // THEN - expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(`${objectKey}/${objectVersion}`)); + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(objectKey)); }); }); @@ -112,13 +102,12 @@ describe('credential spec', () => { // GIVEN const stack = new cdk.Stack(); const objectKey = 'credSpec'; - const objectVersion = 'xwghdvg2672'; const bucket = new s3.Bucket(stack, 'bucket'); - const credSpec = ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, objectKey, objectVersion); + const credSpec = ecs.DomainlessCredentialSpec.fromS3Bucket(bucket, objectKey); const containerDefinition = defineContainerDefinition(stack, credSpec); // THEN - expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(`${objectKey}/${objectVersion}`)); + expect(containerDefinition.credentialSpecs?.at(0)?.location).toEqual(bucket.arnForObjects(objectKey)); }); }); From 73ea43515f23b78918de49fbd5d679c26ca8accf Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 15:58:58 +0000 Subject: [PATCH 12/13] Attended comments; Updated integ test --- ...tion-container-credentialspecs.assets.json | 4 +- ...on-container-credentialspecs.template.json | 39 +++++++---- .../manifest.json | 34 ++++++++-- .../tree.json | 65 ++++++++++++++----- ...sk-definition-container-credentialspecs.ts | 9 ++- packages/aws-cdk-lib/aws-ecs/README.md | 23 ++++++- .../aws-ecs/lib/container-definition.ts | 2 +- .../aws-ecs/lib/credential-spec.ts | 28 +++----- 8 files changed, 146 insertions(+), 58 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json index f0efcbe3852a4..b924c8a6de6c2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b": { + "15b792b5a8db7ad07b3634fb52b565ba733387c368ab58c412b388fcaad71f2a": { "source": { "path": "aws-ecs-task-definition-container-credentialspecs.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b.json", + "objectKey": "15b792b5a8db7ad07b3634fb52b565ba733387c368ab58c412b388fcaad71f2a.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json index c826dfc829711..2805ad765fd87 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/aws-ecs-task-definition-container-credentialspecs.template.json @@ -1,6 +1,6 @@ { "Resources": { - "s3bucket64CB25AF": { + "bucket43879C71": { "Type": "AWS::S3::Bucket", "Properties": { "BucketEncryption": { @@ -22,11 +22,11 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "s3bucketPolicyF7E91061": { + "bucketPolicy638F945D": { "Type": "AWS::S3::BucketPolicy", "Properties": { "Bucket": { - "Ref": "s3bucket64CB25AF" + "Ref": "bucket43879C71" }, "PolicyDocument": { "Statement": [ @@ -44,7 +44,7 @@ "Resource": [ { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, @@ -54,7 +54,7 @@ [ { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, @@ -69,6 +69,13 @@ } } }, + "parameter76C24FC7": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "Sample CredSpec" + } + }, "taskexecutionrole7BB27090": { "Type": "AWS::IAM::Role", "Properties": { @@ -140,14 +147,22 @@ "Fn::Join": [ "", [ - "credentialspec:", + "credentialspec:arn:", { - "Fn::GetAtt": [ - "s3bucket64CB25AF", - "Arn" - ] + "Ref": "AWS::Partition" }, - "/credSpec" + ":ssm:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":parameter/", + { + "Ref": "parameter76C24FC7" + } ] ] } @@ -167,7 +182,7 @@ "credentialspecdomainless:", { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json index ab6d2b39dfefe..b73e5baec0629 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/57c984709d3d3d24477b3086ebc7d9112578f89c8a1d18da0f6a1827977bed8b.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/15b792b5a8db7ad07b3634fb52b565ba733387c368ab58c412b388fcaad71f2a.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -34,16 +34,22 @@ "aws-ecs-task-definition-container-credentialspecs.assets" ], "metadata": { - "/aws-ecs-task-definition-container-credentialspecs/s3-bucket/Resource": [ + "/aws-ecs-task-definition-container-credentialspecs/bucket/Resource": [ { "type": "aws:cdk:logicalId", - "data": "s3bucket64CB25AF" + "data": "bucket43879C71" } ], - "/aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy/Resource": [ + "/aws-ecs-task-definition-container-credentialspecs/bucket/Policy/Resource": [ { "type": "aws:cdk:logicalId", - "data": "s3bucketPolicyF7E91061" + "data": "bucketPolicy638F945D" + } + ], + "/aws-ecs-task-definition-container-credentialspecs/parameter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "parameter76C24FC7" } ], "/aws-ecs-task-definition-container-credentialspecs/task-execution-role/Resource": [ @@ -78,6 +84,24 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "s3bucket64CB25AF": [ + { + "type": "aws:cdk:logicalId", + "data": "s3bucket64CB25AF", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } + ], + "s3bucketPolicyF7E91061": [ + { + "type": "aws:cdk:logicalId", + "data": "s3bucketPolicyF7E91061", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-ecs-task-definition-container-credentialspecs" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json index 1e90d5e3359a6..206bf29b36a21 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.js.snapshot/tree.json @@ -8,13 +8,13 @@ "id": "aws-ecs-task-definition-container-credentialspecs", "path": "aws-ecs-task-definition-container-credentialspecs", "children": { - "s3-bucket": { - "id": "s3-bucket", - "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket", + "bucket": { + "id": "bucket", + "path": "aws-ecs-task-definition-container-credentialspecs/bucket", "children": { "Resource": { "id": "Resource", - "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/bucket/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::Bucket", "aws:cdk:cloudformation:props": { @@ -42,16 +42,16 @@ }, "Policy": { "id": "Policy", - "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy", + "path": "aws-ecs-task-definition-container-credentialspecs/bucket/Policy", "children": { "Resource": { "id": "Resource", - "path": "aws-ecs-task-definition-container-credentialspecs/s3-bucket/Policy/Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/bucket/Policy/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", "aws:cdk:cloudformation:props": { "bucket": { - "Ref": "s3bucket64CB25AF" + "Ref": "bucket43879C71" }, "policyDocument": { "Statement": [ @@ -69,7 +69,7 @@ "Resource": [ { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, @@ -79,7 +79,7 @@ [ { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, @@ -111,6 +111,31 @@ "version": "0.0.0" } }, + "parameter": { + "id": "parameter", + "path": "aws-ecs-task-definition-container-credentialspecs/parameter", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-credentialspecs/parameter/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SSM::Parameter", + "aws:cdk:cloudformation:props": { + "type": "String", + "value": "Sample CredSpec" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ssm.CfnParameter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ssm.StringParameter", + "version": "0.0.0" + } + }, "task-execution-role": { "id": "task-execution-role", "path": "aws-ecs-task-definition-container-credentialspecs/task-execution-role", @@ -241,14 +266,22 @@ "Fn::Join": [ "", [ - "credentialspec:", + "credentialspec:arn:", { - "Fn::GetAtt": [ - "s3bucket64CB25AF", - "Arn" - ] + "Ref": "AWS::Partition" }, - "/credSpec" + ":ssm:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":parameter/", + { + "Ref": "parameter76C24FC7" + } ] ] } @@ -268,7 +301,7 @@ "credentialspecdomainless:", { "Fn::GetAtt": [ - "s3bucket64CB25AF", + "bucket43879C71", "Arn" ] }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts index 79d7b1b61240f..551c89c574171 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.task-definition-container-credentialspecs.ts @@ -2,18 +2,23 @@ import * as cdk from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-ecs-task-definition-container-credentialspecs'); -const bucket = new s3.Bucket(stack, 's3-bucket', { +const bucket = new s3.Bucket(stack, 'bucket', { encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.DESTROY, enforceSSL: true, }); +const parameter = new ssm.StringParameter(stack, 'parameter', { + stringValue: 'Sample CredSpec', +}); + const taskExecutionRole = new iam.Role(stack, 'task-execution-role', { roleName: 'aws-ecs-task-definition-container-credentialspecs-task-exec-role', assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), @@ -29,7 +34,7 @@ taskDefinition.addContainer('DomainJoinedContainer', { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), memoryReservationMiB: 32, memoryLimitMiB: 512, - credentialSpecs: [ecs.DomainJoinedCredentialSpec.fromS3Bucket(bucket, 'credSpec')], + credentialSpecs: [ecs.DomainJoinedCredentialSpec.fromSsmParameter(parameter)], }); taskDefinition.addContainer('DomainlessContainer', { diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index f95c946ed12ef..43ac276abe91c 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -627,12 +627,33 @@ taskDefinition.addContainer('windowsservercore', { Amazon ECS supports Active Directory authentication for Linux containers through a special kind of service account called a group Managed Service Account (gMSA). For more details, please see the [product documentation on how to implement on Windows containers](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/windows-gmsa.html), or this [blog post on how to implement on Linux containers](https://aws.amazon.com/blogs/containers/using-windows-authentication-with-gmsa-on-linux-containers-on-amazon-ecs/). +There are two types of CredentialSpecs, domained-join or domainless. Both types support creation from a S3 bucket, a SSM parameter, or by directly specifying a location for the file in the constructor. + +A domian-joined gMSA container looks like: + +```ts +// Make sure the task definition's execution role has permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. +declare const parameter: ssm.IParameter; +declare const taskDefinition: ecs.TaskDefinition; + +// Domain-joined gMSA container from a SSM parameter +taskDefinition.addContainer('gmsa-domain-joined-container', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + cpu: 128, + memoryLimitMiB: 256, + credentialSpecs: [ecs.DomainJoinedCredentialSpec.fromSsmParameter(parameter)], +}); +``` + +A domianless gMSA container looks like: + ```ts // Make sure the task definition's execution role has permissions to read from the S3 bucket or SSM parameter where the CredSpec file is stored. declare const bucket: s3.Bucket; declare const taskDefinition: ecs.TaskDefinition; -taskDefinition.addContainer('gmsa-container', { +// Domainless gMSA container from a S3 bucket object. +taskDefinition.addContainer('gmsa-domainless-container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), cpu: 128, memoryLimitMiB: 256, diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index 4a891e6f7053f..64eeb25abae0f 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -130,7 +130,7 @@ export interface ContainerDefinitionOptions { /** * A list of ARNs in SSM or Amazon S3 to a credential spec (`CredSpec`) file that configures the container for Active Directory authentication. * - * We recommend that you use this parameter instead of the `dockerSecurityOptions`. The maximum number of ARNs is 1. + * We recommend that you use this parameter instead of the `dockerSecurityOptions`. Only the first entry on this array is used. This may be expanded in the future. * * @default - No credential specs. */ diff --git a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts index 2cc5d5ca812e5..b92fecc0c18aa 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/credential-spec.ts @@ -6,7 +6,7 @@ import { IParameter } from '../../aws-ssm'; */ export class CredentialSpec { /** - * Get the ARN for an S3 object. + * Helper method to generate the ARN for a S3 object. Used to avoid duplication of logic in derived classes. */ protected static arnForS3Object(bucket: IBucket, key: string) { if (!key) { @@ -17,7 +17,7 @@ export class CredentialSpec { } /** - * Get the ARN for a SSM parameter. + * Helper method to generate the ARN for a SSM parameter. Used to avoid duplication of logic in derived classes. */ protected static arnForSsmParameter(parameter: IParameter) { return parameter.parameterArn; @@ -26,17 +26,17 @@ export class CredentialSpec { /** * Prefix string based on the type of CredSpec. */ - public prefixId: string; + public readonly prefixId: string; /** * Location or ARN from where to retrieve the CredSpec file. */ - public fileLocation: string; + public readonly fileLocation: string; /** * @param fileLocation Location or ARN from where to retrieve the CredSpec file */ - constructor(prefixId: string, fileLocation: string) { + public constructor(prefixId: string, fileLocation: string) { this.prefixId = prefixId; this.fileLocation = fileLocation; } @@ -57,11 +57,6 @@ export class CredentialSpec { * Credential specification (CredSpec) file. */ export class DomainJoinedCredentialSpec extends CredentialSpec { - /** - * Prefix Id for this type of CredSpec. - */ - public static readonly PREFIX_ID = 'credentialspec'; - /** * Loads the CredSpec from a S3 bucket object. * @@ -83,8 +78,8 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { return new DomainJoinedCredentialSpec(CredentialSpec.arnForSsmParameter(parameter)); } - constructor(fileLocation: string) { - super(DomainJoinedCredentialSpec.PREFIX_ID, fileLocation); + public constructor(fileLocation: string) { + super('credentialspec', fileLocation); } } @@ -92,11 +87,6 @@ export class DomainJoinedCredentialSpec extends CredentialSpec { * Credential specification for domainless gMSA. */ export class DomainlessCredentialSpec extends CredentialSpec { - /** - * Prefix Id for this type of CredSpec. - */ - public static readonly PREFIX_ID = 'credentialspecdomainless'; - /** * Loads the CredSpec from a S3 bucket object. * @@ -118,8 +108,8 @@ export class DomainlessCredentialSpec extends CredentialSpec { return new DomainlessCredentialSpec(CredentialSpec.arnForSsmParameter(parameter)); } - constructor(fileLocation: string) { - super(DomainlessCredentialSpec.PREFIX_ID, fileLocation); + public constructor(fileLocation: string) { + super('credentialspecdomainless', fileLocation); } } From 67a97ae685480ce0c9070e0162ff10f758840fcd Mon Sep 17 00:00:00 2001 From: Cristobal Espinosa Date: Fri, 16 Feb 2024 17:29:42 +0000 Subject: [PATCH 13/13] Added logic to validate only 1 credspec is provided --- .../aws-ecs/lib/container-definition.ts | 8 +++++- .../aws-ecs/test/container-definition.test.ts | 28 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index 64eeb25abae0f..3c7d3597aeb41 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -130,7 +130,9 @@ export interface ContainerDefinitionOptions { /** * A list of ARNs in SSM or Amazon S3 to a credential spec (`CredSpec`) file that configures the container for Active Directory authentication. * - * We recommend that you use this parameter instead of the `dockerSecurityOptions`. Only the first entry on this array is used. This may be expanded in the future. + * We recommend that you use this parameter instead of the `dockerSecurityOptions`. + * + * Currently, only one credential spec is allowed per container definition. * * @default - No credential specs. */ @@ -556,6 +558,10 @@ export class ContainerDefinition extends Construct { if (props.credentialSpecs) { this.credentialSpecs = []; + if (props.credentialSpecs.length > 1) { + throw new Error('Only one credential spec is allowed per container definition.'); + } + for (const credSpec of props.credentialSpecs) { this.credentialSpecs.push(credSpec.bind()); } diff --git a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts index dd65c599a8594..0e61cc3341af5 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts @@ -404,7 +404,7 @@ describe('container definition', () => { }).toThrow(/Service connect-related port mapping field 'appProtocol' cannot be set without 'name'/); }); - test('multiple port mappings of the same name error out', () =>{ + test('multiple port mappings of the same name error out', () => { // GIVEN const stack = new cdk.Stack(); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); @@ -1103,7 +1103,7 @@ describe('container definition', () => { // THEN const expected = 8080; - expect(actual).toEqual( expected); + expect(actual).toEqual(expected); }); }); @@ -1129,7 +1129,7 @@ describe('container definition', () => { // THEN const expected = 8081; - expect(actual).toEqual( expected); + expect(actual).toEqual(expected); }); test('Ingress port should be 0 if not supplied', () => { @@ -2405,7 +2405,7 @@ describe('container definition', () => { }); // THEN - expect(taskDefinition.defaultContainer).toEqual( container); + expect(taskDefinition.defaultContainer).toEqual(container); }); @@ -2422,7 +2422,7 @@ describe('container definition', () => { }); // THEN - expect(taskDefinition.defaultContainer).toEqual( undefined); + expect(taskDefinition.defaultContainer).toEqual(undefined); }); }); @@ -2666,4 +2666,22 @@ describe('container definition', () => { ], }); }); + + test('fails if more than one credentialSpec is provided', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const containerDefinitionProps = { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskDefinition, + memoryLimitMiB: 2048, + credentialSpecs: [ + new ecs.DomainlessCredentialSpec('arn:aws:s3:::bucket_name/key_name'), + new ecs.DomainlessCredentialSpec('arn:aws:s3:::bucket_name/key_name_2'), + ], + }; + + // THEN + expect(() => new ecs.ContainerDefinition(stack, 'Container', containerDefinitionProps)).toThrow(/Only one credential spec is allowed per container definition/); + }); });