Skip to content

Commit

Permalink
fix: use domain names in policy instead of zone name
Browse files Browse the repository at this point in the history
  • Loading branch information
trautonen committed Apr 29, 2024
1 parent 71d53ff commit f237572
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 21 deletions.
42 changes: 25 additions & 17 deletions src/dns-validated-certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as custom_resources from 'aws-cdk-lib/custom-resources'
import { Construct } from 'constructs'
import { CertificateRequestorFunction } from './certificate-requestor-function'
import { Properties, ValidationHostedZoneProperties } from './certificate-requestor.lambda'
import { booleanToString, cleanDomainName, cleanHostedZoneId } from './utils'
import { booleanToString, cleanDomainName, cleanHostedZoneId, matchNamesToZones } from './utils'

export interface ValidationHostedZone {
/**
Expand Down Expand Up @@ -242,6 +242,11 @@ export class DnsValidatedCertificate extends cdk.Resource implements certificate
})
)

const domainsToZones = matchNamesToZones(
props.validationHostedZones.map((zone) => zone.hostedZone.zoneName),
allDomains,
(domain) => domain
)
const hostedZonesWithRole = props.validationHostedZones.filter((zone) => zone.validationRole !== undefined)
const hostedZonesWithoutRole = props.validationHostedZones.filter((zone) => zone.validationRole === undefined)

Expand All @@ -262,23 +267,26 @@ export class DnsValidatedCertificate extends cdk.Resource implements certificate
resources: ['*'],
})
)
requestorFunction.addToRolePolicy(
new iam.PolicyStatement({
actions: ['route53:ChangeResourceRecordSets'],
resources: [`arn:aws:route53:::hostedzone/${this.normalizeHostedZoneId(zone.hostedZone.hostedZoneId)}`],
conditions: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['CNAME'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
'ForAllValues:StringLike': {
'route53:ChangeResourceRecordSetsNormalizedRecordNames': [
this.wildcardDomainName('MainDomainWildcard', this.normalizeDomainName(zone.hostedZone.zoneName)),
],
const domainNames = domainsToZones[zone.hostedZone.zoneName]
if (domainNames && domainNames.length > 0) {
requestorFunction.addToRolePolicy(
new iam.PolicyStatement({
actions: ['route53:ChangeResourceRecordSets'],
resources: [`arn:aws:route53:::hostedzone/${this.normalizeHostedZoneId(zone.hostedZone.hostedZoneId)}`],
conditions: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['CNAME'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
'ForAllValues:StringLike': {
'route53:ChangeResourceRecordSetsNormalizedRecordNames': domainNames.map((name, index) => {
return this.wildcardDomainName(`DomainWildcard${zone.hostedZone.hostedZoneId}${index}`, name)
}),
},
},
},
})
)
})
)
}
})

const requestorProvider = new custom_resources.Provider(this, 'RequestorProvider', {
Expand Down
33 changes: 31 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const containsSame = <T>(array1: T[], array2: T[]): boolean => {
export const orderBySignificance = (domains: string[]): string[] => {
const copy = [...domains]
copy.sort((a, b) => {
const ac = a.split('.').length
const bc = b.split('.').length
const ac = cleanDomainName(a).split('.').length
const bc = cleanDomainName(b).split('.').length
if (ac > bc) {
return -1
}
Expand All @@ -48,6 +48,35 @@ export const cleanChangeId = (changeId: string): string => {
return changeId.replace('/change/', '')
}

export const matchNamesToZones = <T>(
zoneNames: string[],
records: T[],
name: (record: T) => string
): Record<string, T[]> => {
const orderedZoneNames = orderBySignificance(zoneNames)
const matcher = (zones: string[], items: T[], result: Record<string, T[]>): Record<string, T[]> => {
const [firstZone, ...restZones] = zones
if (!firstZone) {
return result
}
const matchingItems: T[] = []
const unmatchingItems: T[] = []
for (const item of items) {
const normalizedRecordName = cleanDomainName(name(item))
if (normalizedRecordName.endsWith(cleanDomainName(firstZone))) {
matchingItems.push(item)
} else {
unmatchingItems.push(item)
}
}
return matcher(restZones, unmatchingItems, {
...result,
[firstZone]: matchingItems,
})
}
return matcher(orderedZoneNames, records, {})
}

export const tryFor = async <T>(maxSeconds: number, timeoutError: string, fn: () => Promise<T | null>): Promise<T> => {
const startTime = Date.now()
// eslint-disable-next-line no-constant-condition
Expand Down
13 changes: 11 additions & 2 deletions test/dns-validated-certificate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@ test('DnsValidatedCertificate is created', () => {
const app = new cdk.App()
const stack = new cdk.Stack(app, 'TestStack', {})

const hostedZone = route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
const mainHostedZone = route53.HostedZone.fromHostedZoneAttributes(stack, 'MainHostedZone', {
hostedZoneId: 'Z53279245PYHBAN3YU2K',
zoneName: 'example.com',
})

const secondaryHostedZone = route53.HostedZone.fromHostedZoneAttributes(stack, 'SecondaryHostedZone', {
hostedZoneId: 'Z73479245BAEBAN3YK4V',
zoneName: 'secondary.com',
})

new DnsValidatedCertificate(stack, 'Certificate', {
domainName: 'example.com',
alternativeDomainNames: ['test.secondary.com'],
validationHostedZones: [
{
hostedZone,
hostedZone: mainHostedZone,
},
{
hostedZone: secondaryHostedZone,
},
],
})
Expand Down

0 comments on commit f237572

Please sign in to comment.