aws-cdk: Unable to delete hosted zone with DNS verified certificate

When I try to delete a stack that has a hosted zone with a certificate created through certificate manager, it has a CNAME entry created by certificate manager that prevents the hosted zone being deleted. Error report attached below.

My case is as follows though it’s possible you don’t need separate accounts, I haven’t checked. I have two accounts, one that has the DNS hosted zone and name servers for my existing domain name (say Account A) and a separate one that I’m creating a DNS hosted zone underneath (say Account B). For example, account A has the Route53 records for my domain example.com and I want to be able to create a subdomain test.example.com in account B that has a valid certificate.

I’ve got 3 CDK stacks do this, one to create the hosted zone in Account B, one to add the nameservers from that account to Account A and finally one to create the certificate in Account B (see the code samples below). This all works really well for creation but for deletion the certificate manager stack has left a CNAME entry similar to the following in the hosted zone preventing deletion:

_72fb0....bc94c.test.example.com. | CNAME | _c1c3d...ebd76.vhzmpjdqfx.acm-validations.aws. |  
-- | -- | -- | --

I would have expected the certificate manager that created this to remove it as well.

Reproduction Steps

Release the following stacks in order and delete them in reverse order, you’ll need to pass in appropriate environment variables.

Stack 1:

import os

from aws_cdk import aws_route53 as route53, core


class DnsHostedZoneStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        domain_url = os.environ["domain_url"]

        route53.PublicHostedZone(self, "DomainEnvironment", zone_name=domain_url)

Stack 2:

import json
import os

from aws_cdk import aws_route53 as route53, core


class DnsLinkHostedZoneStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        root_domain_url = os.environ["root_domain_url"]
        environment_subdomain = os.environ["environment_subdomain"]
        hosted_zone_id = os.environ["hosted_zone_id"]
        nameservers = json.loads(os.environ["nameservers"])

        full_domain_url = environment_subdomain + "." + root_domain_url

        main_hosted_zone = route53.HostedZone.from_hosted_zone_attributes(
            self,
            "MainHostLookup",
            zone_name=root_domain_url,
            hosted_zone_id=hosted_zone_id,
        )

        route53.RecordSet(
            self,
            "NameServerLink",
            record_name=full_domain_url,
            record_type=route53.RecordType.NS,
            zone=main_hosted_zone,
            target=route53.RecordTarget.from_values(*nameservers),
        )

Stack 3:

import os

from aws_cdk import (
    aws_route53 as route53,
    aws_certificatemanager as certificatemanager,
    core,
)


class CertificateStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        hosted_domain_url = os.environ["hosted_domain_url"]
        hosted_zone_id = os.environ["hosted_zone_id"]
        hosted_zone = route53.HostedZone.from_hosted_zone_attributes(
            self,
            "MainHostLookup",
            zone_name=hosted_domain_url,
            hosted_zone_id=hosted_zone_id,
        )

        self.certificate = certificatemanager.DnsValidatedCertificate(
            self,
            "EnvironmentCertificate",
            hosted_zone=hosted_zone,
            domain_name=hosted_domain_url,
            region="us-east-1",
            subject_alternative_names=[f"*.{hosted_domain_url}"],
            validation_method=certificatemanager.ValidationMethod.DNS,
        )

Error Log

   0 | 5:07:47 PM | DELETE_IN_PROGRESS   | AWS::CDK::Metadata       | CDKMetadata
   1 | 5:07:48 PM | DELETE_FAILED        | AWS::Route53::HostedZone | DomainEnvironment (DomainEnvironment7F28B06E) The specified hosted zone contains non-required resource record sets  and so cannot be deleted. (Service: AmazonRoute53; Status Code: 400; Error Code: HostedZoneNotEmpty; Request ID: 33c379cc-bede-48e8-8083-205a44fed498)
        new HostedZone (/private/tmp/jsii-kernel-HYp2Da/node_modules/@aws-cdk/aws-route53/lib/hosted-zone.js:16:26)
        \_ new PublicHostedZone (/private/tmp/jsii-kernel-HYp2Da/node_modules/@aws-cdk/aws-route53/lib/hosted-zone.js:116:9)
        \_ /Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7838:49
        \_ Kernel._wrapSandboxCode (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:8298:20)
        \_ Kernel._create (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7838:26)
        \_ Kernel.create (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7585:21)
        \_ KernelHost.processRequest (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7372:28)
        \_ KernelHost.run (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7312:14)
        \_ Immediate._onImmediate (/Users/<snip>/lib/python3.6/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7315:37)
        \_ processImmediate (internal/timers.js:439:21)
   2 | 5:07:48 PM | DELETE_COMPLETE      | AWS::CDK::Metadata       | CDKMetadata
   3 | 5:07:48 PM | DELETE_FAILED        | AWS::CloudFormation::Stack | dns-hosted-zone The following resource(s) failed to delete: [DomainEnvironment7F28B06E].

 ❌  dns-hosted-zone: destroy failed Error: The stack named dns-hosted-zone is in a failed state: DELETE_FAILED (The following resource(s) failed to delete: [DomainEnvironment7F28B06E]. )
    at /Users/<snip>/node_modules/aws-cdk/lib/api/util/cloudformation.ts:165:13
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at waitFor (/Users/<snip>/node_modules/aws-cdk/lib/api/util/cloudformation.ts:76:20)
    at Object.destroyStack (/Users/<snip>/node_modules/aws-cdk/lib/api/deploy-stack.ts:261:26)
    at CdkToolkit.destroy (/Users/<snip>/node_modules/aws-cdk/lib/cdk-toolkit.ts:211:9)
    at main (/Users/<snip>/node_modules/aws-cdk/bin/cdk.ts:245:16)
    at initCommandLine (/Users/<snip>/node_modules/aws-cdk/bin/cdk.ts:172:9)
