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): use TLS between RenderQueue and clients by default #491

Merged
merged 6 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
from aws_rfdk.deadline import (
AwsThinkboxEulaAcceptance,
RenderQueue,
RenderQueueExternalTLSProps,
RenderQueueTrafficEncryptionProps,
Repository,
RepositoryRemovalPolicies,
ThinkboxDockerImages,
Expand Down
41 changes: 41 additions & 0 deletions packages/aws-rfdk/docs/upgrade/upgrading-0.37.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Upgrading to RFDK v0.37.x or Newer
jusiskin marked this conversation as resolved.
Show resolved Hide resolved

Starting in RFDK v0.37.0, the default for TLS between the render queue and its clients, which is configured using the `RenderQueueExternalTLSProps` interface that the `RenderQueue` construct takes as a part of its constructor props, is now set to be enabled.

## Upgrading Farms Already Using TLS

If you are already setting fields on the `RenderQueueExternalTLSProps` for the Render Queue, no action is required. Redeploying your render farm after upgrading your version of RFDK should have no effect.

## Upgrading Farms Not Using TLS
jusiskin marked this conversation as resolved.
Show resolved Hide resolved

### RenderQueue Changes

Versions of RFDK prior to 0.37.0 had internal TLS between the load balancer and its backing services on by default. This is configurable with the `internalProtocol` field on the `RenderQueueTrafficEncryptionProps` interface. This default was left as-is, so upgrading RFDK will have no effect on the protocol those backing services were already using and they will not need to be replaced. The TLS being enabled by default is between the listener on the load balancer and any Deadline clients that are connecting to it, which is configurable with the `externalProtocol` property on the `RenderQueueTrafficEncryptionProps` interface.

There will be a few new constructs deployed to your farm:
1. A `PrivateHostedZone` will be created if you do not supply your own. We set the default domain to `aws-rfdk.com`, which we have registered and suggest that you use if you do not have your own registered domain. [RFC 6762](https://datatracker.ietf.org/doc/html/rfc6762#appendix-G) recommends against using any unregistered top-level domains.
1. A self-signed X509 certificate will be generated using OpenSSL and that will then be used to sign a certificate that the Render Queue will use for TLS. Specifically, the certificate will be passed to the Application Listener for the Application Load Balancer that the Render Queue creates. Additional details about how RFDK uses TLS can the built-in certificate management can be found in the developer guide for [Encryption in transit](https://docs.aws.amazon.com/rfdk/latest/guide/security-encrypt-in-transit.html).

These new constructs will require the Render Queue load balancer's listener to need replacing, but the load balancer itself and the backing services it redirects traffic to will not need to be changed.

### WorkerInstanceFleet Changes
jusiskin marked this conversation as resolved.
Show resolved Hide resolved

Since the endpoint and port the listener on the load balancer uses will be changed, and the TLS will require any clients connecting to verify its certificate, any stacks that contain dependencies on the Render Queue will first need to be destroyed. If you are using a tiered architecture similar to what we recommend in our documentation, this would include any `WorkerInstanceFleet` constructs that are in a separate stack from the `RenderQueue`. The `WorkerInstanceFleet` constructs configure their connection to the Render Queue during their start-up. Running `cdk destroy "ComputeTier"` (or whatever name you gave your stack containing the workers) to destroy any worker fleets before running `cdk deploy "*"` to redeploy the entire farm should

The script used to initialize any workers deployed by the farm will also be updated to change the endpoint and port that they use to connect to the Render Queue, and the workers will also need the certificate chain to verify the load balancer (in the default case the cerificate chain only includes the self-signed certificate).

## Disabling External TLS

While we strongly suggest farms be upgraded to use TLS, it is possible to override the new default and keep a farm using HTTP instead. To do this, there is an `enabled` field on the `RenderQueueExternalTLSProps` that can be set to false. This will prevent the farm from automatically upgrading the protocol until you decide you're ready. Here's an example of creating a Render Queue with TLS disabled:

```ts
new RenderQueue(this, 'RenderQueue', {
vpc,
images,
repository,
version,
trafficEncryption: {
externalTLS: { enabled: false },
},
});
```
4 changes: 2 additions & 2 deletions packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export interface RenderQueueExternalTLSProps {

/**
* The ACM certificate that will be used for establishing incoming external TLS connections to the RenderQueue.
* @default If rfdkCertificate and acmCertificate are both not provided, an rfdkCertificate will be generated and used.
* @default If rfdkCertificate and acmCertificate are both not provided when TLS is enabled, an rfdkCertificate will be generated and used.
*/
readonly acmCertificate?: ICertificate;

Expand All @@ -175,7 +175,7 @@ export interface RenderQueueExternalTLSProps {
/**
* The parameters for an X509 Certificate that will be imported into ACM then used by the RenderQueue.
*
* @default If rfdkCertificate and acmCertificate are both not provided, an rfdkCertificate will be generated and used.
* @default If rfdkCertificate and acmCertificate are both not provided when TLS is enabled, an rfdkCertificate will be generated and used.
*/
readonly rfdkCertificate?: IX509CertificatePem;
}
Expand Down
59 changes: 26 additions & 33 deletions packages/aws-rfdk/lib/deadline/lib/render-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,6 @@ export interface IRenderQueue extends IConstruct, IConnectable {
configureClientInstance(params: InstanceConnectOptions): void;
}

/**
* Interface for information about the render queue's domain.
*/
interface DomainInfo {
/**
* The private hosted zone that the render queue's load balancer will be placed in.
*/
readonly domainZone: IPrivateHostedZone;

/**
* The fully qualified domain name that will be given to the load balancer in the private
* hosted zone.
*/
readonly fullyQualifiedDomainName: string;
}

/**
* Interface for information about the render queue's TLS configuration
*/
Expand All @@ -144,9 +128,15 @@ interface TlsInfo {
readonly certChain: ISecret;

/**
* The information about the domain for the render queue.
* The private hosted zone that the render queue's load balancer will be placed in.
*/
readonly domainInfo: DomainInfo;
readonly domainZone: IPrivateHostedZone;

/**
* The fully qualified domain name that will be given to the load balancer in the private
* hosted zone.
*/
readonly fullyQualifiedDomainName: string;
}

/**
Expand Down Expand Up @@ -348,8 +338,13 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {

this.certChain = tlsInfo.certChain;
this.clientCert = tlsInfo.serverCert;
loadBalancerFQDN = tlsInfo.domainInfo.fullyQualifiedDomainName;
domainZone = tlsInfo.domainInfo.domainZone;
loadBalancerFQDN = tlsInfo.fullyQualifiedDomainName;
domainZone = tlsInfo.domainZone;
} else {
if (props.hostname) {
loadBalancerFQDN = this.generateFullyQualifiedDomainName(props.hostname.zone, props.hostname.hostname);
domainZone = props.hostname.zone;
}
}

this.version = props.version;
Expand Down Expand Up @@ -745,8 +740,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
vpc: vpc,
zoneName: RenderQueue.DEFAULT_DOMAIN_NAME,
});
const label = hostname?.hostname ?? RenderQueue.DEFAULT_HOSTNAME;
const domainInfo = this.createDomainInfo(label, domainZone);

const fullyQualifiedDomainName = this.generateFullyQualifiedDomainName(domainZone, hostname?.hostname);

const rootCa = new X509CertificatePem(this, 'RootCA', {
subject: {
Expand All @@ -755,15 +750,16 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
});
const rfdkCert = new X509CertificatePem(this, 'RenderQueuePemCert', {
subject: {
cn: domainInfo.fullyQualifiedDomainName,
cn: fullyQualifiedDomainName,
},
signingCertificate: rootCa,
});
const serverCert = new ImportedAcmCertificate( this, 'AcmCert', rfdkCert );
const certChain = rfdkCert.certChain!;

return {
domainInfo,
domainZone,
fullyQualifiedDomainName,
serverCert,
certChain,
};
Expand All @@ -789,7 +785,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
+ 'with the common name of the certificate matching the hostname + domain name.');
}

const domainInfo = this.createDomainInfo(hostname.hostname, hostname.zone);
const fullyQualifiedDomainName = this.generateFullyQualifiedDomainName(hostname.zone, hostname.hostname);

if ( externalTLS.acmCertificate ) {
if ( externalTLS.acmCertificateChain === undefined ) {
Expand All @@ -807,7 +803,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
}

return {
domainInfo,
domainZone: hostname.zone,
fullyQualifiedDomainName,
serverCert,
certChain,
};
Expand All @@ -817,16 +814,12 @@ export class RenderQueue extends RenderQueueBase implements IGrantable {
* Helper method to create the fully qualified domain name for the given hostname and PrivateHostedZone.
* @param hostname
* @param zone
* @returns DomainInfo containing the PrivateHostedZone and fully qualified domain name
* @returns The fully qualified domain name
*/
private createDomainInfo(hostname: string, zone: IPrivateHostedZone): DomainInfo {
private generateFullyQualifiedDomainName(zone: IPrivateHostedZone, hostname: string = RenderQueue.DEFAULT_HOSTNAME): string {
if (!RenderQueue.RE_VALID_HOSTNAME.test(hostname)) {
throw new Error(`Invalid RenderQueue hostname: ${hostname}`);
}

return {
domainZone: zone,
fullyQualifiedDomainName: `${hostname}.${zone.zoneName}`,
};
return `${hostname}.${zone.zoneName}`;
}
}
57 changes: 46 additions & 11 deletions packages/aws-rfdk/lib/deadline/test/render-queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1243,14 +1243,7 @@ describe('RenderQueue', () => {
},
],
},
'" --render-queue "http://',
{
'Fn::GetAtt': [
'RenderQueueLB235D35F4',
'DNSName',
],
},
':8080" \n' +
'" --render-queue "http://renderqueue.renderfarm.local:8080" \n' +
'rm -f "/tmp/',
{
'Fn::Select': [
Expand Down Expand Up @@ -1421,9 +1414,7 @@ describe('RenderQueue', () => {
},
],
},
'" --render-queue "http://',
isolatedStack.resolve(rq.loadBalancer.loadBalancerDnsName),
':8080" 2>&1\n' +
'" --render-queue "http://renderqueue.renderfarm.local:8080" 2>&1\n' +
'Remove-Item -Path "C:/temp/',
{
'Fn::Select': [
Expand Down Expand Up @@ -2097,6 +2088,50 @@ describe('RenderQueue', () => {
});
});

test.each([
[false],
[true],
])('specified with TLS enabled == %s', (isTlsEnabled: boolean) => {
const zone = new PrivateHostedZone(dependencyStack, 'Zone', {
vpc,
zoneName,
});
const hostname = 'testrq';
const isolatedStack = new Stack(app, 'IsolatedStack');
const props: RenderQueueProps = {
images,
repository,
version: new VersionQuery(isolatedStack, 'Version'),
vpc,
hostname: {
hostname,
zone,
},
trafficEncryption: {
externalTLS: { enabled: isTlsEnabled },
},
};

// WHEN
const renderQueue = new RenderQueue(isolatedStack, 'RenderQueue', props);

const loadBalancerLogicalId = dependencyStack.getLogicalId(
renderQueue.loadBalancer.node.defaultChild as CfnElement,
);
expectCDK(isolatedStack).to(haveResource('AWS::Route53::RecordSet', {
Name: `${hostname}.${zoneName}.`,
Type: 'A',
AliasTarget: objectLike({
HostedZoneId: {
'Fn::GetAtt': [
loadBalancerLogicalId,
'CanonicalHostedZoneID',
],
},
}),
}));
});
jusiskin marked this conversation as resolved.
Show resolved Hide resolved

test.each([
['rq.somedomain.local'],
['1startswithnumber'],
Expand Down