Skip to content

Commit

Permalink
feat(deadline): add Deadline Secrets Management integration in the Re…
Browse files Browse the repository at this point in the history
…nder Queue (#528)
  • Loading branch information
jericht authored and horsmand committed Aug 21, 2021
1 parent 8c7dda6 commit 48baa18
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 18 deletions.
63 changes: 61 additions & 2 deletions packages/aws-rfdk/lib/deadline/lib/render-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
Ec2TaskDefinition,
LogDriver,
PlacementConstraint,
Scope,
UlimitName,
} from '@aws-cdk/aws-ecs';
import {
Expand Down Expand Up @@ -214,15 +215,23 @@ 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, 15, 0]);

/**
* Regular expression that validates a hostname (portion in front of the subdomain).
*/
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.
Expand Down Expand Up @@ -412,12 +421,34 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
});
this.logGroup.grantWrite(this.asg);

if (props.repository.secretsManagementSettings.enabled) {
const errors = [];
if (props.version.isLessThan(RenderQueue.MINIMUM_SECRETS_MANAGEMENT_VERSION)) {
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');
}
if (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(`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({
image: props.images.remoteConnectionServer,
portNumber: internalPortNumber,
protocol: internalProtocol,
repository: props.repository,
runAsUser: RenderQueue.RCS_USER,
secretsManagementOptions: props.repository.secretsManagementSettings.enabled ? {
credentials: props.repository.secretsManagementSettings.credentials!,
posixUsername: RenderQueue.RCS_USER.username,
} : undefined,
});
this.taskDefinition = taskDefinition;

Expand Down Expand Up @@ -530,6 +561,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
});
}

props.repository.secretsManagementSettings.credentials?.grantRead(this);

this.ecsServiceStabilized = new WaitForStableService(this, 'WaitForStableService', {
service: this.pattern.service,
});
Expand Down Expand Up @@ -648,6 +681,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;

Expand Down Expand Up @@ -685,6 +719,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;
Expand All @@ -701,6 +739,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(
{
Expand Down
7 changes: 6 additions & 1 deletion packages/aws-rfdk/lib/deadline/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -557,7 +562,7 @@ export class Repository extends Construct implements IRepository {
private readonly installerGroup: AutoScalingGroup;

/**
* Deadline Secrets Management settings.
* @inheritdoc
*/
public readonly secretsManagementSettings: SecretsManagementProps;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_ARG_STRING=''
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\""
REPO_ARGS+=("--importrepositorysettings" "true" "--repositorysettingsimportoperation" "append" "--repositorysettingsimportfile" "$DEADLINE_REPOSITORY_SETTINGS_FILE")
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)
Expand All @@ -131,8 +132,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"
SECRET_MANAGEMENT_ARG="--installSecretsManagement true --secretsAdminName \"$SECRET_MANAGEMENT_USER\" --secretsAdminPassword \"$SECRET_MANAGEMENT_PASSWORD\""
echo "Secrets management is enabled. Credentials are stored in secret: $SECRET_MANAGEMENT_ARN"
REPO_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
Expand Down Expand Up @@ -164,7 +167,11 @@ 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
# 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
echo "Changing ownership of Deadline Repository files to UID=$REPOSITORY_OWNER_UID GID=$REPOSITORY_OWNER_GID"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('ConfigureSpotEventPlugin', () => {
repository: new Repository(stack, 'Repository', {
vpc,
version,
secretsManagementSettings: { enabled: false },
}),
trafficEncryption: { externalTLS: { enabled: false } },
version,
Expand Down Expand Up @@ -688,6 +689,7 @@ describe('ConfigureSpotEventPlugin', () => {
repository: new Repository(stack, 'Repository2', {
vpc,
version,
secretsManagementSettings: { enabled: false },
}),
trafficEncryption: { externalTLS: { enabled: false } },
version,
Expand Down Expand Up @@ -910,6 +912,7 @@ describe('ConfigureSpotEventPlugin', () => {
repository: new Repository(newStack, 'Repository', {
vpc,
version,
secretsManagementSettings: { enabled: false },
}),
trafficEncryption: { externalTLS: { enabled: false } },
version,
Expand Down Expand Up @@ -945,6 +948,7 @@ describe('ConfigureSpotEventPlugin', () => {
repository: new Repository(newStack, 'Repository', {
vpc,
version,
secretsManagementSettings: { enabled: false },
}),
trafficEncryption: { externalTLS: { enabled: false } },
version,
Expand Down
Loading

0 comments on commit 48baa18

Please sign in to comment.