Skip to content

Zone matching fails for domains in the public suffix list #761

@maksymvavilov

Description

@maksymvavilov

Summary

FindDNSZoneForHost() in internal/provider/provider.go rejects any host whose domain is listed in the public suffix list, even when a matching zone is explicitly configured in the provider secret.

Root Cause

Lines 150–155 of internal/provider/provider.go:

tld, _ := publicsuffix.PublicSuffix(host)
if host == tld {
    return nil, "", fmt.Errorf("%w: %s", ErrNoZoneForHost, originalHost)
}

golang.org/x/net/publicsuffix.PublicSuffix() returns the public suffix of a domain, not just the ICANN TLD. Many real-world domains appear in the public suffix list as privately managed domains (e.g., httpbin.org, github.io, amazonaws.com, azurewebsites.net). For these domains, PublicSuffix(host) returns the host itself, causing the early-exit check host == tld to fire before zone matching is attempted.

Demonstration:

ps, icann := publicsuffix.PublicSuffix("httpbin.org")
// ps = "httpbin.org", icann = false, host == ps → true → rejected

ps, icann = publicsuffix.PublicSuffix("example.com")
// ps = "com", icann = true, host == ps → false → zone matching proceeds

Impact

When using the CoreDNS provider with ZONES=httpbin.org and a DNSRecord with rootHost: httpbin.org, the authoritative record fails reconciliation:

error: "no zone for host: httpbin.org"

The individual DNSRecord succeeds (via the endpoint/delegation provider path), but the authoritative record — which goes through the CoreDNS provider's DNSZoneForHost() directly — hits the public suffix check and fails. This prevents the kuadrant.io/coredns-zone-name label from being added, so the CoreDNS plugin never discovers the record.

This affects any domain in the public suffix list used as both the rootHost and the zone. Subdomains work fine (e.g., simple.k.example.com with zone k.example.com).

Reproduction

# Create provider secret with the domain as the zone
kubectl create secret generic dns-provider-credentials-coredns \
  --namespace=dnstest \
  --type=kuadrant.io/coredns \
  --from-literal=ZONES="httpbin.org"

# Create DNSRecord
kubectl apply -f - <<EOF
apiVersion: kuadrant.io/v1alpha1
kind: DNSRecord
metadata:
  name: httpbin-test
  namespace: dnstest
spec:
  rootHost: httpbin.org
  providerRef:
    name: dns-provider-credentials-coredns
  endpoints:
  - dnsName: httpbin.org
    recordType: A
    recordTTL: 60
    targets:
    - "10.89.0.17"
EOF

# Wait and check — authoritative record will show DNSProviderError
kubectl get dnsrecord -n dnstest

Suggested Fix

The public suffix check was added to prevent recursion past a useful boundary, but it's too aggressive for privately managed public suffixes. Consider:

  1. Only checking icann == true public suffixes (skip privately managed ones)
  2. Checking the zone list before applying the TLD rejection — if a zone explicitly matches the host, allow it
  3. Removing the early exit entirely and relying on the recursive subdomain stripping to terminate naturally

Option 2 seems safest — the user has explicitly configured the zone, so it should be respected.

Context

Discovered while investigating DNS-based egress gateway routing for Kuadrant/kuadrant-operator#1847. The use case is mapping external hostnames to an egress gateway's ClusterIP via kuadrant CoreDNS + DNSRecord, where the rootHost IS the external hostname (e.g., httpbin.org).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions