From db337ff86e05e186ae1f7a42180e4cb9ffa42169 Mon Sep 17 00:00:00 2001 From: Eugene Kozlov Date: Thu, 29 Jul 2021 23:08:34 +0000 Subject: [PATCH] feat: add Secret Management support for repository --- .../aws-rfdk/lib/deadline/lib/repository.ts | 63 +++++++++++++++++++ .../scripts/bash/installDeadlineRepository.sh | 26 +++++++- .../lib/deadline/test/repository.test.ts | 45 ++++++++++++- 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/packages/aws-rfdk/lib/deadline/lib/repository.ts b/packages/aws-rfdk/lib/deadline/lib/repository.ts index db7d4a24f..90585a217 100644 --- a/packages/aws-rfdk/lib/deadline/lib/repository.ts +++ b/packages/aws-rfdk/lib/deadline/lib/repository.ts @@ -45,6 +45,10 @@ import { import { Asset, } from '@aws-cdk/aws-s3-assets'; +import { + ISecret, + Secret, +} from '@aws-cdk/aws-secretsmanager'; import { Annotations, Construct, @@ -271,6 +275,27 @@ export interface RepositorySecurityGroupsOptions { readonly installer?: ISecurityGroup; } +/** + * Settings used by Deadline Secrets Management, a feature introduced in Deadline 10.1.10 for securely managing storage + * and access of Secrets for your render farm. + * More details at: + * https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/secrets-management/deadline-secrets-management.html + * Using Secrets Management requires TLS to be enabled between the RenderQueue and its clients. If this feature is enabled, the + * `externalTLS` on the `RenderQueueTrafficEncryptionProps` interface on the RenderQueue cannot be disabled. + */ +export interface SecretsManagementProps { + /** + * Whether or not to enable the Secrets Management feature. + * @default true + */ + readonly enabled?: boolean; + /** + * A Secret containing the username and password to use for the admin role + * @default: A random username and password will be generated in a Secret and will need to be retrieved from AWS Secrets Manager if it is needed + */ + readonly credentials?: ISecret; +} + /** * Properties for the Deadline repository */ @@ -387,6 +412,15 @@ export interface RepositoryProps { * @default Repository settings are not imported. */ readonly repositorySettings?: Asset; + + /** + * Define the settings used by Deadline Secrets Management, a feature introduced in Deadline 10.1.10 for securely managing storage + * and access of Secrets for your render farm. + * More details at: + * https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/secrets-management/deadline-secrets-management.html + * @default: Secrets Management will be enabled and a username and password will be automatically generated if none are supplied. + */ + readonly secretsManagementSettings?: SecretsManagementProps } /** @@ -501,6 +535,11 @@ export class Repository extends Construct implements IRepository { */ private readonly installerGroup: AutoScalingGroup; + /** + * Deadline Secrets Management settings. + */ + public readonly secretsManagementSettings: SecretsManagementProps + constructor(scope: Construct, id: string, props: RepositoryProps) { super(scope, id); @@ -519,6 +558,23 @@ export class Repository extends Construct implements IRepository { this.version = props.version; + this.secretsManagementSettings = { + enabled: props.secretsManagementSettings?.enabled ?? true, + credentials: props.secretsManagementSettings?.credentials ?? + ((props.secretsManagementSettings?.enabled ?? true) ? new Secret(this, 'SMAdminUser', { + description: 'Admin credentials for Secret Management', + generateSecretString: { + excludeCharacters: '\"$&\'()-/<>[\\]\`{|}', + includeSpace: false, + passwordLength: 24, + requireEachIncludedType: true, + + generateStringKey: 'password', + secretStringTemplate: JSON.stringify({ username: 'admin' }), + }, + }) : undefined), + }; + this.fileSystem = props.fileSystem ?? (() => { const fs = new EfsFileSystem(this, 'FileSystem', { vpc: props.vpc, @@ -923,6 +979,13 @@ export class Repository extends Construct implements IRepository { version.linuxFullVersionString(), ]; + if (this.secretsManagementSettings.enabled) { + installerArgs.push('true'); + installerArgs.push(Stack.of(this).region); + this.secretsManagementSettings.credentials?.grantRead(installerGroup); + installerArgs.push(this.secretsManagementSettings.credentials?.secretArn ?? ''); + } + if (settings) { const repositorySettingsFilePath = installerGroup.userData.addS3DownloadCommand({ bucket: settings.bucket, diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index e6d0491fa..ac5434e53 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -8,7 +8,10 @@ # $1: s3 path for the deadline repository installer. # $2: Path where deadline repository needs to be installed. # $3: Deadline Repository Version being installed. -# $4: (Optional) Deadline Repository settings file to import. +# $4: (Optional) Secret management is enabled. +# $5: (Optional) Region where stacks are deployed. +# $6: (Optional) Secret management admin credentials ARN. +# $7: (Optional) Deadline Repository settings file to import. # exit when any command fails set -xeuo pipefail @@ -16,7 +19,14 @@ set -xeuo pipefail S3PATH=$1 PREFIX=$2 DEADLINE_REPOSITORY_VERSION=$3 -DEADLINE_REPOSITORY_SETTINGS_FILE=${4:-} +SECRET_MANAGEMENT_ENABLED=${4:-"false"} +if [ "$SECRET_MANAGEMENT_ENABLED" == "true" ]; then + AWS_REGION=${5} + SECRET_MANAGEMENT_ARN=${6} + DEADLINE_REPOSITORY_SETTINGS_FILE=${7:-} +else + DEADLINE_REPOSITORY_SETTINGS_FILE=$SECRET_MANAGEMENT_ENABLED +fi shift;shift; # check if repository is already installed at the given path @@ -82,7 +92,17 @@ if [ ! -z "$DEADLINE_REPOSITORY_SETTINGS_FILE" ]; 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='' +if [ "$SECRET_MANAGEMENT_ENABLED" == "true" ]; then + sudo yum install -y jq + SM_SECRET_VALUE=$(aws secretsmanager get-secret-value --secret-id=$SECRET_MANAGEMENT_ARN --region=$AWS_REGION) + SM_SECRET_STRING=$(jq -r '.SecretString' <<< "$SM_SECRET_VALUE") + SECRET_MANAGEMENT_USER=$(jq -r '.username' <<< "$SM_SECRET_STRING") + SECRET_MANAGEMENT_PASSWORD=$(jq -r '.password' <<< "$SM_SECRET_STRING") + SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName $SECRET_MANAGEMENT_USER --secretsAdminPassword $SECRET_MANAGEMENT_PASSWORD" +fi + +$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${INSTALLER_DB_ARGS_STRING} $REPOSITORY_SETTINGS_ARG_STRING $SECRET_MANAGEMENT_ARG set -x diff --git a/packages/aws-rfdk/lib/deadline/test/repository.test.ts b/packages/aws-rfdk/lib/deadline/test/repository.test.ts index 5bef7c81b..1502d5a06 100644 --- a/packages/aws-rfdk/lib/deadline/test/repository.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/repository.test.ts @@ -36,6 +36,7 @@ import { } from '@aws-cdk/aws-efs'; import { Bucket } from '@aws-cdk/aws-s3'; import { Asset } from '@aws-cdk/aws-s3-assets'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; import { App, CfnElement, @@ -1069,7 +1070,7 @@ describe('tagging', () => { 'AWS::EC2::SecurityGroup': 3, 'AWS::DocDB::DBClusterParameterGroup': 1, 'AWS::DocDB::DBSubnetGroup': 1, - 'AWS::SecretsManager::Secret': 1, + 'AWS::SecretsManager::Secret': 2, 'AWS::DocDB::DBCluster': 1, 'AWS::DocDB::DBInstance': 1, 'AWS::IAM::Role': 1, @@ -1224,3 +1225,45 @@ test('imports repository settings', () => { const installerGroup = repository.node.tryFindChild('Installer') as AutoScalingGroup; expect(installerGroup.userData.render()).toContain(`aws s3 cp '${repositorySettings.s3ObjectUrl}'`); }); + +test('secret manager enabled', () => { + // GIVEN + const expectedCredentials = new Secret(stack, 'CustomSMAdminUser', { + description: 'Custom admin credentials for the Secret Management', + generateSecretString: { + excludeCharacters: '\"$&\'()-/<>[\\]\`{|}', + includeSpace: false, + passwordLength: 24, + requireEachIncludedType: true, + generateStringKey: 'password', + secretStringTemplate: JSON.stringify({ username: 'admin' }), + }, + }); + + // WHEN + const repository = new Repository(stack, 'Repository', { + vpc, + version, + secretsManagementSettings: { + enabled: true, + credentials: expectedCredentials, + }, + }); + + // THEN + expect(repository.secretsManagementSettings.credentials).toBe(expectedCredentials); +}); + +test('secret manager disabled', () => { + // WHEN + const repository = new Repository(stack, 'Repository', { + vpc, + version, + secretsManagementSettings: { + enabled: false, + }, + }); + + // THEN + expect(repository.secretsManagementSettings.credentials).toBeUndefined(); +}); \ No newline at end of file