Skip to content

Commit ef62107

Browse files
authored
fix(endpoint/source) Allow '.' in TXT Records (#5844)
* [endpoint] [source] Allow '.' in TXT Records Signed-off-by: hfuss <[email protected]> * pr feedback; lint fix Signed-off-by: hfuss <[email protected]> * using functional interfaces for future cleaners and validators of other record types Signed-off-by: hfuss <[email protected]> * Revert "using functional interfaces for future cleaners and validators of other record types" This reverts commit d9e1c2c. --------- Signed-off-by: hfuss <[email protected]>
1 parent 1300a6a commit ef62107

File tree

4 files changed

+81
-4
lines changed

4 files changed

+81
-4
lines changed

endpoint/endpoint.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,14 @@ func NewEndpoint(dnsName, recordType string, targets ...string) *Endpoint {
256256
func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string) *Endpoint {
257257
cleanTargets := make([]string, len(targets))
258258
for idx, target := range targets {
259-
cleanTargets[idx] = strings.TrimSuffix(target, ".")
259+
// Only trim trailing dots for domain name record types, not for TXT or NAPTR records
260+
// TXT records can contain arbitrary text including multiple dots
261+
switch recordType {
262+
case RecordTypeTXT, RecordTypeNAPTR:
263+
cleanTargets[idx] = target
264+
default:
265+
cleanTargets[idx] = strings.TrimSuffix(target, ".")
266+
}
260267
}
261268

262269
for label := range strings.SplitSeq(dnsName, ".") {

endpoint/endpoint_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"testing"
2323

2424
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
2526
"sigs.k8s.io/external-dns/pkg/events"
2627
)
2728

@@ -1036,3 +1037,29 @@ func TestEndpoint_WithMinTTL(t *testing.T) {
10361037
})
10371038
}
10381039
}
1040+
1041+
// TestNewEndpointWithTTLPreservesDotsInTXTRecords tests that trailing dots are preserved in TXT records
1042+
func TestNewEndpointWithTTLPreservesDotsInTXTRecords(t *testing.T) {
1043+
// TXT records should preserve trailing dots (and any arbitrary text)
1044+
txtEndpoint := NewEndpointWithTTL("example.com", RecordTypeTXT, TTL(300),
1045+
"v=1;some_signature=aBx3d5..",
1046+
"text.with.dots...",
1047+
"simple-text")
1048+
1049+
require.NotNil(t, txtEndpoint, "TXT endpoint should be created")
1050+
require.Len(t, txtEndpoint.Targets, 3, "should have 3 targets")
1051+
1052+
// All dots should be preserved in TXT targets
1053+
assert.Equal(t, "v=1;some_signature=aBx3d5..", txtEndpoint.Targets[0])
1054+
assert.Equal(t, "text.with.dots...", txtEndpoint.Targets[1])
1055+
assert.Equal(t, "simple-text", txtEndpoint.Targets[2])
1056+
1057+
// Domain name record types should still have trailing dots trimmed
1058+
aEndpoint := NewEndpointWithTTL("example.com", RecordTypeA, TTL(300), "1.2.3.4.")
1059+
require.NotNil(t, aEndpoint, "A endpoint should be created")
1060+
assert.Equal(t, "1.2.3.4", aEndpoint.Targets[0], "A record should have trailing dot trimmed")
1061+
1062+
cnameEndpoint := NewEndpointWithTTL("example.com", RecordTypeCNAME, TTL(300), "target.example.com.")
1063+
require.NotNil(t, cnameEndpoint, "CNAME endpoint should be created")
1064+
assert.Equal(t, "target.example.com", cnameEndpoint.Targets[0], "CNAME record should have trailing dot trimmed")
1065+
}

source/crd.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,13 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error
185185
if (ep.RecordType == endpoint.RecordTypeCNAME || ep.RecordType == endpoint.RecordTypeA || ep.RecordType == endpoint.RecordTypeAAAA) && len(ep.Targets) < 1 {
186186
log.Debugf("Endpoint %s with DNSName %s has an empty list of targets, allowing it to pass through for default-targets processing", dnsEndpoint.Name, ep.DNSName)
187187
}
188-
188+
isNAPTR := ep.RecordType == endpoint.RecordTypeNAPTR
189+
isTXT := ep.RecordType == endpoint.RecordTypeTXT
189190
illegalTarget := false
190191
for _, target := range ep.Targets {
191-
isNAPTR := ep.RecordType == endpoint.RecordTypeNAPTR
192192
hasDot := strings.HasSuffix(target, ".")
193-
if (isNAPTR && !hasDot) || (!isNAPTR && hasDot) {
193+
// Skip dot validation for TXT records as they can contain arbitrary text
194+
if !isTXT && ((isNAPTR && !hasDot) || (!isNAPTR && hasDot)) {
194195
illegalTarget = true
195196
break
196197
}

source/crd_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,48 @@ func testCRDSourceEndpoints(t *testing.T) {
475475
expectEndpoints: false,
476476
expectError: false,
477477
},
478+
{
479+
title: "valid target TXT",
480+
registeredAPIVersion: "test.k8s.io/v1alpha1",
481+
apiVersion: "test.k8s.io/v1alpha1",
482+
registeredKind: "DNSEndpoint",
483+
kind: "DNSEndpoint",
484+
namespace: "foo",
485+
registeredNamespace: "foo",
486+
labels: map[string]string{"test": "that"},
487+
labelFilter: "test=that",
488+
endpoints: []*endpoint.Endpoint{
489+
{
490+
DNSName: "example.org",
491+
Targets: endpoint.Targets{"foo.example.org."},
492+
RecordType: endpoint.RecordTypeTXT,
493+
RecordTTL: 180,
494+
},
495+
},
496+
expectEndpoints: true,
497+
expectError: false,
498+
},
499+
{
500+
title: "illegal target A",
501+
registeredAPIVersion: "test.k8s.io/v1alpha1",
502+
apiVersion: "test.k8s.io/v1alpha1",
503+
registeredKind: "DNSEndpoint",
504+
kind: "DNSEndpoint",
505+
namespace: "foo",
506+
registeredNamespace: "foo",
507+
labels: map[string]string{"test": "that"},
508+
labelFilter: "test=that",
509+
endpoints: []*endpoint.Endpoint{
510+
{
511+
DNSName: "example.org",
512+
Targets: endpoint.Targets{"1.2.3.4."},
513+
RecordType: endpoint.RecordTypeA,
514+
RecordTTL: 180,
515+
},
516+
},
517+
expectEndpoints: false,
518+
expectError: false,
519+
},
478520
} {
479521
t.Run(ti.title, func(t *testing.T) {
480522
t.Parallel()

0 commit comments

Comments
 (0)