Skip to content

Commit

Permalink
feat(certificatemanager): deprecate DnsValidatedCertificate (#21982)
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
corymhall authored Jan 25, 2023
1 parent ba14612 commit 64bfbf9
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 31 deletions.
39 changes: 32 additions & 7 deletions packages/@aws-cdk/aws-certificatemanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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', {
Expand Down Expand Up @@ -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', {
Expand All @@ -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', {
Expand All @@ -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', {
Expand All @@ -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');

Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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', {
Expand All @@ -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', {
Expand All @@ -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', {
Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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', {
Expand Down
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-certificatemanager/test/util.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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', {
Expand Down Expand Up @@ -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' } });
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-cloudfront/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
});
```
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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' } });
Expand Down Expand Up @@ -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' } });
Expand Down Expand Up @@ -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' } });
Expand Down

0 comments on commit 64bfbf9

Please sign in to comment.