Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 44 additions & 25 deletions src/v2/components/acm-certificate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { commonTags } from '../../../constants';
export namespace AcmCertificate {
export type Args = {
domain: pulumi.Input<string>;
/**
* Additional domains/subdomains to be included in this certificate.
*/
subjectAlternativeNames?: pulumi.Input<string>[];
hostedZoneId: pulumi.Input<string>;
};
}
Expand All @@ -21,36 +25,51 @@ export class AcmCertificate extends pulumi.ComponentResource {

this.certificate = new aws.acm.Certificate(
`${args.domain}-certificate`,
{ domainName: args.domain, validationMethod: 'DNS', tags: commonTags },
{ parent: this },
);

const certificateValidationDomain = new aws.route53.Record(
`${args.domain}-cert-validation-domain`,
{
name: this.certificate.domainValidationOptions[0].resourceRecordName,
type: this.certificate.domainValidationOptions[0].resourceRecordType,
zoneId: args.hostedZoneId,
records: [
this.certificate.domainValidationOptions[0].resourceRecordValue,
],
ttl: 600,
},
{
parent: this,
deleteBeforeReplace: true,
},
);

const certificateValidation = new aws.acm.CertificateValidation(
`${args.domain}-cert-validation`,
{
certificateArn: this.certificate.arn,
validationRecordFqdns: [certificateValidationDomain.fqdn],
domainName: args.domain,
subjectAlternativeNames: args.subjectAlternativeNames,
validationMethod: 'DNS',
tags: commonTags,
},
{ parent: this },
);

this.createCertValidationRecords(args.domain, args.hostedZoneId);

this.registerOutputs();
}

private createCertValidationRecords(
domainName: AcmCertificate.Args['domain'],
hostedZoneId: AcmCertificate.Args['hostedZoneId'],
) {
this.certificate.domainValidationOptions.apply(domains => {
const validationRecords = domains.map(
domain =>
new aws.route53.Record(
`${domain.domainName}-cert-validation-domain`,
{
name: domain.resourceRecordName,
type: domain.resourceRecordType,
zoneId: hostedZoneId,
records: [domain.resourceRecordValue],
ttl: 600,
},
{
parent: this,
deleteBeforeReplace: true,
},
),
);

const certificateValidation = new aws.acm.CertificateValidation(
`${domainName}-cert-validation`,
{
certificateArn: this.certificate.arn,
validationRecordFqdns: validationRecords.map(record => record.fqdn),
},
{ parent: this },
);
});
}
}
25 changes: 25 additions & 0 deletions tests/acm-certificate/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('ACM Certificate component deployment', () => {
const ctx: AcmCertificateTestContext = {
outputs: {},
config: {
subDomainName: `app.${domainName}`,
exponentialBackOffConfig: {
delayFirstAttempt: true,
numOfAttempts: 5,
Expand Down Expand Up @@ -119,4 +120,28 @@ describe('ACM Certificate component deployment', () => {
'Validation record should have correct value',
);
});

it('should create certificate with subject alternative names', async () => {
const sanCertificate = ctx.outputs.sanCertificate.value;
const certResult = await ctx.clients.acm.send(
new DescribeCertificateCommand({
CertificateArn: sanCertificate.certificate.arn,
}),
);
const cert = certResult.Certificate;
const sans = cert?.SubjectAlternativeNames || [];

const expectedDomains = [
ctx.config.subDomainName,
`api.${ctx.config.subDomainName}`,
`test.${ctx.config.subDomainName}`,
];

expectedDomains.forEach(expectedDomain => {
assert.ok(
sans.includes(expectedDomain),
`Certificate should include: ${expectedDomain}`,
);
});
});
});
15 changes: 13 additions & 2 deletions tests/acm-certificate/infrastructure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ const hostedZone = aws.route53.getZoneOutput({
privateZone: false,
});

const domainName = process.env.ICB_DOMAIN_NAME!;
const certificate = new studion.AcmCertificate(`${appName}-certificate`, {
domain: process.env.ICB_DOMAIN_NAME!,
domain: domainName,
hostedZoneId: hostedZone.zoneId,
});

export { certificate, hostedZone };
const subDomainName = `app.${domainName}`;
const sanCertificate = new studion.AcmCertificate(
`${appName}-certificate-san`,
{
domain: subDomainName,
subjectAlternativeNames: [`api.${subDomainName}`, `test.${subDomainName}`],
hostedZoneId: hostedZone.zoneId,
},
);

export { certificate, sanCertificate, hostedZone };
1 change: 1 addition & 0 deletions tests/acm-certificate/test-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ACMClient } from '@aws-sdk/client-acm';
import { Route53Client } from '@aws-sdk/client-route-53';

interface AcmCertificateTestConfig {
subDomainName: string;
exponentialBackOffConfig: {
delayFirstAttempt: boolean;
numOfAttempts: number;
Expand Down