From 6249fa58887952bdc65cafa4fb5530b4c52a9d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20=C5=A0unji=C4=87?= Date: Tue, 9 Dec 2025 09:41:31 +0100 Subject: [PATCH 1/4] Add subjectAlternativeNames option arg --- src/v2/components/acm-certificate/index.ts | 69 ++++++++++++++-------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/src/v2/components/acm-certificate/index.ts b/src/v2/components/acm-certificate/index.ts index 30e8eaf..00e9873 100644 --- a/src/v2/components/acm-certificate/index.ts +++ b/src/v2/components/acm-certificate/index.ts @@ -4,6 +4,10 @@ import { commonTags } from '../../../constants'; export type AcmCertificateArgs = { domain: pulumi.Input; + /** + * Additional domains/subdomains to be included in this certificate. + */ + subjectAlternativeNames?: pulumi.Input[]; hostedZoneId: pulumi.Input; }; @@ -19,36 +23,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.createCertificationValidationRecords(args.domain, args.hostedZoneId); + this.registerOutputs(); } + + private createCertificationValidationRecords( + domainName: AcmCertificateArgs['domain'], + hostedZoneId: AcmCertificateArgs['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 }, + ); + }); + } } From 710a1023547c2c0c995d38f292b2a9b82d899d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20=C5=A0unji=C4=87?= Date: Tue, 9 Dec 2025 11:21:41 +0100 Subject: [PATCH 2/4] Add test for cert with SANs --- tests/acm-certificate/index.test.ts | 25 +++++++++++++++++++ tests/acm-certificate/infrastructure/index.ts | 11 ++++++++ tests/acm-certificate/test-context.ts | 1 + 3 files changed, 37 insertions(+) diff --git a/tests/acm-certificate/index.test.ts b/tests/acm-certificate/index.test.ts index 9cd8d3e..6363f80 100644 --- a/tests/acm-certificate/index.test.ts +++ b/tests/acm-certificate/index.test.ts @@ -32,6 +32,7 @@ describe('ACM Certificate component deployment', () => { outputs: {}, config: { certificateName: 'acm-cert-test-cert', + subDomainName: `app.${process.env.DOMAIN_NAME!}`, exponentialBackOffConfig: { delayFirstAttempt: true, numOfAttempts: 5, @@ -123,4 +124,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}`, + ); + }); + }); }); diff --git a/tests/acm-certificate/infrastructure/index.ts b/tests/acm-certificate/infrastructure/index.ts index afd4520..dcd84de 100644 --- a/tests/acm-certificate/infrastructure/index.ts +++ b/tests/acm-certificate/infrastructure/index.ts @@ -13,7 +13,18 @@ const certificate = new studion.AcmCertificate(`${appName}-certificate`, { hostedZoneId: hostedZone.zoneId, }); +const subDomainName = `app.${process.env.DOMAIN_NAME!}`; +const sanCertificate = new studion.AcmCertificate( + `${appName}-certificate-san`, + { + domain: subDomainName, + subjectAlternativeNames: [`api.${subDomainName}`, `test.${subDomainName}`], + hostedZoneId: hostedZone.zoneId, + }, +); + module.exports = { certificate, + sanCertificate, hostedZone, }; diff --git a/tests/acm-certificate/test-context.ts b/tests/acm-certificate/test-context.ts index 2ddda33..0fa9ead 100644 --- a/tests/acm-certificate/test-context.ts +++ b/tests/acm-certificate/test-context.ts @@ -4,6 +4,7 @@ import { Route53Client } from '@aws-sdk/client-route-53'; interface AcmCertificateTestConfig { certificateName: string; + subDomainName: string; exponentialBackOffConfig: { delayFirstAttempt: boolean; numOfAttempts: number; From 9a826288dac71603452a7c0ac07d49bb2eab9d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20=C5=A0unji=C4=87?= Date: Wed, 10 Dec 2025 10:40:36 +0100 Subject: [PATCH 3/4] Refactor get zone method args --- tests/acm-certificate/infrastructure/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acm-certificate/infrastructure/index.ts b/tests/acm-certificate/infrastructure/index.ts index 5666b04..a3a1b22 100644 --- a/tests/acm-certificate/infrastructure/index.ts +++ b/tests/acm-certificate/infrastructure/index.ts @@ -9,7 +9,7 @@ const domainName = process.env.DOMAIN_NAME!; const hostedZone = pulumi.output( aws.route53 .getZone({ - name: `${domainName}`, + name: domainName, privateZone: false, }) .catch(() => { From 034c44737f5a569551a969a782fce8038453e37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20=C5=A0unji=C4=87?= Date: Wed, 10 Dec 2025 21:36:50 +0100 Subject: [PATCH 4/4] Fix method name typo --- src/v2/components/acm-certificate/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v2/components/acm-certificate/index.ts b/src/v2/components/acm-certificate/index.ts index 8856ed4..76ed8c0 100644 --- a/src/v2/components/acm-certificate/index.ts +++ b/src/v2/components/acm-certificate/index.ts @@ -34,12 +34,12 @@ export class AcmCertificate extends pulumi.ComponentResource { { parent: this }, ); - this.createCertificationValidationRecords(args.domain, args.hostedZoneId); + this.createCertValidationRecords(args.domain, args.hostedZoneId); this.registerOutputs(); } - private createCertificationValidationRecords( + private createCertValidationRecords( domainName: AcmCertificate.Args['domain'], hostedZoneId: AcmCertificate.Args['hostedZoneId'], ) {