Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deadline): add Deadline Secrets Management integration in the Render Queue #528

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
horsmand marked this conversation as resolved.
Show resolved Hide resolved
// 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