ACM Certificates for Cross-Account Domains

Create and validate ACM certificates for domains that require cross-account DNS validation.

Background

Orcha uses a subdomain delegation pattern where:

For production, we want vanity domains without the prod subdomain:

ACM certificates covering both *.prod.getorcha.com and *.getorcha.com require DNS validation records in two different accounts, which CDK cannot automate. These certificates must be created manually.

Certificates Required

Certificate Domains Used By
App Certificate app.prod.getorcha.com, app.getorcha.com ALB HTTPS listener (default)
Admin Certificate admin.prod.getorcha.com, admin.getorcha.com ALB HTTPS listener (SNI)

When to Run

Before deploying ComputeStack for the first time (app certificate), or before adding admin infrastructure (admin certificate).

Prerequisites

  1. DNS delegation complete - NS records for prod.getorcha.com point to prod account
  2. AWS CLI profiles configured:
  3. Route53 hosted zone IDs:

Steps

cd /home/volrath/code/orcha/orcha/infra

# App certificate
./scripts/create-cross-account-cert.sh \
    --name app \
    --primary-domain app.prod.getorcha.com \
    --san app.getorcha.com

# Admin certificate
./scripts/create-cross-account-cert.sh \
    --name admin \
    --primary-domain admin.prod.getorcha.com \
    --san admin.getorcha.com

The script will:

  1. Request the certificate in the prod account
  2. Display validation records needed for each account
  3. Wait for you to add the records
  4. Verify the certificate is issued
  5. Output the ARN for use in CDK

Option B: Manual Process

1. Request the Certificate

# App certificate
AWS_PROFILE=orcha-prod aws acm request-certificate \
    --region eu-central-1 \
    --domain-name app.prod.getorcha.com \
    --subject-alternative-names app.getorcha.com \
    --validation-method DNS \
    --output json

# Admin certificate
AWS_PROFILE=orcha-prod aws acm request-certificate \
    --region eu-central-1 \
    --domain-name admin.prod.getorcha.com \
    --subject-alternative-names admin.getorcha.com \
    --validation-method DNS \
    --output json

Note the Certificate ARN from the output.

2. Get Validation Records

AWS_PROFILE=orcha-prod aws acm describe-certificate \
    --region eu-central-1 \
    --certificate-arn <CERTIFICATE_ARN> \
    --query 'Certificate.DomainValidationOptions[*].{Domain:DomainName,Name:ResourceRecord.Name,Value:ResourceRecord.Value}' \
    --output table

This will show two CNAME records needed:

3. Add Validation Records

For *.prod.getorcha.com (prod account):

# Get the prod hosted zone ID
PROD_ZONE_ID=$(AWS_PROFILE=orcha-prod aws route53 list-hosted-zones \
    --query "HostedZones[?Name=='prod.getorcha.com.'].Id" \
    --output text | sed 's|/hostedzone/||')

AWS_PROFILE=orcha-prod aws route53 change-resource-record-sets \
    --hosted-zone-id $PROD_ZONE_ID \
    --change-batch '{
      "Changes": [{
        "Action": "UPSERT",
        "ResourceRecordSet": {
          "Name": "<VALIDATION_NAME_FOR_PROD_DOMAIN>",
          "Type": "CNAME",
          "TTL": 300,
          "ResourceRecords": [{"Value": "<VALIDATION_VALUE>"}]
        }
      }]
    }'

For *.getorcha.com (management account):

AWS_PROFILE=orcha aws route53 change-resource-record-sets \
    --hosted-zone-id Z02414383CQNYTPGX2EIK \
    --change-batch '{
      "Changes": [{
        "Action": "UPSERT",
        "ResourceRecordSet": {
          "Name": "<VALIDATION_NAME_FOR_ROOT_DOMAIN>",
          "Type": "CNAME",
          "TTL": 300,
          "ResourceRecords": [{"Value": "<VALIDATION_VALUE>"}]
        }
      }]
    }'

4. Wait for Validation

# Check status (repeat until ISSUED)
AWS_PROFILE=orcha-prod aws acm describe-certificate \
    --region eu-central-1 \
    --certificate-arn <CERTIFICATE_ARN> \
    --query 'Certificate.Status' \
    --output text

Validation typically takes 5-30 minutes.

5. Update CDK Code

Add the certificate ARN to stacks/compute_stack.py:

# For app certificate (line ~158)
self.certificate = acm.Certificate.from_certificate_arn(
    self,
    "Certificate",
    certificate_arn="arn:aws:acm:eu-central-1:700558745280:certificate/<UUID>",
)

# For admin certificate (line ~176)
self.admin_certificate = acm.Certificate.from_certificate_arn(
    self,
    "AdminCertificate",
    certificate_arn="arn:aws:acm:eu-central-1:700558745280:certificate/<UUID>",
)

Troubleshooting

Certificate stuck in "Pending validation"

Cause: DNS validation records not added or not propagated.

Fix:

  1. Verify both validation CNAMEs are added (prod AND management zones)
  2. Check record values match exactly (including trailing dots)
  3. Wait for DNS propagation:
    dig CNAME <VALIDATION_NAME> +short
    

"Record already exists" when adding validation CNAME

Cause: Previous certificate request created the same validation record.

Fix: Use UPSERT action instead of CREATE, or delete the existing record first.

Certificate validation succeeds for one domain but not the other

Cause: Only added validation record to one zone.

Fix: Each domain needs its validation record in the zone that manages it:

CDK deploy fails with "Certificate not found"

Cause: Certificate ARN is wrong or certificate was deleted.

Fix:

  1. Verify the certificate exists:
    AWS_PROFILE=orcha-prod aws acm describe-certificate \
        --region eu-central-1 \
        --certificate-arn <ARN>
    
  2. If deleted, create a new one and update the ARN in code.

Reference

Resource Value
App Certificate ARN arn:aws:acm:eu-central-1:700558745280:certificate/ee9e0e26-92d8-4e67-b35e-1be06ddee221
Admin Certificate ARN arn:aws:acm:eu-central-1:700558745280:certificate/3c6dd615-d14f-401c-b4ec-e800002415d8
Prod Hosted Zone prod.getorcha.com
Management Hosted Zone ID Z02414383CQNYTPGX2EIK
Script scripts/create-cross-account-cert.sh