Skip to content

Commit

Permalink
feat(certificatemanager): requesting private certificates issued by P…
Browse files Browse the repository at this point in the history
…rivate Certificate Authority (#16315)

Support requesting private certificates issued by Private Certificate Authority. 

Similar to the existing construct named `Certificate`, a new construct `PrivateCertificate` was introduced. There are two main differences between them. `PrivateCertificate` has an additional property `certificateAuthority` to specify the Private certificate authority (CA) that will be used to issue the certificate. The validation options are removed because no validation is necessary for private certificates.

Closes #10076.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jumic committed Nov 1, 2021
1 parent a9aae09 commit e26f5be
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-certificatemanager/README.md
Expand Up @@ -113,6 +113,21 @@ new acm.DnsValidatedCertificate(this, 'CrossRegionCertificate', {
});
```

## Requesting private certificates

AWS Certificate Manager can create [private certificates](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-private.html) issued by [Private Certificate Authority (PCA)](https://docs.aws.amazon.com/acm-pca/latest/userguide/PcaWelcome.html). Validation of private certificates is not necessary.

```ts
import * as acmpca from '@aws-cdk/aws-acmpca';

new acm.PrivateCertificate(stack, 'PrivateCertificate', {
domainName: 'test.example.com',
subjectAlternativeNames: ['cool.example.com', 'test.example.net'], // optional
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
});
```

## Importing

If you want to import an existing certificate, you can do so from its ARN:
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-certificatemanager/lib/index.ts
@@ -1,5 +1,6 @@
export * from './certificate';
export * from './dns-validated-certificate';
export * from './private-certificate';
export * from './util';

// AWS::CertificateManager CloudFormation Resources:
Expand Down
@@ -0,0 +1,66 @@
import * as acmpca from '@aws-cdk/aws-acmpca';
import { Construct } from 'constructs';
import { ICertificate } from './certificate';
import { CertificateBase } from './certificate-base';
import { CfnCertificate } from './certificatemanager.generated';

/**
* Properties for your private certificate
*/
export interface PrivateCertificateProps {
/**
* Fully-qualified domain name to request a private certificate for.
*
* May contain wildcards, such as ``*.domain.com``.
*/
readonly domainName: string;

/**
* Alternative domain names on your private certificate.
*
* Use this to register alternative domain names that represent the same site.
*
* @default - No additional FQDNs will be included as alternative domain names.
*/
readonly subjectAlternativeNames?: string[];

/**
* Private certificate authority (CA) that will be used to issue the certificate.
*/
readonly certificateAuthority: acmpca.ICertificateAuthority;
}

/**
* A private certificate managed by AWS Certificate Manager
*
* @resource AWS::CertificateManager::Certificate
*/
export class PrivateCertificate extends CertificateBase implements ICertificate {
/**
* Import a certificate
*/
public static fromCertificateArn(scope: Construct, id: string, certificateArn: string): ICertificate {
class Import extends CertificateBase {
public readonly certificateArn = certificateArn;
}

return new Import(scope, id);
}

/**
* The certificate's ARN
*/
public readonly certificateArn: string;

constructor(scope: Construct, id: string, props: PrivateCertificateProps) {
super(scope, id);

const cert = new CfnCertificate(this, 'Resource', {
domainName: props.domainName,
subjectAlternativeNames: props.subjectAlternativeNames,
certificateAuthorityArn: props.certificateAuthority.certificateAuthorityArn,
});

this.certificateArn = cert.ref;
}
}
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-certificatemanager/package.json
Expand Up @@ -79,6 +79,7 @@
"@types/jest": "^26.0.24"
},
"dependencies": {
"@aws-cdk/aws-acmpca": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
Expand All @@ -88,6 +89,7 @@
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-acmpca": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
Expand All @@ -101,7 +103,8 @@
"awslint": {
"exclude": [
"props-physical-name:@aws-cdk/aws-certificatemanager.CertificateProps",
"props-physical-name:@aws-cdk/aws-certificatemanager.DnsValidatedCertificateProps"
"props-physical-name:@aws-cdk/aws-certificatemanager.DnsValidatedCertificateProps",
"props-physical-name:@aws-cdk/aws-certificatemanager.PrivateCertificateProps"
]
},
"stability": "stable",
Expand Down
@@ -0,0 +1,102 @@
import '@aws-cdk/assert-internal/jest';
import * as acmpca from '@aws-cdk/aws-acmpca';
import { Duration, Lazy, Stack } from '@aws-cdk/core';
import { PrivateCertificate } from '../lib';

test('private certificate authority', () => {
const stack = new Stack();

new PrivateCertificate(stack, 'Certificate', {
domainName: 'test.example.com',
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
});

expect(stack).toHaveResource('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77',
});
});

test('private certificate authority with subjectAlternativeNames', () => {
const stack = new Stack();

new PrivateCertificate(stack, 'Certificate', {
domainName: 'test.example.com',
subjectAlternativeNames: ['extra.example.com'],
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
});

expect(stack).toHaveResource('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
SubjectAlternativeNames: ['extra.example.com'],
CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77',
});
});

test('private certificate authority with multiple subjectAlternativeNames', () => {
const stack = new Stack();

new PrivateCertificate(stack, 'Certificate', {
domainName: 'test.example.com',
subjectAlternativeNames: ['*.test.example.com', '*.foo.test.example.com', 'bar.test.example.com'],
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
});

expect(stack).toHaveResource('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
SubjectAlternativeNames: ['*.test.example.com', '*.foo.test.example.com', 'bar.test.example.com'],
CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77',
});
});

test('private certificate authority with tokens', () => {
const stack = new Stack();

const certificateAuthority = Lazy.string({
produce: () => 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77',
});

const domainName = Lazy.string({
produce: () => 'test.example.com',
});

const domainNameAlternative = Lazy.string({
produce: () => 'extra.example.com',
});

new PrivateCertificate(stack, 'Certificate', {
domainName,
subjectAlternativeNames: [domainNameAlternative],
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', certificateAuthority),
});

expect(stack).toHaveResource('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
SubjectAlternativeNames: ['extra.example.com'],
CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77',
});
});

test('metricDaysToExpiry', () => {
const stack = new Stack();

const certificate = new PrivateCertificate(stack, 'Certificate', {
domainName: 'test.example.com',
certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA',
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'),
});

expect(stack.resolve(certificate.metricDaysToExpiry().toMetricConfig())).toEqual({
metricStat: {
dimensions: [{ name: 'CertificateArn', value: stack.resolve(certificate.certificateArn) }],
metricName: 'DaysToExpiry',
namespace: 'AWS/CertificateManager',
period: Duration.days(1),
statistic: 'Minimum',
},
renderingProperties: expect.anything(),
});
});

0 comments on commit e26f5be

Please sign in to comment.