Fix TXT registry handling of NewTXT domains

This commit is contained in:
Seweryn Chlewicki 2023-06-23 11:52:46 +01:00
parent 930061a990
commit 013f8cf9e9
No known key found for this signature in database
GPG Key ID: 5A6A91F5CE4D3841
2 changed files with 113 additions and 23 deletions

View File

@ -321,7 +321,9 @@ func newaffixNameMapper(prefix, suffix, wildcardReplacement string) affixNameMap
return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix), wildcardReplacement: strings.ToLower(wildcardReplacement)} return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix), wildcardReplacement: strings.ToLower(wildcardReplacement)}
} }
func extractRecordType(name string) (baseName, recordType string) { // extractRecordTypeDefaultPosition extracts record type from the default position
// when not using '%{record_type}' in the prefix/suffix
func extractRecordTypeDefaultPosition(name string) (baseName, recordType string) {
nameS := strings.Split(name, "-") nameS := strings.Split(name, "-")
for _, t := range getSupportedTypes() { for _, t := range getSupportedTypes() {
if nameS[0] == strings.ToLower(t) { if nameS[0] == strings.ToLower(t) {
@ -331,32 +333,34 @@ func extractRecordType(name string) (baseName, recordType string) {
return name, "" return name, ""
} }
// dropAffix strips TXT record to find an endpoint name it manages // dropAffixExtractType strips TXT record to find an endpoint name it manages
// It takes into consideration a fact that it could contain record type // it also returns the record type
// So it gets stripped first func (pr affixNameMapper) dropAffixExtractType(name string) (string, string) {
func (pr affixNameMapper) dropAffix(name string) string {
if pr.recordTypeInAffix() { if pr.recordTypeInAffix() {
for _, t := range getSupportedTypes() { for _, t := range getSupportedTypes() {
t = strings.ToLower(t) t = strings.ToLower(t)
iPrefix := strings.ReplaceAll(pr.prefix, recordTemplate, t) iPrefix := strings.ReplaceAll(pr.prefix, recordTemplate, t)
iSuffix := strings.ReplaceAll(pr.suffix, recordTemplate, t) iSuffix := strings.ReplaceAll(pr.suffix, recordTemplate, t)
if pr.isPrefix() && strings.HasPrefix(name, iPrefix) { if pr.isPrefix() && strings.HasPrefix(name, iPrefix) {
return strings.TrimPrefix(name, iPrefix) return strings.TrimPrefix(name, iPrefix), t
} }
if pr.isSuffix() && strings.HasSuffix(name, iSuffix) { if pr.isSuffix() && strings.HasSuffix(name, iSuffix) {
return strings.TrimSuffix(name, iSuffix) return strings.TrimSuffix(name, iSuffix), t
} }
} }
} }
if strings.HasPrefix(name, pr.prefix) && pr.isPrefix() { if strings.HasPrefix(name, pr.prefix) && pr.isPrefix() {
return strings.TrimPrefix(name, pr.prefix) return extractRecordTypeDefaultPosition(strings.TrimPrefix(name, pr.prefix))
} }
if strings.HasSuffix(name, pr.suffix) && pr.isSuffix() { if strings.HasSuffix(name, pr.suffix) && pr.isSuffix() {
return strings.TrimSuffix(name, pr.suffix) return extractRecordTypeDefaultPosition(strings.TrimSuffix(name, pr.suffix))
} }
return ""
return "", ""
} }
func (pr affixNameMapper) dropAffixTemplate(name string) string { func (pr affixNameMapper) dropAffixTemplate(name string) string {
@ -372,17 +376,22 @@ func (pr affixNameMapper) isSuffix() bool {
} }
func (pr affixNameMapper) toEndpointName(txtDNSName string) (endpointName string, isAAAA bool) { func (pr affixNameMapper) toEndpointName(txtDNSName string) (endpointName string, isAAAA bool) {
lowerDNSName, recordType := extractRecordType(strings.ToLower(txtDNSName)) lowerDNSName := strings.ToLower(txtDNSName)
// drop prefix // drop prefix
if strings.HasPrefix(lowerDNSName, pr.prefix) && pr.isPrefix() { if pr.isPrefix() {
return pr.dropAffix(lowerDNSName), recordType == endpoint.RecordTypeAAAA r, rType := pr.dropAffixExtractType(lowerDNSName)
return r, rType == endpoint.RecordTypeAAAA
} }
// drop suffix // drop suffix
if pr.isSuffix() { if pr.isSuffix() {
DNSName := strings.SplitN(lowerDNSName, ".", 2) dc := strings.Count(pr.suffix, ".")
return pr.dropAffix(DNSName[0]) + "." + DNSName[1], recordType == endpoint.RecordTypeAAAA DNSName := strings.SplitN(lowerDNSName, ".", 2+dc)
domainWithSuffix := strings.Join(DNSName[:1+dc], ".")
r, rType := pr.dropAffixExtractType(domainWithSuffix)
return r + "." + DNSName[1+dc], rType == endpoint.RecordTypeAAAA
} }
return "", false return "", false
} }

View File

@ -114,7 +114,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
newEndpointWithOwner("dualstack.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""), newEndpointWithOwner("dualstack.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
newEndpointWithOwner("txt.dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""), newEndpointWithOwner("txt.dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""), newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
newEndpointWithOwner("aaaa-txt.dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""), newEndpointWithOwner("txt.aaaa-dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
}, },
}) })
expectedRecords := []*endpoint.Endpoint{ expectedRecords := []*endpoint.Endpoint{
@ -216,13 +216,13 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, false, nil) r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, false, nil)
records, _ := r.Records(ctx) records, _ := r.Records(ctx)
assert.True(t, testutils.SameEndpoints(records, expectedRecords)) assert.ElementsMatch(t, expectedRecords, records)
// Ensure prefix is case-insensitive // Ensure prefix is case-insensitive
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "", []string{}, false, nil) r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "", []string{}, false, nil)
records, _ = r.Records(ctx) records, _ = r.Records(ctx)
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords)) assert.ElementsMatch(t, expectedRecords, records)
} }
func testTXTRegistryRecordsSuffixed(t *testing.T) { func testTXTRegistryRecordsSuffixed(t *testing.T) {
@ -1116,8 +1116,8 @@ func TestDropPrefix(t *testing.T) {
aRecord := "foo-a-test.example.com" aRecord := "foo-a-test.example.com"
expectedCnameRecord := "test.example.com" expectedCnameRecord := "test.example.com"
expectedARecord := "test.example.com" expectedARecord := "test.example.com"
actualCnameRecord := mapper.dropAffix(cnameRecord) actualCnameRecord, _ := mapper.dropAffixExtractType(cnameRecord)
actualARecord := mapper.dropAffix(aRecord) actualARecord, _ := mapper.dropAffixExtractType(aRecord)
assert.Equal(t, expectedCnameRecord, actualCnameRecord) assert.Equal(t, expectedCnameRecord, actualCnameRecord)
assert.Equal(t, expectedARecord, actualARecord) assert.Equal(t, expectedARecord, actualARecord)
} }
@ -1127,11 +1127,12 @@ func TestDropSuffix(t *testing.T) {
aRecord := "test-a-foo.example.com" aRecord := "test-a-foo.example.com"
expectedARecord := "test.example.com" expectedARecord := "test.example.com"
r := strings.SplitN(aRecord, ".", 2) r := strings.SplitN(aRecord, ".", 2)
actualARecord := mapper.dropAffix(r[0]) + "." + r[1] rClean, _ := mapper.dropAffixExtractType(r[0])
actualARecord := rClean + "." + r[1]
assert.Equal(t, expectedARecord, actualARecord) assert.Equal(t, expectedARecord, actualARecord)
} }
func TestExtractRecordType(t *testing.T) { func TestExtractRecordTypeDefaultPosition(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expectedName string expectedName string
@ -1160,13 +1161,93 @@ func TestExtractRecordType(t *testing.T) {
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) { t.Run(tc.input, func(t *testing.T) {
actualName, actualType := extractRecordType(tc.input) actualName, actualType := extractRecordTypeDefaultPosition(tc.input)
assert.Equal(t, tc.expectedName, actualName) assert.Equal(t, tc.expectedName, actualName)
assert.Equal(t, tc.expectedType, actualType) assert.Equal(t, tc.expectedType, actualType)
}) })
} }
} }
func TestToEndpointNameNewTXT(t *testing.T) {
type testCase struct {
name string
mapper affixNameMapper
domain string
recordType string
}
testCases := []testCase{
{
name: "prefix",
mapper: newaffixNameMapper("foo", "", ""),
domain: "example.com",
recordType: "A",
},
{
name: "suffix",
mapper: newaffixNameMapper("", "foo", ""),
domain: "example.com",
recordType: "AAAA",
},
{
name: "prefix with dash",
mapper: newaffixNameMapper("foo-", "", ""),
domain: "example.com",
recordType: "A",
},
{
name: "suffix with dash",
mapper: newaffixNameMapper("", "-foo", ""),
domain: "example.com",
recordType: "CNAME",
},
{
name: "prefix with dot",
mapper: newaffixNameMapper("foo.", "", ""),
domain: "example.com",
recordType: "CNAME",
},
{
name: "suffix with dot",
mapper: newaffixNameMapper("", ".foo", ""),
domain: "example.com",
recordType: "CNAME",
},
{
name: "templated prefix",
mapper: newaffixNameMapper("%{record_type}-foo", "", ""),
domain: "example.com",
recordType: "A",
},
{
name: "templated suffix",
mapper: newaffixNameMapper("", "foo-%{record_type}", ""),
domain: "example.com",
recordType: "A",
},
{
name: "templated prefix with dot",
mapper: newaffixNameMapper("%{record_type}foo.", "", ""),
domain: "example.com",
recordType: "CNAME",
},
{
name: "templated suffix with dot",
mapper: newaffixNameMapper("", ".foo%{record_type}", ""),
domain: "example.com",
recordType: "A",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
txtName := tc.mapper.toNewTXTName(tc.domain, tc.recordType)
res, _ := tc.mapper.toEndpointName(txtName)
assert.Equal(t, tc.domain, res)
})
}
}
func TestNewTXTScheme(t *testing.T) { func TestNewTXTScheme(t *testing.T) {
p := inmemory.NewInMemoryProvider() p := inmemory.NewInMemoryProvider()
p.CreateZone(testZone) p.CreateZone(testZone)