Skip to content

Commit c158e80

Browse files
authored
feat(acm): add support for ACM exportable public certificates (aws#35079)
### Issue # (if applicable) Closes aws#35078. ### Reason for this change We want to use ACM exportable public certificates to replace all those certificates purchased from another vendor ### Description of changes Add `certificateExportEnabled` in `acm.Certificate` Construct ### Describe any new or updated permissions being added N/A ### Description of how you validated changes Unit updated to cover new `certificateExportEnabled` property ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 83f0a61 commit c158e80

File tree

8 files changed

+135
-3
lines changed

8 files changed

+135
-3
lines changed

packages/aws-cdk-lib/aws-certificatemanager/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,17 @@ new acm.PrivateCertificate(this, 'PrivateCertificate', {
146146
});
147147
```
148148

149+
## Requesting public SSL/TLS certificates exportable to use anywhere
150+
151+
AWS Certificate Manager can issue an exportable public certificate. There is a charge at certificate issuance and again when the certificate renews. See [opting out of certificate transparency logging](https://docs.aws.amazon.com/acm/latest/userguide/acm-exportable-certificates.html) for details.
152+
153+
```ts
154+
new acm.Certificate(this, 'Certificate', {
155+
domainName: 'test.example.com',
156+
allowExport: true,
157+
});
158+
```
159+
149160
## Requesting certificates without transparency logging
150161

151162
Transparency logging can be opted out of for AWS Certificate Manager certificates. See [opting out of certificate transparency logging](https://docs.aws.amazon.com/acm/latest/userguide/acm-bestpractices.html#best-practices-transparency) for limits.

packages/aws-cdk-lib/aws-certificatemanager/lib/certificate.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ export interface CertificateProps {
8080
*/
8181
readonly validation?: CertificateValidation;
8282

83+
/**
84+
* Enable or disable export of this certificate.
85+
*
86+
* If you issue an exportable public certificate, there is a charge at certificate issuance and again when the certificate renews.
87+
* Ref: https://aws.amazon.com/certificate-manager/pricing
88+
*
89+
* @default false
90+
*/
91+
readonly allowExport?: boolean;
92+
8393
/**
8494
* Enable or disable transparency logging for this certificate
8595
*
@@ -319,6 +329,8 @@ export class Certificate extends CertificateBase implements ICertificate {
319329

320330
const allDomainNames = [props.domainName].concat(props.subjectAlternativeNames || []);
321331

332+
const certificateExport = (props.allowExport === true) ? 'ENABLED' : undefined;
333+
322334
let certificateTransparencyLoggingPreference: string | undefined;
323335
if (props.transparencyLoggingEnabled !== undefined) {
324336
certificateTransparencyLoggingPreference = props.transparencyLoggingEnabled ? 'ENABLED' : 'DISABLED';
@@ -329,6 +341,7 @@ export class Certificate extends CertificateBase implements ICertificate {
329341
subjectAlternativeNames: props.subjectAlternativeNames,
330342
domainValidationOptions: renderDomainValidation(this, validation, allDomainNames),
331343
validationMethod: validation.method,
344+
certificateExport,
332345
certificateTransparencyLoggingPreference,
333346
keyAlgorithm: props.keyAlgorithm?.name,
334347
});

packages/aws-cdk-lib/aws-certificatemanager/lib/private-certificate.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ export interface PrivateCertificateProps {
4242
* @default KeyAlgorithm.RSA_2048
4343
*/
4444
readonly keyAlgorithm?: KeyAlgorithm;
45+
46+
/**
47+
* Enable or disable export of this certificate.
48+
*
49+
* If you issue an exportable public certificate, there is a charge at certificate issuance and again when the certificate renews.
50+
* Ref: https://aws.amazon.com/certificate-manager/pricing
51+
*
52+
* @default false
53+
*/
54+
readonly allowExport?: boolean;
4555
}
4656

4757
/**
@@ -75,11 +85,14 @@ export class PrivateCertificate extends CertificateBase implements ICertificate
7585
// Enhanced CDK Analytics Telemetry
7686
addConstructMetadata(this, props);
7787

88+
const certificateExport = (props.allowExport === true) ? 'ENABLED' : undefined;
89+
7890
const cert = new CfnCertificate(this, 'Resource', {
7991
domainName: props.domainName,
8092
subjectAlternativeNames: props.subjectAlternativeNames,
8193
certificateAuthorityArn: props.certificateAuthority.certificateAuthorityArn,
8294
keyAlgorithm: props.keyAlgorithm?.name,
95+
certificateExport,
8396
});
8497

8598
this.certificateArn = cert.ref;

packages/aws-cdk-lib/aws-certificatemanager/test/certificate.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,48 @@ test('CertificateValidation.fromDnsMultiZone', () => {
363363
});
364364
});
365365

366+
describe('Certificate export setting', () => {
367+
test('leaves certificate export setting untouched by default', () => {
368+
const stack = new Stack();
369+
370+
new Certificate(stack, 'Certificate', {
371+
domainName: 'test.example.com',
372+
});
373+
374+
const certificateNodes = Template.fromStack(stack).findResources('AWS::CertificateManager::Certificate');
375+
expect(certificateNodes.Certificate4E7ABB08).toBeDefined();
376+
expect(certificateNodes.Certificate4E7ABB08.CertificateTransparencyLoggingPreference).toBeUndefined();
377+
});
378+
379+
test('can enable certificate export', () => {
380+
const stack = new Stack();
381+
382+
new Certificate(stack, 'Certificate', {
383+
domainName: 'test.example.com',
384+
allowExport: true,
385+
});
386+
387+
Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
388+
DomainName: 'test.example.com',
389+
CertificateExport: 'ENABLED',
390+
});
391+
});
392+
393+
test('can disable certificate export', () => {
394+
const stack = new Stack();
395+
396+
new Certificate(stack, 'Certificate', {
397+
domainName: 'test.example.com',
398+
allowExport: false,
399+
});
400+
401+
Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
402+
DomainName: 'test.example.com',
403+
CertificateExport: Match.absent(),
404+
});
405+
});
406+
});
407+
366408
describe('Transparency logging settings', () => {
367409
test('leaves transparency logging untouched by default', () => {
368410
const stack = new Stack();

packages/aws-cdk-lib/aws-certificatemanager/test/dns-validated-certificate.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ testDeprecated('creates CloudFormation Custom Resource @aws-cdk/aws-lambda:creat
4141
});
4242
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
4343
Handler: 'index.certificateRequestHandler',
44-
Runtime: 'nodejs22.x',
4544
Timeout: 900,
4645
});
4746
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
@@ -136,7 +135,6 @@ testDeprecated('creates CloudFormation Custom Resource @aws-cdk/aws-lambda:creat
136135
});
137136
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
138137
Handler: 'index.certificateRequestHandler',
139-
Runtime: 'nodejs22.x',
140138
Timeout: 900,
141139
});
142140
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {

packages/aws-cdk-lib/aws-certificatemanager/test/private-certificate.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,51 @@ describe('Key Algorithm', () => {
146146
});
147147
});
148148
});
149+
150+
describe('Certificate export setting', () => {
151+
test('leaves certificate export setting untouched by default', () => {
152+
const stack = new Stack();
153+
154+
new PrivateCertificate(stack, 'Certificate', {
155+
domainName: 'test.example.com',
156+
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
157+
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
158+
});
159+
160+
const certificateNodes = Template.fromStack(stack).findResources('AWS::CertificateManager::Certificate');
161+
expect(certificateNodes.Certificate4E7ABB08).toBeDefined();
162+
expect(certificateNodes.Certificate4E7ABB08.CertificateTransparencyLoggingPreference).toBeUndefined();
163+
});
164+
165+
test('can enable certificate export', () => {
166+
const stack = new Stack();
167+
168+
new PrivateCertificate(stack, 'Certificate', {
169+
domainName: 'test.example.com',
170+
allowExport: true,
171+
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
172+
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
173+
});
174+
175+
Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
176+
DomainName: 'test.example.com',
177+
CertificateExport: 'ENABLED',
178+
});
179+
});
180+
181+
test('can disable certificate export', () => {
182+
const stack = new Stack();
183+
184+
new PrivateCertificate(stack, 'Certificate', {
185+
domainName: 'test.example.com',
186+
allowExport: false,
187+
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
188+
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
189+
});
190+
191+
Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
192+
DomainName: 'test.example.com',
193+
CertificateExport: Match.absent(),
194+
});
195+
});
196+
});

tools/@aws-cdk/spec2cdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@
6565
"dependencies/cdk-point-dependencies"
6666
]
6767
}
68-
}
68+
}

yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@
152152
dependencies:
153153
"@cdklabs/tskb" "^0.0.3"
154154

155+
"@aws-cdk/service-spec-types@^0.0.155":
156+
version "0.0.155"
157+
resolved "https://registry.npmjs.org/@aws-cdk/service-spec-types/-/service-spec-types-0.0.155.tgz#a38ad6291700b38ef4f2e763555065811cae6c67"
158+
integrity sha512-Z4kwxvQesTkbD33uZorUicIUlHlP8/fVunOa/1LGqwQw55b1gbKiZE+2o3tgm2YIOHMEsP/p1ZjqW71cFtOCyg==
159+
dependencies:
160+
"@cdklabs/tskb" "^0.0.3"
161+
155162
"@aws-crypto/[email protected]":
156163
version "5.2.0"
157164
resolved "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1"

0 commit comments

Comments
 (0)