From 9b1051f86abdfa6448b14cdae8e1ef9acb1e6688 Mon Sep 17 00:00:00 2001 From: yoshizawa56 Date: Wed, 6 Jul 2022 02:36:53 +0900 Subject: [PATCH] feat(batch): add secrets props to job definition (#20871) Add a secrets property to batch.JobDefinitionContainer. This interface is almost the same as ecs.ContainerDefinitionOptions. This is reopen PR of #19506 closes #10976 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-batch/README.md | 17 ++++ .../@aws-cdk/aws-batch/lib/job-definition.ts | 23 ++++++ .../aws-batch/rosetta/default.ts-fixture | 1 + .../batch-stack.assets.json | 19 +++++ .../batch-stack.template.json | 42 ++++++++++ .../test/batch.integ.snapshot/cdk.out | 2 +- .../test/batch.integ.snapshot/integ.json | 4 +- .../test/batch.integ.snapshot/manifest.json | 14 +++- .../test/batch.integ.snapshot/tree.json | 80 ++++++++++++++++++- .../@aws-cdk/aws-batch/test/integ.batch.ts | 5 ++ .../aws-batch/test/job-definition.test.ts | 74 ++++++++++++++++- 11 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 67d31ea468b41..272c66d723092 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -300,6 +300,23 @@ new batch.JobDefinition(this, 'job-def', { }); ``` +### Using the secret on secrets manager + +You can set the environment variables from secrets manager. + +```ts +const dbSecret = new secretsmanager.Secret(this, 'secret'); + +new batch.JobDefinition(this, 'batch-job-def-secrets', { + container: { + image: ecs.EcrImage.fromRegistry('docker/whalesay'), + secrets: { + PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), + }, + }, +}); +``` + ### Importing an existing Job Definition #### From ARN diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 025dea4516252..99c28fa03d5c6 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -112,6 +112,13 @@ export interface JobDefinitionContainer { */ readonly environment?: { [key: string]: string }; + /** + * The environment variables from secrets manager or ssm parameter store + * + * @default none + */ + readonly secrets?: { [key: string]: ecs.Secret }; + /** * The image used to start a container. */ @@ -453,6 +460,14 @@ export class JobDefinition extends Resource implements IJobDefinition { platformCapabilities: props.platformCapabilities ?? [PlatformCapabilities.EC2], }); + // add read secrets permission to execution role + if ( props.container.secrets && props.container.executionRole ) { + const executionRole = props.container.executionRole; + Object.values(props.container.secrets).forEach((secret) => { + secret.grantRead(executionRole); + }); + } + this.jobDefinitionArn = this.getResourceArnAttribute(jobDef.ref, { service: 'batch', resource: 'job-definition', @@ -507,6 +522,14 @@ export class JobDefinition extends Resource implements IJobDefinition { return { command: container.command, environment: this.deserializeEnvVariables(container.environment), + secrets: container.secrets + ? Object.entries(container.secrets).map(([key, value]) => { + return { + name: key, + valueFrom: value.arn, + }; + }) + : undefined, image: this.imageConfig.imageName, instanceType: container.instanceType && container.instanceType.toString(), jobRoleArn: container.jobRole && container.jobRole.roleArn, diff --git a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture index 6fcfe8682a3ff..76bb82380fc06 100644 --- a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture @@ -4,6 +4,7 @@ import { Stack } from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as batch from '@aws-cdk/aws-batch'; import * as ecs from '@aws-cdk/aws-ecs'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json new file mode 100644 index 0000000000000..10c7b228276fe --- /dev/null +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "20.0.0", + "files": { + "d3685c79f9ec67f5dd6fda839a136b079f201b3d72695fe0ea3b3788c3471cc8": { + "source": { + "path": "batch-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d3685c79f9ec67f5dd6fda839a136b079f201b3d72695fe0ea3b3788c3471cc8.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json index c7a0b0b5c83aa..6ee6d41362455 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json @@ -1365,6 +1365,14 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, + "batchsecret7CD5E4C6": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {} + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "batchjobdeffromecrE0E30DAD": { "Type": "AWS::Batch::JobDefinition", "Properties": { @@ -1486,6 +1494,32 @@ } } }, + "executionroleDefaultPolicy497F11A3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "executionroleDefaultPolicy497F11A3", + "Roles": [ + { + "Ref": "executionroleD9A39BE6" + } + ] + } + }, "batchjobdeffargate7FE30059": { "Type": "AWS::Batch::JobDefinition", "Properties": { @@ -1509,6 +1543,14 @@ "Type": "MEMORY", "Value": "512" } + ], + "Secrets": [ + { + "Name": "SECRET", + "ValueFrom": { + "Ref": "batchsecret7CD5E4C6" + } + } ] }, "PlatformCapabilities": [ diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json index 307a072859518..25186aa4394c3 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-batch/test/integ.batch": { + "integ.batch": { "stacks": [ "batch-stack" ], diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json index 6210ed0c39e78..4d08217715b57 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -285,6 +285,12 @@ "data": "batchjobrepo4C508C51" } ], + "/batch-stack/batch-secret/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "batchsecret7CD5E4C6" + } + ], "/batch-stack/batch-job-def-from-ecr/Resource": [ { "type": "aws:cdk:logicalId", @@ -303,6 +309,12 @@ "data": "executionroleD9A39BE6" } ], + "/batch-stack/execution-role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "executionroleDefaultPolicy497F11A3" + } + ], "/batch-stack/batch-job-def-fargate/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json index 75308ccef8ff4..5c7381ec38e8b 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json @@ -8,8 +8,8 @@ "id": "Tree", "path": "Tree", "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.33" } }, "batch-stack": { @@ -1614,6 +1614,30 @@ "version": "0.0.0" } }, + "batch-secret": { + "id": "batch-secret", + "path": "batch-stack/batch-secret", + "children": { + "Resource": { + "id": "Resource", + "path": "batch-stack/batch-secret/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SecretsManager::Secret", + "aws:cdk:cloudformation:props": { + "generateSecretString": {} + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.CfnSecret", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.Secret", + "version": "0.0.0" + } + }, "batch-job-def-from-ecr": { "id": "batch-job-def-from-ecr", "path": "batch-stack/batch-job-def-from-ecr", @@ -1814,6 +1838,50 @@ "fqn": "@aws-cdk/aws-iam.CfnRole", "version": "0.0.0" } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "batch-stack/execution-role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "batch-stack/execution-role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "executionroleDefaultPolicy497F11A3", + "roles": [ + { + "Ref": "executionroleD9A39BE6" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } } }, "constructInfo": { @@ -1849,6 +1917,14 @@ "aws:cdk:cloudformation:props": { "type": "container", "containerProperties": { + "secrets": [ + { + "name": "SECRET", + "valueFrom": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], "image": "docker/whalesay", "executionRoleArn": { "Fn::GetAtt": [ diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.ts b/packages/@aws-cdk/aws-batch/test/integ.batch.ts index 4430cda4a7bf3..6de9a121b9b6d 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.ts +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.ts @@ -2,6 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import * as batch from '../lib/'; @@ -93,6 +94,7 @@ new batch.JobQueue(stack, 'batch-job-fargate-queue', { }); const repo = new ecr.Repository(stack, 'batch-job-repo'); +const secret = new secretsmanager.Secret(stack, 'batch-secret'); new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { container: { @@ -115,5 +117,8 @@ new batch.JobDefinition(stack, 'batch-job-def-fargate', { container: { image: ecs.ContainerImage.fromRegistry('docker/whalesay'), executionRole, + secrets: { + SECRET: ecs.Secret.fromSecretsManager(secret), + }, }, }); diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index 13926b6b80788..addaa5447f6ec 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -1,5 +1,5 @@ import { throws } from 'assert'; -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecs from '@aws-cdk/aws-ecs'; @@ -31,6 +31,12 @@ describe('Batch Job Definition', () => { options: { 'awslogs-region': 'us-east-1' }, }; + const secret = new secretsmanager.Secret(stack, 'test-secret'); + const parameter = ssm.StringParameter.fromSecureStringParameterAttributes(stack, 'test-parameter', { + parameterName: '/name', + version: 1, + }); + jobDefProps = { jobDefinitionName: 'test-job', container: { @@ -38,6 +44,10 @@ describe('Batch Job Definition', () => { environment: { foo: 'bar', }, + secrets: { + SECRET: ecs.Secret.fromSecretsManager(secret), + PARAMETER: ecs.Secret.fromSsmParameter(parameter), + }, jobRole: role, gpuCount: 1, image: ecs.EcrImage.fromRegistry('docker/whalesay'), @@ -82,6 +92,37 @@ describe('Batch Job Definition', () => { Value: 'bar', }, ], + Secrets: [ + { + Name: 'SECRET', + ValueFrom: { + Ref: Match.stringLikeRegexp('^testsecret[0-9A-Z]{8}$'), + }, + }, + { + Name: 'PARAMETER', + ValueFrom: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/name', + ], + ], + }, + }, + ], InstanceType: jobDefProps.container.instanceType ? jobDefProps.container.instanceType.toString() : '', LinuxParameters: {}, LogConfiguration: { @@ -144,6 +185,37 @@ describe('Batch Job Definition', () => { Value: 'bar', }, ], + Secrets: [ + { + Name: 'SECRET', + ValueFrom: { + Ref: Match.stringLikeRegexp('^testsecret[0-9A-Z]{8}$'), + }, + }, + { + Name: 'PARAMETER', + ValueFrom: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/name', + ], + ], + }, + }, + ], ExecutionRoleArn: { 'Fn::GetAtt': [ 'executionroleD9A39BE6',