From af3b471ca7e7340c1afd17e760dc42790ea9a271 Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Mon, 9 Aug 2021 22:11:40 +0000 Subject: [PATCH 1/7] feat(deadline): add Deadline Secrets Management integration in the Render Queue --- .../lib/deadline/lib/render-queue-ref.ts | 18 +++ .../aws-rfdk/lib/deadline/lib/render-queue.ts | 52 ++++++- .../aws-rfdk/lib/deadline/lib/repository.ts | 7 +- .../scripts/bash/installDeadlineRepository.sh | 2 +- .../lib/deadline/test/render-queue.test.ts | 130 +++++++++++++++++- 5 files changed, 204 insertions(+), 5 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index 7059ac176..76927fb42 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -377,6 +377,24 @@ export interface RenderQueueProps { * @default false */ readonly enableLocalFileCaching?: boolean; + + /** + * The credentials for configuring Deadline Secrets Management on the Render Queue. Providing this value will enable Secrets Management + * on the Render Queue. + * + * The secret must be in the following JSON format: + * + * ``` + * { + * username: string, + * password: string, + * } + * ``` + * + * @see https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/secrets-management/deadline-secrets-management.html + * @default Deadline Secrets Management is not enabled + */ + readonly secretsManagementCredentials?: ISecret; } /** diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index dea2c9830..9241d3296 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -29,6 +29,7 @@ import { Ec2TaskDefinition, LogDriver, PlacementConstraint, + Scope, UlimitName, } from '@aws-cdk/aws-ecs'; import { @@ -220,9 +221,9 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { private static readonly RE_VALID_HOSTNAME = /^[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/i; /** - * UID/GID for the RCS user. + * Information for the RCS user. */ - private static readonly RCS_USER = { uid: 1000, gid: 1000 }; + private static readonly RCS_USER = { uid: 1000, gid: 1000, username: 'ec2-user' }; /** * The principal to grant permissions to. @@ -412,12 +413,31 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { }); this.logGroup.grantWrite(this.asg); + if (props.secretsManagementCredentials !== undefined) { + const errors = []; + if (props.repository.secretsManagementSettings.enabled !== true) { + errors.push('Secrets Management is not enabled on the Repository'); + } + if (props.trafficEncryption?.internalProtocol !== ApplicationProtocol.HTTPS) { + errors.push('The internal protocol on the Render Queue is not HTTPS.'); + } + if (externalProtocol !== ApplicationProtocol.HTTPS) { + errors.push('External TLS on the Render Queue is not enabled.'); + } + if (errors.length > 0) { + throw new Error(`Secrets Management cannot be enabled on the Render Queue for the following reasons:\n${errors.join('\n')}`); + } + } const taskDefinition = this.createTaskDefinition({ image: props.images.remoteConnectionServer, portNumber: internalPortNumber, protocol: internalProtocol, repository: props.repository, runAsUser: RenderQueue.RCS_USER, + secretsManagementOptions: props.secretsManagementCredentials ? { + credentials: props.secretsManagementCredentials, + posixUsername: RenderQueue.RCS_USER.username, + } : undefined, }); this.taskDefinition = taskDefinition; @@ -530,6 +550,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { }); } + props.secretsManagementCredentials?.grantRead(this); + this.ecsServiceStabilized = new WaitForStableService(this, 'WaitForStableService', { service: this.pattern.service, }); @@ -648,6 +670,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { protocol: ApplicationProtocol, repository: IRepository, runAsUser?: { uid: number, gid?: number }, + secretsManagementOptions?: { credentials: ISecret, posixUsername: string }, }) { const { image, portNumber, protocol, repository } = props; @@ -685,6 +708,10 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { environment.RCS_TLS_REQUIRE_CLIENT_CERT = 'no'; } + if (props.secretsManagementOptions !== undefined) { + environment.RCS_SM_CREDENTIALS_URI = props.secretsManagementOptions.credentials.secretArn; + } + // We can ignore this in test coverage because we always use RenderQueue.RCS_USER /* istanbul ignore next */ const user = props.runAsUser ? `${props.runAsUser.uid}:${props.runAsUser.gid}` : undefined; @@ -701,6 +728,27 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { containerDefinition.addMountPoints(connection.readWriteMountPoint); + if (props.secretsManagementOptions !== undefined) { + // Create volume to persist the RSA keypairs generated by Deadline between ECS tasks + // This makes it so subsequent ECS tasks use the same initial Secrets Management identity + const volumeName = 'deadline-user-keypairs'; + taskDefinition.addVolume({ + name: volumeName, + dockerVolumeConfiguration: { + scope: Scope.SHARED, + autoprovision: true, + driver: 'local', + }, + }); + + // Mount the volume into the container at the location where Deadline expects it + containerDefinition.addMountPoints({ + readOnly: false, + sourceVolume: volumeName, + containerPath: `/home/${props.secretsManagementOptions.posixUsername}/.config/.mono/keypairs`, + }); + } + // Increase ulimits containerDefinition.addUlimits( { diff --git a/packages/aws-rfdk/lib/deadline/lib/repository.ts b/packages/aws-rfdk/lib/deadline/lib/repository.ts index 438c6e34f..5c3c9cd34 100644 --- a/packages/aws-rfdk/lib/deadline/lib/repository.ts +++ b/packages/aws-rfdk/lib/deadline/lib/repository.ts @@ -190,6 +190,11 @@ export interface IRepository extends IConstruct { */ readonly version: IVersion; + /** + * Deadline Secrets Management settings. + */ + readonly secretsManagementSettings: SecretsManagementProps; + /** * Configures an ECS Container Instance and Task Definition for deploying a Deadline Client that directly connects to * this repository. @@ -557,7 +562,7 @@ export class Repository extends Construct implements IRepository { private readonly installerGroup: AutoScalingGroup; /** - * Deadline Secrets Management settings. + * @inheritdoc */ public readonly secretsManagementSettings: SecretsManagementProps; diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index bef513bca..579953742 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -132,7 +132,7 @@ if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then exit 1 fi echo "Secret management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN" - SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName \"$SECRET_MANAGEMENT_USER\" --secretsAdminPassword \"$SECRET_MANAGEMENT_PASSWORD\"" + SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName $SECRET_MANAGEMENT_USER --secretsAdminPassword $SECRET_MANAGEMENT_PASSWORD" fi if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 2e8566b4b..f1cf67265 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -35,6 +35,7 @@ import { } from '@aws-cdk/aws-ec2'; import { ContainerImage, + Ec2TaskDefinition, TaskDefinition, } from '@aws-cdk/aws-ecs'; import { @@ -50,7 +51,7 @@ import { import { Bucket, } from '@aws-cdk/aws-s3'; -import { Secret } from '@aws-cdk/aws-secretsmanager'; +import { CfnSecret, ISecret, Secret } from '@aws-cdk/aws-secretsmanager'; import { App, CfnElement, @@ -2731,4 +2732,131 @@ describe('RenderQueue', () => { ), })); }); + + describe('Secrets Management', () => { + let secretsManagementCredentials: ISecret; + let rqSecretsManagementProps: RenderQueueProps; + + beforeEach(() => { + secretsManagementCredentials = new Secret(stack, 'SecretsManagementCredentials'); + rqSecretsManagementProps = { + vpc, + images, + repository, + version: renderQueueVersion, + secretsManagementCredentials, + trafficEncryption: { + internalProtocol: ApplicationProtocol.HTTPS, + externalTLS: { enabled: true }, + }, + }; + }); + + test('throws if secrets management not enabled on repository', () => { + // GIVEN + const secret = new Secret(dependencyStack, 'DeadlineSecretsManagementCredentials'); + const smRepository = new Repository(dependencyStack, 'SecretsManagementRepository', { + vpc, + version, + secretsManagementSettings: { + enabled: false, + credentials: secret, + }, + }); + + // WHEN + expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { + ...rqSecretsManagementProps, + repository: smRepository, + })) + + // THEN + .toThrowError(/Secrets Management is not enabled on the Repository/); + }); + + test('throws if internal protocol is not HTTPS', () => { + // WHEN + expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { + ...rqSecretsManagementProps, + trafficEncryption: { + internalProtocol: ApplicationProtocol.HTTP, + }, + })) + + // THEN + .toThrowError(/The internal protocol on the Render Queue is not HTTPS./); + }); + + test('throws if external TLS is not enabled', () => { + // WHEN + expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { + ...rqSecretsManagementProps, + trafficEncryption: { + externalTLS: { enabled: false }, + }, + })) + + // THEN + .toThrowError(/External TLS on the Render Queue is not enabled./); + }); + + test('grants read permissions to secrets management credentials', () => { + // WHEN + const rq = new RenderQueue(stack, 'SecretsManagementRenderQueue', rqSecretsManagementProps); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: objectLike({ + Statement: arrayWith({ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: stack.resolve((secretsManagementCredentials.node.defaultChild as CfnSecret).ref), + }), + }), + Roles: [stack.resolve((rq.node.tryFindChild('RCSTask') as Ec2TaskDefinition).taskRole.roleName)], + })); + }); + + test('defines secrets management credentials environment variable', () => { + // WHEN + new RenderQueue(stack, 'SecretsManagementRenderQueue', rqSecretsManagementProps); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: arrayWith(objectLike({ + Environment: arrayWith({ + Name: 'RCS_SM_CREDENTIALS_URI', + Value: stack.resolve((secretsManagementCredentials.node.defaultChild as CfnSecret).ref), + }), + })), + })); + }); + + test('creates and mounts docker volume for deadline key pairs', () => { + // WHEN + new RenderQueue(stack, 'SecretsManagementRenderQueue', rqSecretsManagementProps); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: arrayWith(objectLike({ + MountPoints: arrayWith({ + ContainerPath: '/home/ec2-user/.config/.mono/keypairs', + ReadOnly: false, + SourceVolume: 'deadline-user-keypairs', + }), + })), + Volumes: arrayWith({ + DockerVolumeConfiguration: { + Autoprovision: true, + Driver: 'local', + Scope: 'shared', + }, + Name: 'deadline-user-keypairs', + }), + })); + }); + }); }); From c13400887fbd0e725f6f433da9a2b7a790020b74 Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 15:26:54 +0000 Subject: [PATCH 2/7] general cleanup and update unit tests --- .../lib/deadline/lib/render-queue-ref.ts | 18 ---- .../aws-rfdk/lib/deadline/lib/render-queue.ts | 14 +-- .../scripts/bash/installDeadlineRepository.sh | 10 +-- .../test/configure-spot-event-plugin.test.ts | 4 + .../lib/deadline/test/render-queue.test.ts | 86 ++++++++++++------- .../test/spot-event-plugin-fleet.test.ts | 1 + .../test/usage-based-licensing.test.ts | 1 + .../test/worker-configuration.test.ts | 1 + .../lib/deadline/test/worker-fleet.test.ts | 1 + 9 files changed, 73 insertions(+), 63 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index 76927fb42..7059ac176 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -377,24 +377,6 @@ export interface RenderQueueProps { * @default false */ readonly enableLocalFileCaching?: boolean; - - /** - * The credentials for configuring Deadline Secrets Management on the Render Queue. Providing this value will enable Secrets Management - * on the Render Queue. - * - * The secret must be in the following JSON format: - * - * ``` - * { - * username: string, - * password: string, - * } - * ``` - * - * @see https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/secrets-management/deadline-secrets-management.html - * @default Deadline Secrets Management is not enabled - */ - readonly secretsManagementCredentials?: ISecret; } /** diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index 9241d3296..d75fcd422 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -413,12 +413,12 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { }); this.logGroup.grantWrite(this.asg); - if (props.secretsManagementCredentials !== undefined) { + if (props.repository.secretsManagementSettings.enabled) { const errors = []; - if (props.repository.secretsManagementSettings.enabled !== true) { - errors.push('Secrets Management is not enabled on the Repository'); + if (props.repository.secretsManagementSettings.credentials === undefined) { + errors.push('The Repository does not have Secrets Management credentials'); } - if (props.trafficEncryption?.internalProtocol !== ApplicationProtocol.HTTPS) { + if (internalProtocol !== ApplicationProtocol.HTTPS) { errors.push('The internal protocol on the Render Queue is not HTTPS.'); } if (externalProtocol !== ApplicationProtocol.HTTPS) { @@ -434,8 +434,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { protocol: internalProtocol, repository: props.repository, runAsUser: RenderQueue.RCS_USER, - secretsManagementOptions: props.secretsManagementCredentials ? { - credentials: props.secretsManagementCredentials, + secretsManagementOptions: props.repository.secretsManagementSettings.enabled ? { + credentials: props.repository.secretsManagementSettings.credentials!, posixUsername: RenderQueue.RCS_USER.username, } : undefined, }); @@ -550,7 +550,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { }); } - props.secretsManagementCredentials?.grantRead(this); + props.repository.secretsManagementSettings.credentials?.grantRead(this); this.ecsServiceStabilized = new WaitForStableService(this, 'WaitForStableService', { service: this.pattern.service, diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index 579953742..f44b8c012 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -105,17 +105,17 @@ set +x INSTALLER_DB_ARGS_STRING='' for key in "${!INSTALLER_DB_ARGS[@]}"; do INSTALLER_DB_ARGS_STRING=$INSTALLER_DB_ARGS_STRING"${key} ${INSTALLER_DB_ARGS[$key]} "; done -REPOSITORY_SETTINGS_ARG_STRING='' +REPOSITORY_SETTINGS_ARGS=() if [ ! -z "${DEADLINE_REPOSITORY_SETTINGS_FILE+x}" ]; then if [ ! -f "$DEADLINE_REPOSITORY_SETTINGS_FILE" ]; then echo "ERROR: Repository settings file was specified but is not a file: $DEADLINE_REPOSITORY_SETTINGS_FILE." exit 1 else - REPOSITORY_SETTINGS_ARG_STRING="--importrepositorysettings true --repositorysettingsimportoperation append --repositorysettingsimportfile \"$DEADLINE_REPOSITORY_SETTINGS_FILE\"" + REPOSITORY_SETTINGS_ARGS=("--importrepositorysettings" "true" "--repositorysettingsimportoperation" "append" "--repositorysettingsimportfile" "$DEADLINE_REPOSITORY_SETTINGS_FILE") fi fi -SECRET_MANAGEMENT_ARG='' +SECRET_MANAGEMENT_ARGS=() if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then sudo yum install -y jq SM_SECRET_VALUE=$(aws secretsmanager get-secret-value --secret-id=$SECRET_MANAGEMENT_ARN --region=$AWS_REGION) @@ -132,7 +132,7 @@ if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then exit 1 fi echo "Secret management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN" - SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName $SECRET_MANAGEMENT_USER --secretsAdminPassword $SECRET_MANAGEMENT_PASSWORD" + SECRET_MANAGEMENT_ARGS=("--installSecretsManagement" "true" "--secretsAdminName" "$SECRET_MANAGEMENT_USER" "--secretsAdminPassword" "$SECRET_MANAGEMENT_PASSWORD") fi if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then @@ -164,7 +164,7 @@ if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then fi fi -$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${INSTALLER_DB_ARGS_STRING} $REPOSITORY_SETTINGS_ARG_STRING $SECRET_MANAGEMENT_ARG +$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${INSTALLER_DB_ARGS_STRING} "${REPOSITORY_SETTINGS_ARGS[@]}" "${SECRET_MANAGEMENT_ARGS[@]}" if [[ -n "${REPOSITORY_OWNER_UID+x}" ]]; then echo "Changing ownership of Deadline Repository files to UID=$REPOSITORY_OWNER_UID GID=$REPOSITORY_OWNER_GID" diff --git a/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts b/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts index 7566635ee..bd7a6b536 100644 --- a/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts @@ -85,6 +85,7 @@ describe('ConfigureSpotEventPlugin', () => { repository: new Repository(stack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, @@ -688,6 +689,7 @@ describe('ConfigureSpotEventPlugin', () => { repository: new Repository(stack, 'Repository2', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, @@ -910,6 +912,7 @@ describe('ConfigureSpotEventPlugin', () => { repository: new Repository(newStack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, @@ -945,6 +948,7 @@ describe('ConfigureSpotEventPlugin', () => { repository: new Repository(newStack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index f1cf67265..83a849378 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -51,7 +51,10 @@ import { import { Bucket, } from '@aws-cdk/aws-s3'; -import { CfnSecret, ISecret, Secret } from '@aws-cdk/aws-secretsmanager'; +import { + CfnSecret, + Secret, +} from '@aws-cdk/aws-secretsmanager'; import { App, CfnElement, @@ -639,9 +642,14 @@ describe('RenderQueue', () => { beforeEach(() => { // GIVEN isolatedStack = new Stack(app, 'IsolatedStack'); + const nonSmRepository = new Repository(dependencyStack, 'NonSMRepository', { + vpc, + version, + secretsManagementSettings: { enabled: false }, + }); const props: RenderQueueProps = { images, - repository, + repository: nonSmRepository, version: new VersionQuery(isolatedStack, 'Version'), vpc, trafficEncryption: { @@ -803,10 +811,15 @@ describe('RenderQueue', () => { }, signingCertificate: caCert, }); + const nonSmRepository = new Repository(dependencyStack, 'NonSMRepository', { + vpc, + version, + secretsManagementSettings: { enabled: false }, + }); const props: RenderQueueProps = { images, - repository, + repository: nonSmRepository, version: new VersionQuery(isolatedStack, 'Version'), vpc, trafficEncryption: { @@ -1070,9 +1083,14 @@ describe('RenderQueue', () => { vpc, zoneName: ZONE_NAME, }); + const nonSmRepository = new Repository(dependencyStack, 'NonSMRepository', { + vpc, + version, + secretsManagementSettings: { enabled: false }, + }); const props: RenderQueueProps = { images, - repository, + repository: nonSmRepository, version: new VersionQuery(isolatedStack, 'Version'), vpc, hostname: { @@ -1999,9 +2017,14 @@ describe('RenderQueue', () => { beforeEach(() => { // GIVEN isolatedStack = new Stack(app, 'IsolatedStack'); + const nonSmRepository = new Repository(dependencyStack, 'NonSMRepository', { + vpc, + version, + secretsManagementSettings: { enabled: false }, + }); const props: RenderQueueProps = { images, - repository, + repository: nonSmRepository, trafficEncryption: { externalTLS: { enabled: false } }, version: new VersionQuery(isolatedStack, 'Version'), vpc, @@ -2100,9 +2123,14 @@ describe('RenderQueue', () => { }); const hostname = 'testrq'; const isolatedStack = new Stack(app, 'IsolatedStack'); + const nonSmRepository = new Repository(dependencyStack, 'NonSMRepository', { + vpc, + version, + secretsManagementSettings: { enabled: false }, + }); const props: RenderQueueProps = { images, - repository, + repository: nonSmRepository, version: new VersionQuery(isolatedStack, 'Version'), vpc, hostname: { @@ -2734,17 +2762,14 @@ describe('RenderQueue', () => { }); describe('Secrets Management', () => { - let secretsManagementCredentials: ISecret; let rqSecretsManagementProps: RenderQueueProps; beforeEach(() => { - secretsManagementCredentials = new Secret(stack, 'SecretsManagementCredentials'); rqSecretsManagementProps = { vpc, images, repository, version: renderQueueVersion, - secretsManagementCredentials, trafficEncryption: { internalProtocol: ApplicationProtocol.HTTPS, externalTLS: { enabled: true }, @@ -2752,28 +2777,6 @@ describe('RenderQueue', () => { }; }); - test('throws if secrets management not enabled on repository', () => { - // GIVEN - const secret = new Secret(dependencyStack, 'DeadlineSecretsManagementCredentials'); - const smRepository = new Repository(dependencyStack, 'SecretsManagementRepository', { - vpc, - version, - secretsManagementSettings: { - enabled: false, - credentials: secret, - }, - }); - - // WHEN - expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { - ...rqSecretsManagementProps, - repository: smRepository, - })) - - // THEN - .toThrowError(/Secrets Management is not enabled on the Repository/); - }); - test('throws if internal protocol is not HTTPS', () => { // WHEN expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { @@ -2800,6 +2803,23 @@ describe('RenderQueue', () => { .toThrowError(/External TLS on the Render Queue is not enabled./); }); + test('throws if repository does not have SM credentials', () => { + // WHEN + expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { + ...rqSecretsManagementProps, + repository: { + ...repository, + secretsManagementSettings: { + ...repository.secretsManagementSettings, + credentials: undefined, + }, + } as Repository, + })) + + // THEN + .toThrowError(/The Repository does not have Secrets Management credentials/); + }); + test('grants read permissions to secrets management credentials', () => { // WHEN const rq = new RenderQueue(stack, 'SecretsManagementRenderQueue', rqSecretsManagementProps); @@ -2813,7 +2833,7 @@ describe('RenderQueue', () => { 'secretsmanager:DescribeSecret', ], Effect: 'Allow', - Resource: stack.resolve((secretsManagementCredentials.node.defaultChild as CfnSecret).ref), + Resource: stack.resolve((repository.secretsManagementSettings.credentials!.node.defaultChild as CfnSecret).ref), }), }), Roles: [stack.resolve((rq.node.tryFindChild('RCSTask') as Ec2TaskDefinition).taskRole.roleName)], @@ -2829,7 +2849,7 @@ describe('RenderQueue', () => { ContainerDefinitions: arrayWith(objectLike({ Environment: arrayWith({ Name: 'RCS_SM_CREDENTIALS_URI', - Value: stack.resolve((secretsManagementCredentials.node.defaultChild as CfnSecret).ref), + Value: stack.resolve((repository.secretsManagementSettings.credentials!.node.defaultChild as CfnSecret).ref), }), })), })); diff --git a/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts index d41860546..78c52989a 100644 --- a/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts @@ -99,6 +99,7 @@ describe('SpotEventPluginFleet', () => { repository: new Repository(stack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, diff --git a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts index fec8b95ba..46319afd4 100644 --- a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts @@ -86,6 +86,7 @@ describe('UsageBasedLicensing', () => { repository: new Repository(dependencyStack, 'RepositoryNonDefault', { vpc, version: versionedInstallers, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version: versionedInstallers, diff --git a/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts index 96140c6d0..20a0d75f9 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts @@ -328,6 +328,7 @@ describe('Test WorkerInstanceConfiguration connect to RenderQueue', () => { repository: new Repository(stack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, }); diff --git a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts index d7914d27a..fe74e17d5 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts @@ -89,6 +89,7 @@ beforeEach(() => { repository: new Repository(stack, 'Repository', { vpc, version, + secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, From 39577f947d46f79fc5c38e268a982cbb7aec3be5 Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 16:10:43 +0000 Subject: [PATCH 3/7] add message when sm is not enabled on repository --- .../lib/deadline/scripts/bash/installDeadlineRepository.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index f44b8c012..4c7626cb4 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -18,7 +18,7 @@ Required arguments: Optional arguments -s Deadline Repository settings file to import. -o The UID[:GID] that this script will chown the Repository files for. If GID is not specified, it defults to be the same as UID. - -c Secret management admin credentials ARN. If this parameter is specified, secrets management will be enabled. + -c Secrets management admin credentials ARN. If this parameter is specified, secrets management will be enabled. -r Region where stacks are deployed. Required to get secret management credentials." while getopts "i:p:v:s:o:c:r:" opt; do @@ -131,8 +131,10 @@ if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then echo "ERROR: Admin password is too weak. It must be at least 8 characters long and contain at least one lowercase letter, one uppercase letter, one symbol and one digit." exit 1 fi - echo "Secret management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN" + echo "Secrets management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN" SECRET_MANAGEMENT_ARGS=("--installSecretsManagement" "true" "--secretsAdminName" "$SECRET_MANAGEMENT_USER" "--secretsAdminPassword" "$SECRET_MANAGEMENT_PASSWORD") +else + echo "Secrets management is not enabled." fi if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then From 84efb6845b6ed9d852c895a0ec990a9dd8511efe Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 19:09:41 +0000 Subject: [PATCH 4/7] fix repository installer script --- .../scripts/bash/installDeadlineRepository.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index 4c7626cb4..ccd6f5749 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -102,20 +102,21 @@ chmod +x $REPO_INSTALLER set +x -INSTALLER_DB_ARGS_STRING='' -for key in "${!INSTALLER_DB_ARGS[@]}"; do INSTALLER_DB_ARGS_STRING=$INSTALLER_DB_ARGS_STRING"${key} ${INSTALLER_DB_ARGS[$key]} "; done +REPO_ARGS=() + +for key in "${!INSTALLER_DB_ARGS[@]}"; do + REPO_ARGS+=("${key}" "${INSTALLER_DB_ARGS[$key]}") +done -REPOSITORY_SETTINGS_ARGS=() if [ ! -z "${DEADLINE_REPOSITORY_SETTINGS_FILE+x}" ]; then if [ ! -f "$DEADLINE_REPOSITORY_SETTINGS_FILE" ]; then echo "ERROR: Repository settings file was specified but is not a file: $DEADLINE_REPOSITORY_SETTINGS_FILE." exit 1 else - REPOSITORY_SETTINGS_ARGS=("--importrepositorysettings" "true" "--repositorysettingsimportoperation" "append" "--repositorysettingsimportfile" "$DEADLINE_REPOSITORY_SETTINGS_FILE") + REPO_ARGS+=("--importrepositorysettings" "true" "--repositorysettingsimportoperation" "append" "--repositorysettingsimportfile" "$DEADLINE_REPOSITORY_SETTINGS_FILE") fi fi -SECRET_MANAGEMENT_ARGS=() if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then sudo yum install -y jq SM_SECRET_VALUE=$(aws secretsmanager get-secret-value --secret-id=$SECRET_MANAGEMENT_ARN --region=$AWS_REGION) @@ -132,7 +133,7 @@ if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then exit 1 fi echo "Secrets management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN" - SECRET_MANAGEMENT_ARGS=("--installSecretsManagement" "true" "--secretsAdminName" "$SECRET_MANAGEMENT_USER" "--secretsAdminPassword" "$SECRET_MANAGEMENT_PASSWORD") + REPO_ARGS+=("--installSecretsManagement" "true" "--secretsAdminName" "$SECRET_MANAGEMENT_USER" "--secretsAdminPassword" "$SECRET_MANAGEMENT_PASSWORD") else echo "Secrets management is not enabled." fi @@ -166,7 +167,7 @@ if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then fi fi -$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${INSTALLER_DB_ARGS_STRING} "${REPOSITORY_SETTINGS_ARGS[@]}" "${SECRET_MANAGEMENT_ARGS[@]}" +$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${REPO_ARGS[@]+"${REPO_ARGS[@]}"} if [[ -n "${REPOSITORY_OWNER_UID+x}" ]]; then echo "Changing ownership of Deadline Repository files to UID=$REPOSITORY_OWNER_UID GID=$REPOSITORY_OWNER_GID" From 179d09ef02e036548f3ff978980fb74a4bb4b3e7 Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 20:24:03 +0000 Subject: [PATCH 5/7] improve secrets management validation on render queue --- .../aws-rfdk/lib/deadline/lib/render-queue.ts | 11 ++++++++++- .../lib/deadline/test/render-queue.test.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index d75fcd422..9e536d85b 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -215,6 +215,11 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { */ private static readonly MINIMUM_LOAD_BALANCING_VERSION = new Version([10, 1, 10, 0]); + /** + * The minimum Deadline version required to enable Deadline Secrets Management on the Render Queue. + */ + private static readonly MINIMUM_SECRETS_MANAGEMENT_VERSION = new Version([10, 1, 18, 0]); + /** * Regular expression that validates a hostname (portion in front of the subdomain). */ @@ -223,6 +228,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { /** * Information for the RCS user. */ + private static readonly RCS_USER = { uid: 1000, gid: 1000, username: 'ec2-user' }; /** @@ -415,6 +421,9 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { if (props.repository.secretsManagementSettings.enabled) { const errors = []; + if (props.version.isLessThan(RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION)) { + errors.push(`The supplied Deadline version is lower than the minimum required version: ${RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION.toString()}`); + } if (props.repository.secretsManagementSettings.credentials === undefined) { errors.push('The Repository does not have Secrets Management credentials'); } @@ -425,7 +434,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { errors.push('External TLS on the Render Queue is not enabled.'); } if (errors.length > 0) { - throw new Error(`Secrets Management cannot be enabled on the Render Queue for the following reasons:\n${errors.join('\n')}`); + throw new Error(`Deadline Secrets Management is enabled on the supplied Repository but cannot be enabled on the Render Queue for the following reasons:\n${errors.join('\n')}`); } } const taskDefinition = this.createTaskDefinition({ diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 83a849378..90cd1052d 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -2820,6 +2820,21 @@ describe('RenderQueue', () => { .toThrowError(/The Repository does not have Secrets Management credentials/); }); + test('throws if deadline version is too low', () => { + // GIVEN + const oldVersion = new VersionQuery(new Stack(app, 'OldDeadlineVersionStack'), 'OldDeadlineVersion', { version: '10.0.0.0' }); + + // WHEN + expect(() => new RenderQueue(stack, 'SecretsManagementRenderQueue', { + ...rqSecretsManagementProps, + version: oldVersion, + })) + + // THEN + /* eslint-disable-next-line dot-notation */ + .toThrowError(`The supplied Deadline version is lower than the minimum required version: ${RenderQueue['MINIMUM_SECRETS_MANAGEMENT_VERSION'].toString()}`); + }); + test('grants read permissions to secrets management credentials', () => { // WHEN const rq = new RenderQueue(stack, 'SecretsManagementRenderQueue', rqSecretsManagementProps); From 8bd66a4be0fe808e535b160380d159309e3bf1c3 Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 21:15:09 +0000 Subject: [PATCH 6/7] add comment explaining bash array expansion syntax --- .../lib/deadline/scripts/bash/installDeadlineRepository.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index ccd6f5749..f4deb1349 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -167,6 +167,10 @@ if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then fi fi +# The syntax ${array[@]+"${array[@]}"} is a way to get around the expansion of an empty array raising an unbound variable error since this script +# sets the "u" shell option above. This is a use of the ${parameter+word} shell expansion. If the value of "parameter" is unset, nothing will be +# substituted in its place. If "parameter" is set, then the value of "word" is used, which is the expansion of the populated array. +# Since bash treats the expansion of an empty array as an unset variable, we can use this pattern expand the array only if it is populated. $REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${REPO_ARGS[@]+"${REPO_ARGS[@]}"} if [[ -n "${REPOSITORY_OWNER_UID+x}" ]]; then From 4cfa0aaa54f22ffd1ed19356a0cddddd873137ca Mon Sep 17 00:00:00 2001 From: Jericho Tolentino Date: Thu, 19 Aug 2021 21:34:09 +0000 Subject: [PATCH 7/7] update minimum version and improve related error message --- packages/aws-rfdk/lib/deadline/lib/render-queue.ts | 6 ++++-- packages/aws-rfdk/lib/deadline/test/render-queue.test.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index 9e536d85b..5922d457e 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -215,10 +215,12 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { */ private static readonly MINIMUM_LOAD_BALANCING_VERSION = new Version([10, 1, 10, 0]); + // TODO: Update this with the version of Deadline that includes the changes for RFDK Secrets Management. + // This is a temporary minimum version until this feature branch is merged /** * The minimum Deadline version required to enable Deadline Secrets Management on the Render Queue. */ - private static readonly MINIMUM_SECRETS_MANAGEMENT_VERSION = new Version([10, 1, 18, 0]); + private static readonly MINIMUM_SECRETS_MANAGEMENT_VERSION = new Version([10, 1, 15, 0]); /** * Regular expression that validates a hostname (portion in front of the subdomain). @@ -422,7 +424,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { if (props.repository.secretsManagementSettings.enabled) { const errors = []; if (props.version.isLessThan(RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION)) { - errors.push(`The supplied Deadline version is lower than the minimum required version: ${RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION.toString()}`); + errors.push(`The supplied Deadline version (${props.version.versionString}) is lower than the minimum required version: ${RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION.toString()}`); } if (props.repository.secretsManagementSettings.credentials === undefined) { errors.push('The Repository does not have Secrets Management credentials'); diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 90cd1052d..893637e2c 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -2832,7 +2832,7 @@ describe('RenderQueue', () => { // THEN /* eslint-disable-next-line dot-notation */ - .toThrowError(`The supplied Deadline version is lower than the minimum required version: ${RenderQueue['MINIMUM_SECRETS_MANAGEMENT_VERSION'].toString()}`); + .toThrowError(`The supplied Deadline version (${oldVersion.versionString}) is lower than the minimum required version: ${RenderQueue['MINIMUM_SECRETS_MANAGEMENT_VERSION'].toString()}`); }); test('grants read permissions to secrets management credentials', () => {