From 64bfbf9b981a32a4db1b07476144d280d6eced32 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Tue, 24 Jan 2023 20:02:51 -0500 Subject: [PATCH] feat(certificatemanager): deprecate DnsValidatedCertificate (#21982) Now that the official CloudFormation resource `AWS::CertificateManager::Certificate` (CDK's `Certificate` construct) supports DNS validation we do not want to recommend using the `DnsValidatedCertificate` construct. The `DnsValidatedCertificate` construct uses CloudFormation custom resources to perform the certificate creation and this creates a lot of maintenance burden on our team (see the list of linked issues). Currently the primary use case for using `DnsValidatedCertificate` over `Certificate` is for cross region use cases. For this use case I have updated the README to have our suggested solution. The example in the README is tested in this [integration test](https://github.com/aws/aws-cdk/blob/main/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-cross-region-cert.ts) fixes #8934, #2914, #20698, #17349, #15217, #14519 ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-certificatemanager/README.md | 39 +++++++++++++++---- .../lib/dns-validated-certificate.ts | 1 + .../rosetta/default.ts-fixture | 7 +++- .../test/dns-validated-certificate.test.ts | 23 +++++------ .../aws-certificatemanager/test/util.test.ts | 5 ++- packages/@aws-cdk/aws-cloudfront/README.md | 6 +-- .../load-balanced-fargate-service.test.ts | 6 +-- .../test/bucket-website-target.test.ts | 7 ++-- 8 files changed, 63 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index cc7f9fb9b9986..54b98fda913b5 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -100,15 +100,40 @@ new acm.Certificate(this, 'Certificate', { ## Cross-region Certificates ACM certificates that are used with CloudFront -- or higher-level constructs which rely on CloudFront -- must be in the `us-east-1` region. -The `DnsValidatedCertificate` construct exists to facilitate creating these certificates cross-region. This resource can only be used with -Route53-based DNS validation. +CloudFormation allows you to create a Stack with a CloudFront distribution in any region. In order +to create an ACM certificate in us-east-1 and reference it in a CloudFront distribution is a +different region, it is recommended to perform a multi stack deployment. + +Enable the Stack property `crossRegionReferences` +in order to access the cross stack/region certificate. + +> **This feature is currently experimental** ```ts -declare const myHostedZone: route53.HostedZone; -new acm.DnsValidatedCertificate(this, 'CrossRegionCertificate', { - domainName: 'hello.example.com', - hostedZone: myHostedZone, - region: 'us-east-1', +const stack1 = new Stack(app, 'Stack1', { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, +}); +const cert = new acm.Certificate(stack1, 'Cert', { + domainName: '*.example.com', + validation: acm.CertificateValidation.fromDns(PublicHostedZone.fromHostedZoneId(stack1, 'Zone', 'ZONE_ID')), +}); + +const stack2 = new Stack(app, 'Stack2', { + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, +}); + +new cloudfront.Distribution(stack2, 'Distribution', { + defaultBehavior: { + origin: new origins.HttpOrigin('example.com'), + }, + domainNames: ['dev.example.com'], + certificate: cert, }); ``` diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index 775178379728c..b2d2e0a35e6cd 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -66,6 +66,7 @@ export interface DnsValidatedCertificateProps extends CertificateProps { * validated using DNS validation against the specified Route 53 hosted zone. * * @resource AWS::CertificateManager::Certificate + * @deprecated use {@link Certificate} instead */ export class DnsValidatedCertificate extends CertificateBase implements ICertificate, cdk.ITaggable { public readonly certificateArn: string; diff --git a/packages/@aws-cdk/aws-certificatemanager/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-certificatemanager/rosetta/default.ts-fixture index 0a11d49d2511f..774bd94dbca22 100644 --- a/packages/@aws-cdk/aws-certificatemanager/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-certificatemanager/rosetta/default.ts-fixture @@ -1,12 +1,15 @@ // Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as route53 from '@aws-cdk/aws-route53'; +import { PublicHostedZone } from '@aws-cdk/aws-route53'; +import { StringParameter } from '@aws-cdk/aws-ssm'; +import { Stack, Names } from '@aws-cdk/core'; +import { AwsCustomResource, PhysicalResourceId, AwsCustomResourcePolicy } from '@aws-cdk/custom-resources'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); /// here } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts index 1f7fa5a64f455..f7c827cddfebd 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts @@ -1,10 +1,11 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import { HostedZone, PublicHostedZone } from '@aws-cdk/aws-route53'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack, Token, Tags, RemovalPolicy, Aws } from '@aws-cdk/core'; import { DnsValidatedCertificate } from '../lib/dns-validated-certificate'; -test('creates CloudFormation Custom Resource', () => { +testDeprecated('creates CloudFormation Custom Resource', () => { const stack = new Stack(); const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -94,7 +95,7 @@ test('creates CloudFormation Custom Resource', () => { }); }); -test('adds validation error on domain mismatch', () => { +testDeprecated('adds validation error on domain mismatch', () => { const stack = new Stack(); const helloDotComZone = new PublicHostedZone(stack, 'HelloDotCom', { @@ -111,7 +112,7 @@ test('adds validation error on domain mismatch', () => { }).toThrow(/DNS zone hello.com is not authoritative for certificate domain name example.com/); }); -test('does not try to validate unresolved tokens', () => { +testDeprecated('does not try to validate unresolved tokens', () => { const stack = new Stack(); const helloDotComZone = new PublicHostedZone(stack, 'HelloDotCom', { @@ -126,7 +127,7 @@ test('does not try to validate unresolved tokens', () => { Template.fromStack(stack); // does not throw }); -test('test root certificate', () => { +testDeprecated('test root certificate', () => { const stack = new Stack(); const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -152,7 +153,7 @@ test('test root certificate', () => { }); }); -test('test tags are passed to customresource', () => { +testDeprecated('test tags are passed to customresource', () => { const stack = new Stack(); Tags.of(stack).add('Key1', 'Value1'); @@ -182,7 +183,7 @@ test('test tags are passed to customresource', () => { }); }); -test('works with imported zone', () => { +testDeprecated('works with imported zone', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'Stack', { @@ -213,7 +214,7 @@ test('works with imported zone', () => { }); }); -test('works with imported role', () => { +testDeprecated('works with imported role', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'Stack', { @@ -238,7 +239,7 @@ test('works with imported role', () => { }); -test('throws when domain name is longer than 64 characters', () => { +testDeprecated('throws when domain name is longer than 64 characters', () => { const stack = new Stack(); const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -252,7 +253,7 @@ test('throws when domain name is longer than 64 characters', () => { }).toThrow(/Domain name must be 64 characters or less/); }), -test('does not throw when domain name is longer than 64 characters with tokens', () => { +testDeprecated('does not throw when domain name is longer than 64 characters with tokens', () => { const stack = new Stack(); const zoneName = 'example.com'; const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -293,7 +294,7 @@ test('does not throw when domain name is longer than 64 characters with tokens', }); }); -test('test transparency logging settings is passed to the custom resource', () => { +testDeprecated('test transparency logging settings is passed to the custom resource', () => { const stack = new Stack(); const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -321,7 +322,7 @@ test('test transparency logging settings is passed to the custom resource', () = }); }); -test('can set removal policy', () => { +testDeprecated('can set removal policy', () => { const stack = new Stack(); const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { diff --git a/packages/@aws-cdk/aws-certificatemanager/test/util.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/util.test.ts index 5c1ccf5315499..ce2203f005b41 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/util.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/util.test.ts @@ -1,4 +1,5 @@ import { PublicHostedZone } from '@aws-cdk/aws-route53'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Aws, Stack } from '@aws-cdk/core'; import { Certificate, DnsValidatedCertificate } from '../lib'; import { apexDomain, getCertificateRegion, isDnsValidatedCertificate } from '../lib/util'; @@ -15,7 +16,7 @@ describe('apex domain', () => { }); describe('isDnsValidatedCertificate', () => { - test('new DnsValidatedCertificate is a DnsValidatedCertificate', () => { + testDeprecated('new DnsValidatedCertificate is a DnsValidatedCertificate', () => { const stack = new Stack(); const hostedZone = new PublicHostedZone(stack, 'ExampleDotCom', { @@ -61,7 +62,7 @@ describe('getCertificateRegion', () => { expect(getCertificateRegion(certificate)).toEqual('eu-west-1'); }); - test('from DnsValidatedCertificate region', () => { + testDeprecated('from DnsValidatedCertificate region', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'RegionStack', { env: { region: 'eu-west-1' } }); diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index ecd6f06448885..95e0acb755f13 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -99,9 +99,9 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as route53 from '@aws-cdk/aws-route53'; declare const hostedZone: route53.HostedZone; -const myCertificate = new acm.DnsValidatedCertificate(this, 'mySiteCert', { +const myCertificate = new acm.Certificate(this, 'mySiteCert', { domainName: 'www.example.com', - hostedZone, + validation: acm.CertificateValidation.fromDns(hostedZone), }); declare const myBucket: s3.Bucket; @@ -574,7 +574,7 @@ just HTTP/3. For all supported HTTP versions, see the `HttpVerson` enum. ```ts // Configure a distribution to use HTTP/2 and HTTP/3 new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { origin: new origins.HttpOrigin('www.example.com'); }, + defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, httpVersion: cloudfront.HttpVersion.HTTP2_AND_3, }); ``` diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 57c2fc989e101..5cbb22774d95f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -1,6 +1,6 @@ import { Match, Template } from '@aws-cdk/assertions'; import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; -import { DnsValidatedCertificate } from '@aws-cdk/aws-certificatemanager'; +import { Certificate, CertificateValidation } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; @@ -1013,9 +1013,9 @@ test('domainName and domainZone not required for HTTPS listener with provided ce const exampleDotComZone = new route53.PublicHostedZone(stack, 'ExampleDotCom', { zoneName: 'example.com', }); - const certificate = new DnsValidatedCertificate(stack, 'Certificate', { + const certificate = new Certificate(stack, 'Certificate', { domainName: 'test.example.com', - hostedZone: exampleDotComZone, + validation: CertificateValidation.fromDns(exampleDotComZone), }); // WHEN diff --git a/packages/@aws-cdk/aws-route53-patterns/test/bucket-website-target.test.ts b/packages/@aws-cdk/aws-route53-patterns/test/bucket-website-target.test.ts index 972129fa02f79..145ee1c48b5f1 100644 --- a/packages/@aws-cdk/aws-route53-patterns/test/bucket-website-target.test.ts +++ b/packages/@aws-cdk/aws-route53-patterns/test/bucket-website-target.test.ts @@ -1,11 +1,12 @@ import { Template } from '@aws-cdk/assertions'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; import { HostedZone } from '@aws-cdk/aws-route53'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import { ROUTE53_PATTERNS_USE_CERTIFICATE } from '@aws-cdk/cx-api'; import { HttpsRedirect } from '../lib'; -test('create HTTPS redirect', () => { +testDeprecated('create HTTPS redirect', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'test', { env: { region: 'us-east-1' } }); @@ -63,7 +64,7 @@ test('create HTTPS redirect', () => { }); }); -test('create HTTPS redirect for apex', () => { +testDeprecated('create HTTPS redirect for apex', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'test', { env: { region: 'us-east-1' } }); @@ -96,7 +97,7 @@ test('create HTTPS redirect for apex', () => { }); }); -test('create HTTPS redirect with existing cert', () => { +testDeprecated('create HTTPS redirect with existing cert', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'test', { env: { region: 'us-east-1' } });