Skip to content

CRD source validation rejects FQDN with trailing dots, breaking automation workflows #5954

@maxstepanov

Description

@maxstepanov

What happened:

When using DNSEndpoint CRDs with CNAME targets that have trailing dots (standard FQDN format), external-dns logs a warning and skips creating the DNS record:

level=warning msg="Endpoint config-control/my-dns-record with DNSName _acme-challenge_xxx.example.com. has an illegal target format."

The validation in source/crd.go lines 189-201 rejects non-NAPTR records with trailing dots:

illegalTarget := false
for _, target := range ep.Targets {
    isNAPTR := ep.RecordType == endpoint.RecordTypeNAPTR
    hasDot := strings.HasSuffix(target, ".")
    if (isNAPTR && !hasDot) || (!isNAPTR && hasDot) {
        illegalTarget = true
        break
    }
}

What you expected to happen:

external-dns should accept CNAME records with trailing dots and create the DNS record. This is how other sources (Service, Ingress, etc.) behave - they automatically normalize trailing dots via NewEndpointWithTTL() in endpoint/endpoint.go:

func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string) *Endpoint {
    cleanTargets := make([]string, len(targets))
    for idx, target := range targets {
        cleanTargets[idx] = strings.TrimSuffix(target, ".")  // Normalizes
    }
    return &Endpoint{
        DNSName:    strings.TrimSuffix(dnsName, "."),  // Normalizes
        Targets:    cleanTargets,
        // ...
    }
}

CRD source is inconsistent - it uses endpoints directly from the spec (source/crd.go line 184) without normalization, then validates the raw data which still has trailing dots.

How to reproduce it (as minimally and precisely as possible):

  1. Deploy external-dns with CRD source:
args:
- --source=crd
- --crd-source-apiversion=externaldns.k8s.io/v1alpha1
- --crd-source-kind=DNSEndpoint
- --provider=cloudflare
  1. Create a DNSEndpoint with FQDN target (trailing dot):
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: my-dns-record
  namespace: default
spec:
  endpoints:
  - dnsName: "subdomain.example.com."
    recordTTL: 300
    recordType: "CNAME"
    targets:
    - "target.example.com."
  1. Check external-dns logs - you'll see the "illegal target format" warning and no DNS record created.

  2. Workaround: Manually strip trailing dots from both dnsName and targets - then it works.

Anything else we need to know?:

Impact: This breaks automation workflows where external systems return FQDN data with trailing dots. For example:

  • Google Certificate Manager DNS-01 ACME challenges return targets like <token>.4.us-central1.authorize.certificatemanager.goog.
  • kpt apply-time-mutation copy this data to DNSEndpoint CRDs
  • external-dns rejects it, breaking the automation

No configuration workaround exists:

  • ❌ No command-line flags to skip/disable validation
  • ❌ No annotations to control validation behavior
  • ❌ No spec fields to bypass validation
  • ❌ Validation is hardcoded in source/crd.go lines 189-201

History: The validation was added in July 2019 (commit 6bf1c2bc, v0.10.0) to prevent external-dns from "constantly adding and removing records." The assumption was that trailing dots were user errors, not valid FQDN formatting from external systems.

Related: NAPTR fix #3979 shows the maintainers already recognized this validation was too strict for NAPTR records. The same issue applies to CNAME records when FQDN data comes from external systems.

Environment:

  • External-DNS version: v0.19.0 (issue present since v0.10.0)
  • DNS provider: cloudflare
  • Others: Kubernetes 1.29 (GKE), CRD source (DNSEndpoint v1alpha1)

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions