Skip to content

Commit

Permalink
feat: add Secret Management support for repository
Browse files Browse the repository at this point in the history
  • Loading branch information
kozlove-aws committed Aug 11, 2021
1 parent 7268ab8 commit fc7fc68
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 2 deletions.
69 changes: 69 additions & 0 deletions packages/aws-rfdk/lib/deadline/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ import {
import {
Asset,
} from '@aws-cdk/aws-s3-assets';
import {
ISecret,
Secret,
} from '@aws-cdk/aws-secretsmanager';
import {
Annotations,
Construct,
Expand Down Expand Up @@ -271,6 +275,34 @@ 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.
* The contents of this secret must be a JSON document with the keys "username" and "password". ex:
* {
* "username": <admin user name>,
* "password": <admin user password>,
* }
* Password should contain at least one lowercase letter, one uppercase letter, one symbol and one number.
*
* @default: A random username and password will be generated in a Secret with ID `SMAdminUser` and will need to be retrieved from AWS Secrets Manager if it is needed
*/
readonly credentials?: ISecret;
}

/**
* Properties for the Deadline repository
*/
Expand Down Expand Up @@ -387,6 +419,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
}

/**
Expand Down Expand Up @@ -508,6 +549,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);

Expand All @@ -526,6 +572,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,
Expand Down Expand Up @@ -934,6 +997,12 @@ export class Repository extends Construct implements IRepository {
'-v', version.linuxFullVersionString(),
];

if (this.secretsManagementSettings.enabled) {
installerArgs.push('-r', Stack.of(this.secretsManagementSettings.credentials ?? this).region);
this.secretsManagementSettings.credentials?.grantRead(installerGroup);
installerArgs.push('-c', this.secretsManagementSettings.credentials?.secretArn ?? '');
}

if (settings) {
const repositorySettingsFilePath = installerGroup.userData.addS3DownloadCommand({
bucket: settings.bucket,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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.
-r Region where stacks are deployed. Required to get secret management credentials.

while getopts "i:p:v:s:o:" opt; do
case $opt in
Expand All @@ -31,6 +33,10 @@ while getopts "i:p:v:s:o:" opt; do
;;
o) DEADLINE_REPOSITORY_OWNER="$OPTARG"
;;
c) SECRET_MANAGEMENT_ARN="$OPTARG"
;;
r) AWS_REGION="$OPTARG"
;;
/?)
echo "$USAGE"
exit 1
Expand Down Expand Up @@ -109,6 +115,39 @@ if [ ! -z "${DEADLINE_REPOSITORY_SETTINGS_FILE+x}" ]; then
fi
fi

SECRET_MANAGEMENT_ARG=''
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)
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")

len=$(echo ${#SECRET_MANAGEMENT_PASSWORD})
if test $len -ge 8 ; then
echo "$SECRET_MANAGEMENT_PASSWORD" | grep -q [0-9]
if test $? -eq 0 ; then
echo "$SECRET_MANAGEMENT_PASSWORD" | grep -q [A-Z]
if test $? -eq 0 ; then
echo "$SECRET_MANAGEMENT_PASSWORD" | grep -q [a-z]
if test $? -eq 0 ; then
echo "$SECRET_MANAGEMENT_PASSWORD" | grep -q [~,.,:,@,!,\#,%,*,_,+,-,=,?]
if test $? -eq 0 ; then
SM_STRONG_PASSWORD='true'
fi
fi
fi
fi
fi
if [ -z "${SM_STRONG_PASSWORD+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"
SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName \"$SECRET_MANAGEMENT_USER\" --secretsAdminPassword \"$SECRET_MANAGEMENT_PASSWORD\""
fi

if [[ -n "${DEADLINE_REPOSITORY_OWNER+x}" ]]; then
if [[ ! "$DEADLINE_REPOSITORY_OWNER" =~ ^[0-9]+(:[0-9]+)?$ ]]; then
echo "ERROR: Deadline Repository owner is invalid: ${DEADLINE_REPOSITORY_OWNER}"
Expand Down Expand Up @@ -138,7 +177,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
$REPO_INSTALLER --mode unattended --setpermissions false --prefix "$PREFIX" --installmongodb false --backuprepo false ${INSTALLER_DB_ARGS_STRING} $REPOSITORY_SETTINGS_ARG_STRING $SECRET_MANAGEMENT_ARG

if [[ -n "${REPOSITORY_OWNER_UID+x}" ]]; then
echo "Changing ownership of Deadline Repository files to UID=$REPOSITORY_OWNER_UID GID=$REPOSITORY_OWNER_GID"
Expand Down
59 changes: 58 additions & 1 deletion packages/aws-rfdk/lib/deadline/test/repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1260,3 +1261,59 @@ test('IMountableLinuxFilesystem.usesUserPosixPermissions() = false does not chan
// THEN
expect(script).not.toMatch('-o 1000:1000');
});

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);
const installerGroup = repository.node.tryFindChild('Installer') as AutoScalingGroup;
expect(installerGroup.userData.render()).toContain(`--installSecretsManagement true ${stack.region} ${expectedCredentials.secretArn}`);
});

test('secret manager is enabled by default', () => {
// WHEN
const repository = new Repository(stack, 'Repository', {
vpc,
version,
});

// THEN
expect(repository.secretsManagementSettings.enabled).toBeTruthy();
expect(repository.secretsManagementSettings.credentials).toBeDefined();
});

test('credentials are undefined when secrets management is disabled', () => {
// WHEN
const repository = new Repository(stack, 'Repository', {
vpc,
version,
secretsManagementSettings: {
enabled: false,
},
});

// THEN
expect(repository.secretsManagementSettings.credentials).toBeUndefined();
});

0 comments on commit fc7fc68

Please sign in to comment.