From 42d1056b5a647a161dce65e230a812df1aabd5cf Mon Sep 17 00:00:00 2001 From: David Horsman Date: Fri, 25 Jun 2021 14:41:14 +0000 Subject: [PATCH 1/6] feat(deadline): tls between render queue and clients enabled by default fixes #490 BREAKING CHANGE: Farms currently not configured to use external TLS on the Render Queue will be modified to have it enabled and using the default certificate and hosted zone. To continue to keep external TLS disabled, the `enabled` flag on the `RenderQueueExternalTLSProps` can be set to false; however, we strongly encourage you to enable TLS. --- .../python/package/lib/base_farm_stack.py | 3 + .../ts/lib/base-farm-stack.ts | 1 + integ/lib/render-struct.ts | 12 +- .../lib/deadline/lib/render-queue-ref.ts | 14 +- .../aws-rfdk/lib/deadline/lib/render-queue.ts | 209 ++++++++-- .../test/configure-spot-event-plugin.test.ts | 4 + .../lib/deadline/test/render-queue.test.ts | 362 ++++++++++++------ .../test/spot-event-plugin-fleet.test.ts | 1 + .../test/usage-based-licensing.test.ts | 1 + .../test/worker-configuration.test.ts | 1 + .../lib/deadline/test/worker-fleet.test.ts | 1 + 11 files changed, 456 insertions(+), 153 deletions(-) diff --git a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py index 3bfac26d9..97757589d 100644 --- a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py +++ b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py @@ -15,6 +15,8 @@ from aws_rfdk.deadline import ( AwsThinkboxEulaAcceptance, RenderQueue, + RenderQueueExternalTLSProps, + RenderQueueTrafficEncryptionProps, Repository, RepositoryRemovalPolicies, ThinkboxDockerImages, @@ -81,4 +83,5 @@ def __init__(self, scope: Construct, stack_id: str, *, props: BaseFarmStackProps images=images, repository=repository, deletion_protection=False, + traffic_encryption=RenderQueueTrafficEncryptionProps( external_tls=RenderQueueExternalTLSProps( enabled=False ) ), ) diff --git a/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts b/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts index de2addf04..fac4b628a 100644 --- a/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts +++ b/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts @@ -68,6 +68,7 @@ export class BaseFarmStack extends Stack { images, repository, deletionProtection: false, + trafficEncryption: { externalTLS: { enabled: false } }, }); } } diff --git a/integ/lib/render-struct.ts b/integ/lib/render-struct.ts index 667514e61..4a9237013 100644 --- a/integ/lib/render-struct.ts +++ b/integ/lib/render-struct.ts @@ -11,7 +11,9 @@ import { X509CertificatePem } from 'aws-rfdk'; import { IRepository, RenderQueue, + RenderQueueHostNameProps, RenderQueueProps, + RenderQueueTrafficEncryptionProps, ThinkboxDockerRecipes, } from 'aws-rfdk/deadline'; import { ThinkboxDockerImageOverrides } from './ThinkboxDockerImageOverrides'; @@ -52,9 +54,9 @@ export class RenderStruct extends Construct { const maxLength = 64 - host.length - '.'.length - suffix.length - 1; const zoneName = Stack.of(this).stackName.slice(0, maxLength) + suffix; - let trafficEncryption: any; - let hostname: any; - let cacert: any; + let trafficEncryption: RenderQueueTrafficEncryptionProps | undefined; + let hostname: RenderQueueHostNameProps | undefined; + let cacert: X509CertificatePem | undefined; // If configured for HTTPS, the render queue requires a private domain and a signed certificate for authentication if( props.protocol === 'https' ) { @@ -72,8 +74,8 @@ export class RenderStruct extends Construct { }, signingCertificate: cacert, }), - internalProtocol: ApplicationProtocol.HTTP, }, + internalProtocol: ApplicationProtocol.HTTP, }; hostname = { zone: new PrivateHostedZone(this, 'Zone', { @@ -83,7 +85,7 @@ export class RenderStruct extends Construct { hostname: host, }; } else { - trafficEncryption = undefined; + trafficEncryption = { externalTLS: { enabled: false } }; hostname = undefined; } diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index cabe872fb..6d9944b6b 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -148,9 +148,15 @@ export interface RenderQueueHealthCheckConfiguration { * In both cases the certificate chain **must** include only the CA certificates PEM file due to a known limitation in Deadline. */ export interface RenderQueueExternalTLSProps { + /** + * Whether to enable TLS between the Render Queue and Deadline clients. + * @default true + */ + readonly enabled?: boolean; + /** * The ACM certificate that will be used for establishing incoming external TLS connections to the RenderQueue. - * @default If not provided then the rfdkCertificate must be provided. + * @default: If not provided, rfdkCertificate will be used. */ readonly acmCertificate?: ICertificate; @@ -159,14 +165,14 @@ export interface RenderQueueExternalTLSProps { * * This certifiate chain **must** include only the CA Certificates PEM file. * - * @default If an acmCertificate was provided then this must be provided, otherwise this is ignored. + * @default: If an acmCertificate was provided then this must be provided, otherwise this is ignored. */ readonly acmCertificateChain?: ISecret; /** * The parameters for an X509 Certificate that will be imported into ACM then used by the RenderQueue. * - * @default If not provided then an acmCertificate and acmCertificateChain must be provided. + * @default: If rfdkCertificate and acmCertificate are both not provided, an rfdkCertificate will be generated and used. */ readonly rfdkCertificate?: IX509CertificatePem; } @@ -281,7 +287,7 @@ export interface RenderQueueProps { /** * Hostname to use to connect to the RenderQueue. * - * @default A hostname is generated by the Application Load Balancer that fronts the RenderQueue. + * @default: A private hosted host will be created and the default hostname will be used. */ readonly hostname?: RenderQueueHostNameProps; diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index e5c80bbe9..32da62216 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -19,6 +19,7 @@ import { IConnectable, InstanceType, ISecurityGroup, + IVpc, Port, SubnetType, } from '@aws-cdk/aws-ec2'; @@ -50,6 +51,7 @@ import { import { ILogGroup, } from '@aws-cdk/aws-logs'; +import { IHostedZone, IPrivateHostedZone, PrivateHostedZone } from '@aws-cdk/aws-route53'; import { ISecret, } from '@aws-cdk/aws-secretsmanager'; @@ -64,6 +66,8 @@ import { InstanceConnectOptions, IRepository, IVersion, + RenderQueueExternalTLSProps, + RenderQueueHostNameProps, RenderQueueProps, RenderQueueSizeConstraints, VersionQuery, @@ -109,6 +113,42 @@ 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 + */ +interface TlsInfo { + /** + * The certificate to use for the TLS connection. + */ + readonly clientCert: ICertificate; + + /** + * The certificate chain clients can use to verify the certificate. + */ + readonly certChain: ISecret; + + /** + * The information about the domain for the render queue. + */ + readonly domainInfo: DomainInfo; +} + /** * Base class for Render Queue providers */ @@ -162,7 +202,7 @@ abstract class RenderQueueBase extends Construct implements IRenderQueue { * should be governed carefully, as malicious software could use the API to remotely execute code across the entire render farm. * - The RenderQueue can be deployed with network encryption through Transport Layer Security (TLS) or without it. Unencrypted * network communications can be eavesdropped upon or modified in transit. We strongly recommend deploying the RenderQueue - * with TLS enabled in production environments. + * with TLS enabled in production environments and it is configured to be on by default. */ export class RenderQueue extends RenderQueueBase implements IGrantable { /** @@ -173,6 +213,10 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { [ApplicationProtocol.HTTPS]: 4433, }; + private static readonly DEFAULT_HOSTNAME = 'renderqueue'; + + private static readonly DEFAULT_DOMAIN_NAME = 'rfdk.internal'; + /** * The minimum Deadline version required for the Remote Connection Server to support load-balancing */ @@ -295,38 +339,23 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { this.version = props?.version; - let externalProtocol: ApplicationProtocol; - if ( props.trafficEncryption?.externalTLS ) { - externalProtocol = ApplicationProtocol.HTTPS; - - if ( (props.trafficEncryption.externalTLS.acmCertificate === undefined ) === - (props.trafficEncryption.externalTLS.rfdkCertificate === undefined) ) { - throw new Error('Exactly one of externalTLS.acmCertificate and externalTLS.rfdkCertificate must be provided when using externalTLS.'); - } else if (props.trafficEncryption.externalTLS.rfdkCertificate ) { - if (props.trafficEncryption.externalTLS.rfdkCertificate.certChain === undefined) { - throw new Error('Provided rfdkCertificate does not contain a certificate chain.'); - } - this.clientCert = new ImportedAcmCertificate(this, 'AcmCert', props.trafficEncryption.externalTLS.rfdkCertificate ); - this.certChain = props.trafficEncryption.externalTLS.rfdkCertificate.certChain; - } else { - if (props.trafficEncryption.externalTLS.acmCertificateChain === undefined) { - throw new Error('externalTLS.acmCertificateChain must be provided when using externalTLS.acmCertificate.'); - } - this.clientCert = props.trafficEncryption.externalTLS.acmCertificate; - this.certChain = props.trafficEncryption.externalTLS.acmCertificateChain; - } - } else { - externalProtocol = ApplicationProtocol.HTTP; + const externalProtocol = props.trafficEncryption?.externalTLS?.enabled === false ? ApplicationProtocol.HTTP : ApplicationProtocol.HTTPS; + let loadBalancerFQDN: string | undefined; + let domainZone: IHostedZone | undefined; + + if ( externalProtocol === ApplicationProtocol.HTTPS ) { + const tlsInfo = this.getOrCreateTlsInfo(props); + + this.certChain = tlsInfo.certChain; + this.clientCert = tlsInfo.clientCert; + loadBalancerFQDN = tlsInfo.domainInfo.fullyQualifiedDomainName; + domainZone = tlsInfo.domainInfo.domainZone; } this.version = props.version; const internalProtocol = props.trafficEncryption?.internalProtocol ?? ApplicationProtocol.HTTPS; - if (externalProtocol === ApplicationProtocol.HTTPS && !props.hostname) { - throw new Error('A hostname must be provided when the external protocol is HTTPS'); - } - this.cluster = new Cluster(this, 'Cluster', { vpc: props.vpc, }); @@ -390,14 +419,6 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { this.taskDefinition = taskDefinition; // The fully-qualified domain name to use for the ALB - let loadBalancerFQDN: string | undefined; - if (props.hostname) { - const label = props.hostname.hostname ?? 'renderqueue'; - if (props.hostname.hostname && !RenderQueue.RE_VALID_HOSTNAME.test(label)) { - throw new Error(`Invalid RenderQueue hostname: ${label}`); - } - loadBalancerFQDN = `${label}.${props.hostname.zone.zoneName}`; - } const loadBalancer = new ApplicationLoadBalancer(this, 'LB', { vpc: this.cluster.vpc, @@ -411,7 +432,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { certificate: this.clientCert, cluster: this.cluster, desiredCount: this.renderQueueSize?.desired, - domainZone: props.hostname?.zone, + domainZone, domainName: loadBalancerFQDN, listenerPort: externalPortNumber, loadBalancer, @@ -692,4 +713,120 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { return taskDefinition; } + + /** + * Checks if the user supplied any certificate to use for TLS and uses them, or creates defaults to use. + * @param props + * @returns TlsInfo either based on input to the render queue, or the created defaults + */ + private getOrCreateTlsInfo(props: RenderQueueProps): TlsInfo { + if ( (props.trafficEncryption?.externalTLS?.acmCertificate !== undefined ) || + (props.trafficEncryption?.externalTLS?.rfdkCertificate !== undefined) ) { + if (props.hostname === undefined) { + throw new Error('The hostname for the render queue must be defined if supplying your own certificates.'); + } + return this.getTlsInfoFromUserProps( + props.trafficEncryption.externalTLS, + props.hostname, + ); + } + + return this.createDefaultTlsInfo(props.vpc, props.hostname); + } + + /** + * Creates a default certificate to use for TLS and a PrivateHostedZone to put the load balancer in. + * @param vpc + * @param hostname + * @returns default TlsInfo + */ + private createDefaultTlsInfo(vpc: IVpc, hostname?: RenderQueueHostNameProps) { + const domainZone = hostname?.zone ?? new PrivateHostedZone(this, 'DnsZone', { + vpc: vpc, + zoneName: RenderQueue.DEFAULT_DOMAIN_NAME, + }); + const label = hostname?.hostname ?? RenderQueue.DEFAULT_HOSTNAME; + const domainInfo = this.createDomainInfo(label, domainZone); + + const rootCa = new X509CertificatePem(this, 'RootCA', { + subject: { + cn: 'RenderQueueRootCA', + }, + }); + const rfdkCert = new X509CertificatePem(this, 'RenderQueueCA', { + subject: { + cn: domainInfo.fullyQualifiedDomainName, + }, + signingCertificate: rootCa, + }); + const clientCert = new ImportedAcmCertificate(this, 'AcmCert', rfdkCert ); + const certChain = rfdkCert.certChain!; + + return { + domainInfo, + clientCert, + certChain, + }; + } + + /** + * Gets the certificate and PrivateHostedZone provided in the Render Queue's construct props. + * @param externalTLS + * @param hostname + * @returns The provided certificate and domain info + */ + private getTlsInfoFromUserProps(externalTLS: RenderQueueExternalTLSProps, hostname: RenderQueueHostNameProps): TlsInfo { + let clientCert: ICertificate; + let certChain: ISecret; + + if ( (externalTLS.acmCertificate !== undefined ) && + (externalTLS.rfdkCertificate !== undefined) ) { + throw new Error('Exactly one of externalTLS.acmCertificate and externalTLS.rfdkCertificate must be provided when using externalTLS.'); + } + + if (!hostname.hostname) { + throw new Error('A hostname must be supplied if a certificate is supplied, ' + + 'with the common name of the certificate matching the hostname + domain name.'); + } + + const domainInfo = this.createDomainInfo(hostname.hostname, hostname.zone); + + if ( externalTLS.acmCertificate ) { + if ( externalTLS.acmCertificateChain === undefined ) { + throw new Error('externalTLS.acmCertificateChain must be provided when using externalTLS.acmCertificate.'); + } + clientCert = externalTLS.acmCertificate; + certChain = externalTLS.acmCertificateChain; + + } else { // Using externalTLS.rfdkCertificate + if ( externalTLS.rfdkCertificate!.certChain === undefined ) { + throw new Error('Provided rfdkCertificate does not contain a certificate chain.'); + } + clientCert = new ImportedAcmCertificate(this, 'AcmCert', externalTLS.rfdkCertificate! ); + certChain = externalTLS.rfdkCertificate!.certChain; + } + + return { + domainInfo, + clientCert, + certChain, + }; + } + + /** + * 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 + */ + private createDomainInfo(hostname: string, zone: IPrivateHostedZone): DomainInfo { + if (!RenderQueue.RE_VALID_HOSTNAME.test(hostname)) { + throw new Error(`Invalid RenderQueue hostname: ${hostname}`); + } + + return { + domainZone: zone, + fullyQualifiedDomainName: `${hostname}.${zone.zoneName}`, + }; + } } diff --git a/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts b/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts index b7a513edb..7566635ee 100644 --- a/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/configure-spot-event-plugin.test.ts @@ -86,6 +86,7 @@ describe('ConfigureSpotEventPlugin', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); @@ -688,6 +689,7 @@ describe('ConfigureSpotEventPlugin', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); @@ -909,6 +911,7 @@ describe('ConfigureSpotEventPlugin', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); @@ -943,6 +946,7 @@ describe('ConfigureSpotEventPlugin', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 3da4d0ae2..9f0328c2a 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -490,10 +490,10 @@ describe('RenderQueue', () => { })); }); - test('to HTTP externally between clients and ALB', () => { + test('to HTTPS externally between clients and ALB', () => { expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { - Protocol: 'HTTP', - Port: 8080, + Protocol: 'HTTPS', + Port: 4433, })); }); }); @@ -642,6 +642,7 @@ describe('RenderQueue', () => { vpc, trafficEncryption: { internalProtocol: ApplicationProtocol.HTTP, + externalTLS: { enabled: false }, }, }; @@ -683,10 +684,11 @@ describe('RenderQueue', () => { trafficEncryption: { externalTLS: { acmCertificate: Certificate.fromCertificateArn(stack, 'Certificate', CERT_ARN), - acmCertificateChain: Secret.fromSecretArn(stack, 'CA_Cert', CA_ARN), + acmCertificateChain: Secret.fromSecretPartialArn(stack, 'CA_Cert', CA_ARN), }, }, hostname: { + hostname: 'renderqueue', zone, }, }; @@ -718,7 +720,7 @@ describe('RenderQueue', () => { })); }); - test('raises an error when a cert is specified without a hostname', () => { + test('raises an error when a cert is specified without a hosted zone', () => { // GIVEN const props: RenderQueueProps = { images, @@ -738,99 +740,219 @@ describe('RenderQueue', () => { new RenderQueue(stack, 'RenderQueue', props); }) // THEN - .toThrow(/A hostname must be provided when the external protocol is HTTPS/); + .toThrow(/The hostname for the render queue must be defined if supplying your own certificates./); }); - }); - - describe('externalProtocol is HTTPS importing cert', () => { - let isolatedStack: Stack; - let zone: PrivateHostedZone; - const ZONE_NAME = 'renderfarm.local'; - beforeEach(() => { + test('raises an error when a cert is specified without a hostname', () => { // GIVEN - isolatedStack = new Stack(app, 'IsolatedStack'); - zone = new PrivateHostedZone(isolatedStack, 'RenderQueueZone', { + const zone = new PrivateHostedZone(isolatedStack, 'RenderQueueZoneNoName', { vpc, zoneName: ZONE_NAME, }); - const caCert = new X509CertificatePem(isolatedStack, 'CaCert', { - subject: { - cn: `ca.${ZONE_NAME}`, - }, - }); - const serverCert = new X509CertificatePem(isolatedStack, 'ServerCert', { - subject: { - cn: `server.${ZONE_NAME}`, - }, - signingCertificate: caCert, - }); - const props: RenderQueueProps = { images, repository, - version: new VersionQuery(isolatedStack, 'Version'), + version: renderQueueVersion, vpc, trafficEncryption: { externalTLS: { - rfdkCertificate: serverCert, + acmCertificate: Certificate.fromCertificateArn(stack, 'Cert', 'certArn'), + acmCertificateChain: Secret.fromSecretArn(stack, 'CA_Cert2', CA_ARN), }, - internalProtocol: ApplicationProtocol.HTTP, - }, - hostname: { - zone, }, + hostname: { zone }, }; // WHEN - new RenderQueue(isolatedStack, 'RenderQueue', props); + expect(() => { + new RenderQueue(stack, 'RenderQueue', props); + }) + // THEN + .toThrow(/A hostname must be supplied if a certificate is supplied, with the common name of the certificate matching the hostname \+ domain name/); }); + }); - test('sets the listener port to 4433', () => { - // THEN - expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { - Port: 4433, - })); - }); + describe('externalProtocol is HTTPS importing cert', () => { + describe('passing cases', () => { + let isolatedStack: Stack; + let zone: PrivateHostedZone; + const ZONE_NAME = 'renderfarm.local'; + const HOSTNAME = 'server'; + + beforeEach(() => { + // GIVEN + isolatedStack = new Stack(app, 'IsolatedStack'); + zone = new PrivateHostedZone(isolatedStack, 'RenderQueueZone', { + vpc, + zoneName: ZONE_NAME, + }); + + const caCert = new X509CertificatePem(isolatedStack, 'CaCert', { + subject: { + cn: `ca.${ZONE_NAME}`, + }, + }); + const serverCert = new X509CertificatePem(isolatedStack, 'ServerCert', { + subject: { + cn: `${HOSTNAME}.${ZONE_NAME}`, + }, + signingCertificate: caCert, + }); + + const props: RenderQueueProps = { + images, + repository, + version: new VersionQuery(isolatedStack, 'Version'), + vpc, + trafficEncryption: { + externalTLS: { + rfdkCertificate: serverCert, + }, + internalProtocol: ApplicationProtocol.HTTP, + }, + hostname: { + zone, + hostname: HOSTNAME, + }, + }; - test('sets the listener protocol to HTTPS', () => { - // THEN - expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { - Protocol: 'HTTPS', - })); - }); + // WHEN + new RenderQueue(isolatedStack, 'RenderQueue', props); + }); - test('Imports Cert to ACM', () => { - // THEN - expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_AcmImportedCertificate', { - X509CertificatePem: { - Cert: { - 'Fn::GetAtt': [ - 'ServerCert', - 'Cert', - ], + test('sets the listener port to 4433', () => { + // THEN + expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { + Port: 4433, + })); + }); + + test('sets the listener protocol to HTTPS', () => { + // THEN + expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { + Protocol: 'HTTPS', + })); + }); + + test('Imports Cert to ACM', () => { + // THEN + expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_AcmImportedCertificate', { + X509CertificatePem: { + Cert: { + 'Fn::GetAtt': [ + 'ServerCert', + 'Cert', + ], + }, + Key: { + 'Fn::GetAtt': [ + 'ServerCert', + 'Key', + ], + }, + Passphrase: { + Ref: 'ServerCertPassphraseE4C3CB38', + }, + CertChain: { + 'Fn::GetAtt': [ + 'ServerCert', + 'CertChain', + ], + }, }, - Key: { - 'Fn::GetAtt': [ - 'ServerCert', - 'Key', - ], + })); + }); + }); + + describe('failure cases,', () => { + test('Throws when missing cert chain', () => { + const ZONE_NAME = 'renderfarm.local'; + const HOSTNAME = 'server'; + // GIVEN + const isolatedStack = new Stack(app, 'IsolatedStack'); + const zone = new PrivateHostedZone(isolatedStack, 'RenderQueueZone', { + vpc, + zoneName: ZONE_NAME, + }); + + const rootCert = new X509CertificatePem(isolatedStack, 'RootCert', { + subject: { + cn: `ca.${ZONE_NAME}`, }, - Passphrase: { - Ref: 'ServerCertPassphraseE4C3CB38', + }); + + const props: RenderQueueProps = { + images, + repository, + version: new VersionQuery(isolatedStack, 'Version'), + vpc, + trafficEncryption: { + externalTLS: { + rfdkCertificate: rootCert, + }, + internalProtocol: ApplicationProtocol.HTTP, }, - CertChain: { - 'Fn::GetAtt': [ - 'ServerCert', - 'CertChain', - ], + hostname: { + zone, + hostname: HOSTNAME, }, - }, - })); + }; + + // WHEN + expect(() => { + new RenderQueue(isolatedStack, 'RenderQueue', props); + }) + // THEN + .toThrow(/Provided rfdkCertificate does not contain a certificate chain/); + }); }); }); + test('Creates default RFDK cert if no cert given', () => { + // GIVEN + const isolatedStack = new Stack(app, 'IsolatedStack'); + + const props: RenderQueueProps = { + images, + repository, + version: new VersionQuery(isolatedStack, 'Version'), + vpc, + trafficEncryption: { + externalTLS: { + }, + }, + }; + + new RenderQueue(isolatedStack, 'RenderQueue', props); + + expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_AcmImportedCertificate', { + X509CertificatePem: { + Cert: { + 'Fn::GetAtt': [ + 'RenderQueueRenderQueueCA5EEAD7C4', + 'Cert', + ], + }, + Key: { + 'Fn::GetAtt': [ + 'RenderQueueRenderQueueCA5EEAD7C4', + 'Key', + ], + }, + Passphrase: { + Ref: 'RenderQueueRenderQueueCAPassphrase95F29CE0', + }, + CertChain: { + 'Fn::GetAtt': [ + 'RenderQueueRenderQueueCA5EEAD7C4', + 'CertChain', + ], + }, + }, + })); + }); + test('Throws if given ACM cert and RFDK Cert', () => { // GIVEN const isolatedStack = new Stack(app, 'IsolatedStack'); @@ -880,41 +1002,10 @@ describe('RenderQueue', () => { .toThrow(/Exactly one of externalTLS.acmCertificate and externalTLS.rfdkCertificate must be provided when using externalTLS/); }); - test('Throws if no Cert given', () => { - // GIVEN - const isolatedStack = new Stack(app, 'IsolatedStack'); - const ZONE_NAME = 'renderfarm.local'; - - const zone = new PrivateHostedZone(isolatedStack, 'RenderQueueZone', { - vpc, - zoneName: ZONE_NAME, - }); - - const props: RenderQueueProps = { - images, - repository, - version: new VersionQuery(isolatedStack, 'Version'), - vpc, - trafficEncryption: { - externalTLS: { - }, - }, - hostname: { - zone, - }, - }; - - // WHEN - expect(() => { - new RenderQueue(isolatedStack, 'RenderQueue', props); - }) - // THEN - .toThrow(/Exactly one of externalTLS.acmCertificate and externalTLS.rfdkCertificate must be provided when using externalTLS/); - }); - test('Throws if ACM Cert is given without a cert chain', () => { // GIVEN const isolatedStack = new Stack(app, 'IsolatedStack'); + const HOSTNAME = 'renderqueue'; const ZONE_NAME = 'renderfarm.local'; const CERT_ARN = 'certArn'; @@ -934,6 +1025,7 @@ describe('RenderQueue', () => { }, }, hostname: { + hostname: HOSTNAME, zone, }, }; @@ -969,6 +1061,7 @@ describe('RenderQueue', () => { hostname: { zone, }, + trafficEncryption: { externalTLS: { enabled: false } }, }; // WHEN @@ -1134,7 +1227,14 @@ describe('RenderQueue', () => { }, ], }, - `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" \n` + + '" --render-queue "http://', + { + 'Fn::GetAtt': [ + 'RenderQueueLB235D35F4', + 'DNSName', + ], + }, + ':8080" \n' + 'rm -f "/tmp/', { 'Fn::Select': [ @@ -1305,7 +1405,14 @@ describe('RenderQueue', () => { }, ], }, - `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" 2>&1\n` + + '" --render-queue "http://', + { + 'Fn::GetAtt': [ + 'RenderQueueLB235D35F4', + 'DNSName', + ], + }, + ':8080" 2>&1\n' + 'Remove-Item -Path "C:/temp/', { 'Fn::Select': [ @@ -1365,6 +1472,7 @@ describe('RenderQueue', () => { let isolatedStack: Stack; let zone: PrivateHostedZone; let rq: RenderQueue; + const HOSTNAME = 'renderqueue'; const ZONE_NAME = 'renderfarm.local'; const CERT_ARN = 'arn:a:b:c:dcertarn'; const CA_ARN = 'arn:aws:secretsmanager:123456789012:secret:ca/arn'; @@ -1382,6 +1490,7 @@ describe('RenderQueue', () => { version: new VersionQuery(isolatedStack, 'Version'), vpc, hostname: { + hostname: HOSTNAME, zone, }, trafficEncryption: { @@ -1881,7 +1990,7 @@ describe('RenderQueue', () => { // GIVEN const zoneName = 'mydomain.local'; - describe('not specified', () => { + describe('not specified with no TLS', () => { let isolatedStack: Stack; beforeEach(() => { @@ -1890,6 +1999,7 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, + trafficEncryption: { externalTLS: { enabled: false } }, version: new VersionQuery(isolatedStack, 'Version'), vpc, }; @@ -1904,6 +2014,42 @@ describe('RenderQueue', () => { }); }); + test('not specified with TLS', () => { + // GIVEN + const isolatedStack = new Stack(app, 'IsolatedStack'); + + const props: RenderQueueProps = { + images, + repository, + version: new VersionQuery(isolatedStack, 'Version'), + vpc, + trafficEncryption: { + externalTLS: { + }, + }, + }; + + const renderQueue = new RenderQueue(isolatedStack, 'RenderQueue', props); + + const loadBalancerLogicalId = dependencyStack.getLogicalId( + renderQueue.loadBalancer.node.defaultChild as CfnElement, + ); + + expectCDK(isolatedStack).to(haveResource('AWS::Route53::RecordSet', { + // eslint-disable-next-line dot-notation + Name: `${RenderQueue['DEFAULT_HOSTNAME']}.${RenderQueue['DEFAULT_DOMAIN_NAME']}.`, + Type: 'A', + AliasTarget: objectLike({ + HostedZoneId: { + 'Fn::GetAtt': [ + loadBalancerLogicalId, + 'CanonicalHostedZoneID', + ], + }, + }), + })); + }); + describe('specified with zone but no hostname', () => { let zone: PrivateHostedZone; let isolatedStack: Stack; @@ -2271,13 +2417,13 @@ describe('RenderQueue', () => { resourceTypeCounts: { 'AWS::ECS::Cluster': 1, 'AWS::EC2::SecurityGroup': 2, - 'AWS::IAM::Role': 8, + 'AWS::IAM::Role': 10, 'AWS::AutoScaling::AutoScalingGroup': 1, - 'AWS::Lambda::Function': 4, + 'AWS::Lambda::Function': 6, 'AWS::SNS::Topic': 1, 'AWS::ECS::TaskDefinition': 1, - 'AWS::DynamoDB::Table': 2, - 'AWS::SecretsManager::Secret': 2, + 'AWS::DynamoDB::Table': 5, + 'AWS::SecretsManager::Secret': 4, 'AWS::ElasticLoadBalancingV2::LoadBalancer': 1, 'AWS::ElasticLoadBalancingV2::TargetGroup': 1, 'AWS::ECS::Service': 1, diff --git a/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts index e0eea3857..d41860546 100644 --- a/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/spot-event-plugin-fleet.test.ts @@ -100,6 +100,7 @@ describe('SpotEventPluginFleet', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); spotFleetStack = new Stack(app, 'SpotFleetStack', { diff --git a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts index 5f5692b8e..fec8b95ba 100644 --- a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts @@ -87,6 +87,7 @@ describe('UsageBasedLicensing', () => { vpc, version: versionedInstallers, }), + trafficEncryption: { externalTLS: { enabled: false } }, version: versionedInstallers, }); diff --git a/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts index c6b0d8cae..96140c6d0 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-configuration.test.ts @@ -329,6 +329,7 @@ describe('Test WorkerInstanceConfiguration connect to RenderQueue', () => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, }); const rqSecGrp = renderQueue.connections.securityGroups[0] as SecurityGroup; renderQueueSGId = stack.resolve(rqSecGrp.securityGroupId); diff --git a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts index a6d2ddf84..d7914d27a 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts @@ -90,6 +90,7 @@ beforeEach(() => { vpc, version, }), + trafficEncryption: { externalTLS: { enabled: false } }, version, }); wfstack = new Stack(app, 'workerFleetStack', { From 64ef85722f1f711b7d2e070d1930d371f699909f Mon Sep 17 00:00:00 2001 From: David Horsman Date: Fri, 9 Jul 2021 18:27:01 +0000 Subject: [PATCH 2/6] CR updates --- .../python/package/lib/base_farm_stack.py | 1 - .../ts/lib/base-farm-stack.ts | 1 - .../lib/deadline/lib/render-queue-ref.ts | 13 +-- .../aws-rfdk/lib/deadline/lib/render-queue.ts | 22 ++--- .../lib/deadline/test/render-queue.test.ts | 85 ++++++++++--------- 5 files changed, 62 insertions(+), 60 deletions(-) diff --git a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py index 97757589d..3f00831a0 100644 --- a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py +++ b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py @@ -83,5 +83,4 @@ def __init__(self, scope: Construct, stack_id: str, *, props: BaseFarmStackProps images=images, repository=repository, deletion_protection=False, - traffic_encryption=RenderQueueTrafficEncryptionProps( external_tls=RenderQueueExternalTLSProps( enabled=False ) ), ) diff --git a/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts b/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts index fac4b628a..de2addf04 100644 --- a/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts +++ b/examples/deadline/EC2-Image-Builder/ts/lib/base-farm-stack.ts @@ -68,7 +68,6 @@ export class BaseFarmStack extends Stack { images, repository, deletionProtection: false, - trafficEncryption: { externalTLS: { enabled: false } }, }); } } diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index 6d9944b6b..98e9c8868 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -56,7 +56,10 @@ export interface RenderQueueHostNameProps { readonly hostname?: string; /** - * The private zone to which the DNS A record for the render queue will be added. + * The private zone to which the DNS A record for the render queue will be added. We do not recommend + * using an unregistered domain for your PrivateHostedZone and we have registered aws-rfdk.com that + * can be used if you do not own your own. Refer to RFC 6762 Appendix G for more details about private + * DNS namespaces: https://datatracker.ietf.org/doc/html/rfc6762#appendix-G */ readonly zone: IPrivateHostedZone; } @@ -156,7 +159,7 @@ export interface RenderQueueExternalTLSProps { /** * The ACM certificate that will be used for establishing incoming external TLS connections to the RenderQueue. - * @default: If not provided, rfdkCertificate will be used. + * @default If rfdkCertificate and acmCertificate are both not provided, an rfdkCertificate will be generated and used. */ readonly acmCertificate?: ICertificate; @@ -165,14 +168,14 @@ export interface RenderQueueExternalTLSProps { * * This certifiate chain **must** include only the CA Certificates PEM file. * - * @default: If an acmCertificate was provided then this must be provided, otherwise this is ignored. + * @default If an acmCertificate was provided then this must be provided, otherwise this is ignored. */ readonly acmCertificateChain?: ISecret; /** * 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, an rfdkCertificate will be generated and used. */ readonly rfdkCertificate?: IX509CertificatePem; } @@ -287,7 +290,7 @@ export interface RenderQueueProps { /** * Hostname to use to connect to the RenderQueue. * - * @default: A private hosted host will be created and the default hostname will be used. + * @default - The hostname `renderqueue` will be used and a PrivateHostedZone will be created with the domain name `aws-rfdk.com` */ readonly hostname?: RenderQueueHostNameProps; diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index 32da62216..a99834682 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -134,9 +134,9 @@ interface DomainInfo { */ interface TlsInfo { /** - * The certificate to use for the TLS connection. + * The certificate the Render Queue's server will use for the TLS connection. */ - readonly clientCert: ICertificate; + readonly serverCert: ICertificate; /** * The certificate chain clients can use to verify the certificate. @@ -215,7 +215,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { private static readonly DEFAULT_HOSTNAME = 'renderqueue'; - private static readonly DEFAULT_DOMAIN_NAME = 'rfdk.internal'; + private static readonly DEFAULT_DOMAIN_NAME = 'aws-rfdk.com'; /** * The minimum Deadline version required for the Remote Connection Server to support load-balancing @@ -347,7 +347,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { const tlsInfo = this.getOrCreateTlsInfo(props); this.certChain = tlsInfo.certChain; - this.clientCert = tlsInfo.clientCert; + this.clientCert = tlsInfo.serverCert; loadBalancerFQDN = tlsInfo.domainInfo.fullyQualifiedDomainName; domainZone = tlsInfo.domainInfo.domainZone; } @@ -753,18 +753,18 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { cn: 'RenderQueueRootCA', }, }); - const rfdkCert = new X509CertificatePem(this, 'RenderQueueCA', { + const rfdkCert = new X509CertificatePem(this, 'RenderQueuePemCert', { subject: { cn: domainInfo.fullyQualifiedDomainName, }, signingCertificate: rootCa, }); - const clientCert = new ImportedAcmCertificate(this, 'AcmCert', rfdkCert ); + const serverCert = new ImportedAcmCertificate( this, 'AcmCert', rfdkCert ); const certChain = rfdkCert.certChain!; return { domainInfo, - clientCert, + serverCert, certChain, }; } @@ -776,7 +776,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { * @returns The provided certificate and domain info */ private getTlsInfoFromUserProps(externalTLS: RenderQueueExternalTLSProps, hostname: RenderQueueHostNameProps): TlsInfo { - let clientCert: ICertificate; + let serverCert: ICertificate; let certChain: ISecret; if ( (externalTLS.acmCertificate !== undefined ) && @@ -795,20 +795,20 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { if ( externalTLS.acmCertificateChain === undefined ) { throw new Error('externalTLS.acmCertificateChain must be provided when using externalTLS.acmCertificate.'); } - clientCert = externalTLS.acmCertificate; + serverCert = externalTLS.acmCertificate; certChain = externalTLS.acmCertificateChain; } else { // Using externalTLS.rfdkCertificate if ( externalTLS.rfdkCertificate!.certChain === undefined ) { throw new Error('Provided rfdkCertificate does not contain a certificate chain.'); } - clientCert = new ImportedAcmCertificate(this, 'AcmCert', externalTLS.rfdkCertificate! ); + serverCert = new ImportedAcmCertificate( this, 'AcmCert', externalTLS.rfdkCertificate! ); certChain = externalTLS.rfdkCertificate!.certChain; } return { domainInfo, - clientCert, + serverCert, certChain, }; } diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 9f0328c2a..5c492530a 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -6,6 +6,7 @@ import { ABSENT, arrayWith, + countResources, countResourcesLike, deepObjectLike, expect as expectCDK, @@ -53,10 +54,12 @@ import { Secret } from '@aws-cdk/aws-secretsmanager'; import { App, CfnElement, + CustomResource, Stack, } from '@aws-cdk/core'; import { + ImportedAcmCertificate, X509CertificatePem, } from '../..'; import { @@ -924,33 +927,46 @@ describe('RenderQueue', () => { }, }; - new RenderQueue(isolatedStack, 'RenderQueue', props); + const rq = new RenderQueue(isolatedStack, 'RenderQueue', props); + + const rootCa = rq.node.findChild('RootCA').node.defaultChild as X509CertificatePem; + const rootCaGen = rootCa.node.defaultChild as CustomResource; + const rfdkCert = rq.node.findChild('RenderQueuePemCert').node.defaultChild as X509CertificatePem; + const rfdkCertGen = rfdkCert.node.defaultChild as CustomResource; + const acmCert = rq.node.findChild('AcmCert').node.defaultChild as ImportedAcmCertificate; + + expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_X509Generator', { + Passphrase: isolatedStack.resolve(rootCa.passphrase), + })); + expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_X509Generator', { + Passphrase: isolatedStack.resolve(rfdkCert.passphrase), + SigningCertificate: { + Cert: isolatedStack.resolve(rootCaGen.getAtt('Cert')), + Key: isolatedStack.resolve(rootCaGen.getAtt('Key')), + Passphrase: isolatedStack.resolve(rootCa.passphrase), + CertChain: '', + }, + })); + + expectCDK(isolatedStack).to(countResources('Custom::RFDK_AcmImportedCertificate', 1)); expectCDK(isolatedStack).to(haveResourceLike('Custom::RFDK_AcmImportedCertificate', { X509CertificatePem: { - Cert: { - 'Fn::GetAtt': [ - 'RenderQueueRenderQueueCA5EEAD7C4', - 'Cert', - ], - }, - Key: { - 'Fn::GetAtt': [ - 'RenderQueueRenderQueueCA5EEAD7C4', - 'Key', - ], - }, - Passphrase: { - Ref: 'RenderQueueRenderQueueCAPassphrase95F29CE0', - }, - CertChain: { - 'Fn::GetAtt': [ - 'RenderQueueRenderQueueCA5EEAD7C4', - 'CertChain', - ], - }, + Cert: isolatedStack.resolve(rfdkCertGen.getAtt('Cert')), + Key: isolatedStack.resolve(rfdkCertGen.getAtt('Key')), + Passphrase: isolatedStack.resolve(rfdkCert.passphrase), + CertChain: isolatedStack.resolve(rfdkCertGen.getAtt('CertChain')), }, })); + + expectCDK(isolatedStack).to(countResources('AWS::ElasticLoadBalancingV2::Listener', 1)); + expectCDK(isolatedStack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', { + Certificates: [ + { + CertificateArn: isolatedStack.resolve(acmCert.certificateArn), + }, + ], + })); }); test('Throws if given ACM cert and RFDK Cert', () => { @@ -1406,12 +1422,7 @@ describe('RenderQueue', () => { ], }, '" --render-queue "http://', - { - 'Fn::GetAtt': [ - 'RenderQueueLB235D35F4', - 'DNSName', - ], - }, + isolatedStack.resolve(rq.loadBalancer.loadBalancerDnsName), ':8080" 2>&1\n' + 'Remove-Item -Path "C:/temp/', { @@ -1990,7 +2001,7 @@ describe('RenderQueue', () => { // GIVEN const zoneName = 'mydomain.local'; - describe('not specified with no TLS', () => { + describe('not specified, with no TLS', () => { let isolatedStack: Stack; beforeEach(() => { @@ -2014,7 +2025,7 @@ describe('RenderQueue', () => { }); }); - test('not specified with TLS', () => { + test('not specified, with TLS', () => { // GIVEN const isolatedStack = new Stack(app, 'IsolatedStack'); @@ -2031,21 +2042,11 @@ describe('RenderQueue', () => { const renderQueue = new RenderQueue(isolatedStack, 'RenderQueue', props); - const loadBalancerLogicalId = dependencyStack.getLogicalId( - renderQueue.loadBalancer.node.defaultChild as CfnElement, - ); - expectCDK(isolatedStack).to(haveResource('AWS::Route53::RecordSet', { - // eslint-disable-next-line dot-notation - Name: `${RenderQueue['DEFAULT_HOSTNAME']}.${RenderQueue['DEFAULT_DOMAIN_NAME']}.`, + Name: 'renderqueue.aws-rfdk.com.', Type: 'A', AliasTarget: objectLike({ - HostedZoneId: { - 'Fn::GetAtt': [ - loadBalancerLogicalId, - 'CanonicalHostedZoneID', - ], - }, + HostedZoneId: isolatedStack.resolve(renderQueue.loadBalancer.loadBalancerCanonicalHostedZoneId), }), })); }); From 9768af153a93355e6f437ff6fef40690a28205cb Mon Sep 17 00:00:00 2001 From: David Horsman Date: Wed, 21 Jul 2021 13:24:45 +0000 Subject: [PATCH 3/6] CR updates 2 --- .../python/package/lib/base_farm_stack.py | 2 - .../aws-rfdk/docs/upgrade/upgrading-0.37.md | 41 +++++++++++++ .../lib/deadline/lib/render-queue-ref.ts | 4 +- .../aws-rfdk/lib/deadline/lib/render-queue.ts | 59 ++++++++----------- .../lib/deadline/test/render-queue.test.ts | 57 ++++++++++++++---- 5 files changed, 115 insertions(+), 48 deletions(-) create mode 100644 packages/aws-rfdk/docs/upgrade/upgrading-0.37.md diff --git a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py index 3f00831a0..3bfac26d9 100644 --- a/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py +++ b/examples/deadline/EC2-Image-Builder/python/package/lib/base_farm_stack.py @@ -15,8 +15,6 @@ from aws_rfdk.deadline import ( AwsThinkboxEulaAcceptance, RenderQueue, - RenderQueueExternalTLSProps, - RenderQueueTrafficEncryptionProps, Repository, RepositoryRemovalPolicies, ThinkboxDockerImages, diff --git a/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md new file mode 100644 index 000000000..c9b266e1e --- /dev/null +++ b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md @@ -0,0 +1,41 @@ +# Upgrading to RFDK v0.37.x or Newer + +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 + +### 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 + +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 }, + }, +}); +``` diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index 98e9c8868..7059ac176 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -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; @@ -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; } diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts index a99834682..6a15ec66d 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue.ts @@ -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 */ @@ -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; } /** @@ -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; @@ -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: { @@ -755,7 +750,7 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { }); const rfdkCert = new X509CertificatePem(this, 'RenderQueuePemCert', { subject: { - cn: domainInfo.fullyQualifiedDomainName, + cn: fullyQualifiedDomainName, }, signingCertificate: rootCa, }); @@ -763,7 +758,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { const certChain = rfdkCert.certChain!; return { - domainInfo, + domainZone, + fullyQualifiedDomainName, serverCert, certChain, }; @@ -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 ) { @@ -807,7 +803,8 @@ export class RenderQueue extends RenderQueueBase implements IGrantable { } return { - domainInfo, + domainZone: hostname.zone, + fullyQualifiedDomainName, serverCert, certChain, }; @@ -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}`; } } diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 5c492530a..876721bf1 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -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': [ @@ -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': [ @@ -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', + ], + }, + }), + })); + }); + test.each([ ['rq.somedomain.local'], ['1startswithnumber'], From d57b59da17f546cf756ef70da79ee41ceb419070 Mon Sep 17 00:00:00 2001 From: David Horsman Date: Fri, 23 Jul 2021 21:20:50 +0000 Subject: [PATCH 4/6] CR updates 3 --- packages/aws-rfdk/docs/upgrade/index.md | 1 + .../aws-rfdk/docs/upgrade/upgrading-0.37.md | 24 +++++++++++++++---- .../lib/deadline/test/render-queue.test.ts | 6 +++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/aws-rfdk/docs/upgrade/index.md b/packages/aws-rfdk/docs/upgrade/index.md index 254a7f779..7d2d1290c 100644 --- a/packages/aws-rfdk/docs/upgrade/index.md +++ b/packages/aws-rfdk/docs/upgrade/index.md @@ -5,3 +5,4 @@ applications. The documentation is separated by RFDK versions that included pote upgrading to (or beyond) a version listed below, you should consult the the linked upgrade documentation. * [`0.27.x`](./upgrading-0.27.md) +* [`0.37.x`](./upgrading-0.37.md) diff --git a/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md index c9b266e1e..d7c41f26d 100644 --- a/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md +++ b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md @@ -8,7 +8,14 @@ If you are already setting fields on the `RenderQueueExternalTLSProps` for the R ## Upgrading Farms Not Using TLS -### RenderQueue Changes +To upgrade your farm if it does not currently configure TLS for connections to the Render Queue, there are two options: + +1. [Migrating to TLS](#migrating-to-tls) (recommended) +1. [Preserving plain HTTP](#preserving-plain-http) + +### Migrating to TLS + +#### 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. @@ -18,13 +25,20 @@ There will be a few new constructs deployed to your farm: 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 +#### WorkerInstanceFleet and SpotEventPluginFleet Changes -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 +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 or `SpotEventPluginFleet` and `ConfigureSpotEventPlugin` constructs. If you are not using a tiered architecture, we still recommend destroying these constructs since we're changing the endpoint that they need to connect to, and that configuration of the endpoint happens in the initialization script for an instance. -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). +To perform the removal of these constructs: +1. Suspend any jobs that are being run by workers that the constructs deployed. +1. If any spot instances are running, they will need to be terminated since their lifecycle is controlled by Deadline's Spot Event Plugin and not your RFDK app. Trying to destroy/remove the `SpotEventPluginFleet` construct will fail if these hosts are left running because the spot instances use the security group that the construct creates. +1. Next we have to destroy the constructs that are deploying workers, which could be done in a few ways: + 1. If you can destroy the Stack that contains these constructs without destroying the rest of your app, then destroy it using the command `cdk destroy "ComputeTier"` (or whatever name you gave your stack). + 1. If you cannot destroy a single stack or you are not using a tiered architecture, you can just comment out these constructs in your app, rebuild it, and then run `cdk deploy "*"` to perform the removal. +1. Now that we won't cause any dependency issues, we can update the version of RFDK in our app's `package.json`, install it, and then run the deployment. If worker constructs were commented out in the last step, they can be added back in here and redeployed during the upgrade. +1. Any jobs that were paused can be resumed after the deployment is complete. Any workers deployed with the `WorkerInstanceFleet` should be connecting through TLS now, and the Spot Event Plugin should be configured so that any new Spot instances it deploys will be properly configured as well. -## Disabling External TLS +### Preserving plain HTTP 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: diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 876721bf1..90f9a9e33 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -1243,7 +1243,7 @@ describe('RenderQueue', () => { }, ], }, - '" --render-queue "http://renderqueue.renderfarm.local:8080" \n' + + `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" 2>&1\n` + 'rm -f "/tmp/', { 'Fn::Select': [ @@ -1414,7 +1414,7 @@ describe('RenderQueue', () => { }, ], }, - '" --render-queue "http://renderqueue.renderfarm.local:8080" 2>&1\n' + + `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" 2>&1\n` + 'Remove-Item -Path "C:/temp/', { 'Fn::Select': [ @@ -2092,6 +2092,7 @@ describe('RenderQueue', () => { [false], [true], ])('specified with TLS enabled == %s', (isTlsEnabled: boolean) => { + // GIVEN const zone = new PrivateHostedZone(dependencyStack, 'Zone', { vpc, zoneName, @@ -2115,6 +2116,7 @@ describe('RenderQueue', () => { // WHEN const renderQueue = new RenderQueue(isolatedStack, 'RenderQueue', props); + // THEN const loadBalancerLogicalId = dependencyStack.getLogicalId( renderQueue.loadBalancer.node.defaultChild as CfnElement, ); From 509b3d7166f0f618d1a108a020dfcd5a149146e7 Mon Sep 17 00:00:00 2001 From: David Horsman Date: Fri, 23 Jul 2021 21:47:08 +0000 Subject: [PATCH 5/6] Fixed broken unit test --- packages/aws-rfdk/lib/deadline/test/render-queue.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index 90f9a9e33..94065e887 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -1243,7 +1243,7 @@ describe('RenderQueue', () => { }, ], }, - `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" 2>&1\n` + + `" --render-queue "http://renderqueue.${ZONE_NAME}:8080" \n` + 'rm -f "/tmp/', { 'Fn::Select': [ From 9fea621792f1a88ec30974438f90f56f745cdae0 Mon Sep 17 00:00:00 2001 From: David Horsman Date: Fri, 23 Jul 2021 22:18:03 +0000 Subject: [PATCH 6/6] CR Updates 5 --- packages/aws-rfdk/docs/upgrade/upgrading-0.37.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md index d7c41f26d..6e8ffee68 100644 --- a/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md +++ b/packages/aws-rfdk/docs/upgrade/upgrading-0.37.md @@ -31,12 +31,12 @@ Since the endpoint and port the listener on the load balancer uses will be chang To perform the removal of these constructs: 1. Suspend any jobs that are being run by workers that the constructs deployed. -1. If any spot instances are running, they will need to be terminated since their lifecycle is controlled by Deadline's Spot Event Plugin and not your RFDK app. Trying to destroy/remove the `SpotEventPluginFleet` construct will fail if these hosts are left running because the spot instances use the security group that the construct creates. -1. Next we have to destroy the constructs that are deploying workers, which could be done in a few ways: - 1. If you can destroy the Stack that contains these constructs without destroying the rest of your app, then destroy it using the command `cdk destroy "ComputeTier"` (or whatever name you gave your stack). - 1. If you cannot destroy a single stack or you are not using a tiered architecture, you can just comment out these constructs in your app, rebuild it, and then run `cdk deploy "*"` to perform the removal. -1. Now that we won't cause any dependency issues, we can update the version of RFDK in our app's `package.json`, install it, and then run the deployment. If worker constructs were commented out in the last step, they can be added back in here and redeployed during the upgrade. -1. Any jobs that were paused can be resumed after the deployment is complete. Any workers deployed with the `WorkerInstanceFleet` should be connecting through TLS now, and the Spot Event Plugin should be configured so that any new Spot instances it deploys will be properly configured as well. +2. If you are using the `ConfigureSpotEventPlugin` and `SpotEventPluginFleet` constructs, then any spot fleets launched by the Spot Event Plugin will need to be terminated, since their lifecycle is controlled by Deadline's Spot Event Plugin and not your RFDK app. Trying to destroy/remove the `SpotEventPluginFleet` construct will fail if these hosts are left running because the spot instances use the security group that the construct creates. +3. Next we have to destroy the constructs that are deploying workers, which could be done in a few ways: + 1. If you can destroy the Stack that contains these constructs without destroying the rest of your app, then destroy it using the command `cdk destroy "ComputeTier"` (or whatever name you gave your stack). + 2. If you cannot destroy a single stack or you are not using a tiered architecture, you can just comment out these constructs in your app, rebuild it, and then run `cdk deploy "*"` to perform the removal. +4. Now that we won't cause any dependency issues, we can upgrade the version of RFDK and then run the deployment. If worker constructs were commented out in the last step, they can be added back in here and redeployed during the upgrade. +5. Any jobs that were paused can be resumed after the deployment is complete. Any workers deployed with the `WorkerInstanceFleet` should be connecting through TLS now, and the Spot Event Plugin should be configured so that any new Spot instances it deploys will be properly configured as well. ### Preserving plain HTTP