The stack named dns-hosted-zone is in a failed state: DELETE_FAILED (The following resource(s) failed to delete: [DomainEnvironment7F28B06E]. )

Environment

  • CLI Version : 1.31
  • Framework Version: 1.31 (guessing this is the Python library versions)
  • OS : Mac
  • Language : Python

Other


This is 🐛 Bug Report

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 43
  • Comments: 33 (10 by maintainers)

Commits related to this issue

Most upvoted comments

This is extremely frustrating in CI, because the cleanup cannot happen automatically.

There should be a similar cleanupRoute53Records property in certificatemanager.Certificate if the other construct is deprecated. It’s frustrating that this issue hasn’t been fixed in over 3 years.

Having the same issue with Certificate which is frustrating. Any fixes please?

This issue is closed as fixed by #18311. Can you please check if the referred fix covers your problem?

It appears that the current solution only works for the deprecated DnsValidatedCertificate. It would be great to include this feature for Certificate as well.

Seems to me that the bug is in the ACM teardown. ACM setup that CNAME entry in the first place, when it’s deleted it should do the cleanup too. I’d still expect the DNS hosted zone to fail deletion if I’ve manually added a record that wasn’t cleaned up say. In the same way that with an S3 bucket I’d expect it to not clearup if there’s still data in it.

Thanks @runtooctober! I was using aws-certificatemanager.Certificate with the validation prop set to CertificateValidation.fromDns(publicHostedZone). Switching to aws-certificatemanager.DnsValidatedCertificate like you mentioned gave me access to this feature.

@moltar @chasemaier - marking this as a p1 so that it gets prioritized. I’ll need to work through the repro steps to start putting together a fix. will post an update when I have more details to share. stay tuned!

Ironically aws-certificatemanager.DnsValidatedCertificate is being deprecated and the recommended alternative is aws-certificatemanager.Certificate which exhibits the same issue for me when using DNS validation.

I see this fix was added in v1.141.0, but I’m still experiencing this issue on v1.152.0. Any suggestions?

@skkrail-amzn I’ve run into this myself and come across this thread. On your certificate aws-certificatemanager.DnsValidatedCertificate object, you would need to specifically set the cleanupRoute53Records boolean to true as it’s an opt-in feature added in https://github.com/aws/aws-cdk/pull/18311.

E.g.

const certificate = new acm.DnsValidatedCertificate(this, "SiteCertificate", {
      domainName: props.domainName,
      hostedZone: props.zone,
      region: "us-east-1", // Cloudfront only checks this region for certificates.
      cleanupRoute53Records: true
    });

Here you go: https://github.com/moltar/cdk-hosted-zone

I also added repro steps in the README

This is quite frustrating. Would highly appreciate a fix.

Experiencing this as well

Facing the same issue using CloudFormation… Definitely needs a fix

+1 … This issue is still occurring, please fix.

I just want to highlight @Dzhuneyt: While the verification record should definitely be auto-deleted, we really need an option to delete all records when tearing down the hosted zone. This issue comes up often.

I was thinking of maybe attaching a Lambda “Custom Resource” that does a “cleanup” of DNS records within the hosted zone as part of the HostedZone deletion. It seems kind of risky though? What’s the opinion of CDK maintainers on this topic?

This approach is very similar to the s3.Bucket being often replaced by AutoDeleteBucket. Maybe we (the community) should develop a third party AutoDeleteHostedZone construct?

Still present in 1.61.0. It would be nice if the hosted zone was created as part of the stack if it could be flagged as “okay to delete all records sets upon zone deletion”. Perhaps that’s a Cloudformation limitation though?

It wouldn’t be so bad, but the stack rollback eliminates anything but the hosted zone and then fails to complete the rollback. You can’t then simply redeploy the stack since that creates a new zone (with the same problem), and you have to swap around NS records with each attempt to deploy a zone followed by some manual cleanup of the DNS validation records for the certificates.