aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.68k stars 3.93k forks source link

(cdk-route53): Unable to create VPC Endpoint Service private DNS name in existing PublicHostedZone #18698

Open xtralife opened 2 years ago

xtralife commented 2 years ago

What is the problem?

I'm trying to add a private DNS name to VPC Endpoint Service using VpcEndpointServiceDomainName construct. For that, I need to pass IPublicHostedZone as one of the parameters to the builder. There is a public hosted zone already created in the AWS account I'm deploying to, so I just need to use an existing one, not create a new one. I'm using PublicHostedZone.fromPublicHostedZoneId() method to get the public hosted zone and then pass it as an argument to VpcEndpointServiceDomainName builder. However, when I try to run CDK deployment, I get the following error: Error: cannot retrieve "zoneName" from an an imported hosted zone

I tried to replace PublicHostedZone.fromPublicHostedZoneId() method with creating a new instance of PublicHostedZone, as described here. In this case deployment succeeds, but it creates a duplicate hosted zone with the same DNS name along with already existing one.

Also, I can't use other lookup methods, like PublicHostedZone.fromLookup() or PublicHostedZone.fromHostedZoneAttributes() because they return an instance of IHostedZone, while VpcEndpointServiceDomainName builder requires an instance of IPublicHostedZone.

Reproduction Steps

        var vpcEndpointService = VpcEndpointService.Builder
                .create(this, "vpce-srv")
                .vpcEndpointServiceLoadBalancers(List.of(nlb))
                .acceptanceRequired(false)
                .allowedPrincipals(new ArnPrincipal(String.format("arn:aws:iam::%s:root", accountId)))
                .build();

        var phz = PublicHostedZone.fromPublicHostedZoneId(this, "phz", "SOME_HZ_ID");
       // or, alternatively, which creates duplicate hosted zone
       // var phz = new PublicHostedZone(this, "phz", PublicHostedZoneProps.builder().zoneName(domainName).build());

        var vpcEndpointServiceDomain = VpcEndpointServiceDomainName.Builder
                .create(this, "vpce-srv-dns")
                .domainName("some-stuff.my-public-domain.com")
                .endpointService(vpcEndpointService)
                .publicHostedZone(phz)
                .build();

What did you expect to happen?

What actually happened?

When using var phz = PublicHostedZone.fromPublicHostedZoneId(this, "phz", "SOME_HZ_ID"); statement:

Error: cannot retrieve "zoneName" from an an imported hosted zone
    at Import.get zoneName [as zoneName] (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/hosted-zone.js:202:36)
    at Object.determineFullyQualifiedDomainName (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/util.js:52:39)
    at new RecordSet (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/record-set.js:97:26)
    at new TxtRecord (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/record-set.js:197:9)
    at VpcEndpointServiceDomainName.verifyPrivateDnsConfiguration (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.js:134:36)
    at new VpcEndpointServiceDomainName (/private/var/folders/nf/r64fmvdj09gfsfgdbmkt2mbm0000gp/T/jsii-kernel-F9jYTv/node_modules/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.js:43:14)

OR When using var phz = new PublicHostedZone(this, "phz", PublicHostedZoneProps.builder().zoneName(domainName).build()); statement:

CDK CLI Version

1.136.0

Framework Version

No response

Node.js Version

v16.10.0

OS

MacOS Big Sur 11.6

Language

Java

Language Version

openjdk 17.0.1

Other information

Similar issues: https://github.com/aws/aws-cdk/issues/3558 https://github.com/aws/aws-cdk/issues/8406 https://github.com/aws/aws-cdk/issues/3663

The only workaround solution I found so far is to create a new IPublicHostedZone implementation with IHostedZone member instance, and delegate all interface calls to it.

        public class PublicHostedZoneDelegate extends Construct implements IPublicHostedZone {
            private final IHostedZone zone;
            public PublicHostedZoneDelegate(@NotNull Construct scope, @NotNull String id, @NotNull IHostedZone zone) {
                super(scope, id);
                this.zone = zone;
            }
            @Override
            public @NotNull String getHostedZoneArn() {
                return zone.getHostedZoneArn();
            }
            // ... Other interface methods using the same delegation approach
        }

Usage:

        IHostedZone zone = PublicHostedZone.fromLookup(scope, "domain", HostedZoneProviderProps
                .builder()
                .domainName(domainName)
                .build());

        var phz = new PublicHostedZoneDelegate(scope, "phz", zone);
        var vpcEndpointServiceDomain = VpcEndpointServiceDomainName.Builder
                .create(scope, "vpce-srv-dns")
                .domainName("some-stuff.my-public-domain.com")
                .endpointService(vpcEndpointService)
                .publicHostedZone(phz)
                .build();
njlynch commented 2 years ago

Thanks for this bug report, and for linking the related items; there's definitely a history here.

Generally, we could have either always required the zone name and ID, or updated our validation logic to detect when a zone doesn't have that information, and gracefully handling it. However, both changes are non-trivial to work through in a backwards-compatible way. We could try to rewrite the determineFullyQualifiedDomainName (or its callers) to catch this error and ignore if possible, but it's certainly not ideal.

https://github.com/aws/aws-cdk/blob/468051672435a7ed56c72d407174ea136455ec45/packages/@aws-cdk/aws-route53/lib/util.ts#L52

As a work-around, you could create your own subclass that extends HostedZone and implements IPublicHostedZone, but that's certainly not ideal. That, or just cast the IHostedZone returned from HostedZone.fromHostedZoneAttributes into a IPublicHostedZone; the interfaces are the same. However, I'm not sure if jsii might complain about that.

The real (short-term) solution here is likely just to define a PublicHostedZone.fromHostedZoneAttributes; that would enable the correct behavior here.

ustulation commented 2 years ago

What happens if the domain is registered elsewhere? Eg. if CloudFlare is where the domain resolution is happening.

I should not need to create any PublicHostedZone in AWS in that case and domain ownership verification will simply happen when I put the TXT record that I'm asked to put as seen in the console.

For such cases should not this parameter of IPublicHostedZone be entirely optional? Or how do I cater for this in the current setup?

What I'm doing now is creating a new PublicHostedZone just to satisfy the API, but that is just a useless entry in Route53 as nothing will ever resolve to it - since the actual domain is hosted in CloudFlare. What is the solution here?

jvzoggel-pv commented 2 weeks ago

Bump, as @ustulation mentions this is exact our situation. I just need to define the FQDN entry on the VPC Endpoint and share the generated TXT record with the external DNS management team. In CDK there is now no alternative then to do it manual (yike) or scaffold a complete useless hosted zone