John Edward Willman V
· 1 min read · #aws #cdk #cloudformation

When CloudFormation lies

ACM certs that say they're done before they actually are, and a CDK construct to make CFN tell the truth.

Spent half a day this week debugging a CDK deploy that kept failing the same way:

SiteDistribution  UPDATE_FAILED
  "specified SSL certificate doesn't exist, isn't in us-east-1 region,
   isn't valid, or doesn't include a valid certificate chain."

The cert was real. It was ISSUED. It was in us-east-1. The chain was fine. CloudFormation said CREATE_COMPLETE. CloudFront still refused to attach it.

The clue was in the events: the AWS::CertificateManager::Certificate resource transitioned from CREATE_IN_PROGRESS to CREATE_COMPLETE in 32 seconds, with an info-level message that read Content of DNS Record is: null. ACM hadn’t populated the validation record yet, CFN’s polling gave up, and the resource was marked done while still in PENDING_VALIDATION. CloudFront — next in the dependency chain — looked up the cert, saw it wasn’t actually issued, and rejected it with a misleading “doesn’t exist” error.

This is aws-cdk#8401, unfixed for years. The CDK workaround is DnsValidatedCertificate, which uses a Lambda-backed custom resource to poll until the cert is genuinely ISSUED — but it’s deprecated and locked to RSA-2048.

The cleaner answer is a small custom construct that wraps the modern acm.Certificate (keeping keyAlgorithm support) with a cr.Provider whose isComplete handler hits acm:DescribeCertificate in a loop:

const cert = new acm.Certificate(this, "Certificate", {
  domainName,
  validation: acm.CertificateValidation.fromDns(zone),
  keyAlgorithm: acm.KeyAlgorithm.EC_PRIME256V1,
});

const waiter = new CertIssuedWaiter(this, "CertWaiter", {
  certificateArn: cert.certificateArn,
});

distribution.node.addDependency(waiter);

CloudFront now blocks on the cert being actually issued, not on CFN’s say-so. The site you’re reading uses exactly this construct.

The general lesson: when a CloudFormation resource lies about its state, the fix is almost always a custom resource that asks the underlying service directly. Don’t trust the wrapper — ask the source.

Related

Comments