Skip to content

Commit

Permalink
Merge pull request #4283 from angeloxx/feature/rfc-2136-ptr
Browse files Browse the repository at this point in the history
feat(rfc2136): add PTR optional support
  • Loading branch information
k8s-ci-robot authored Jun 20, 2024
2 parents 939e7b8 + 1d93309 commit 8245b89
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 7 deletions.
10 changes: 10 additions & 0 deletions docs/tutorials/rfc2136.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ A default TTL for all records can be set using the the flag with a time in secon
There are other annotation that can affect the generation of DNS records, but these are beyond the scope of this
tutorial and are covered in the main documentation.

### Generate reverse DNS records

If you want to generate reverse DNS records for your services, you have to enable the functionality using the `--rfc2136-create-ptr`
flag. You have also to add the zone to the list of zones managed by ExternalDNS via the `--rfc2136-zone` and `--domain-filter` flags.
An example of a valid configuration is the following:

```--domain-filter=157.168.192.in-addr.arpa --rfc2136-zone=157.168.192.in-addr.arpa```

PTR record tracking is managed by the A/AAAA record so you can't create PTR records for already generated A/AAAA records.

### Test with external-dns installed on local machine (optional)
You may install external-dns and test on a local machine by running:

Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ func main() {
ClientCertKeyFilePath: cfg.TLSClientCertKey,
ServerName: "",
}
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, nil)
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136CreatePTR, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, nil)
case "ns1":
p, err = ns1.NewNS1Provider(
ns1.NS1Config{
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ type Config struct {
RFC2136Zone []string
RFC2136Insecure bool
RFC2136GSSTSIG bool
RFC2136CreatePTR bool
RFC2136KerberosRealm string
RFC2136KerberosUsername string
RFC2136KerberosPassword string `secure:"yes"`
Expand Down Expand Up @@ -551,6 +552,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("rfc2136-host", "When using the RFC2136 provider, specify the host of the DNS server").Default(defaultConfig.RFC2136Host).StringVar(&cfg.RFC2136Host)
app.Flag("rfc2136-port", "When using the RFC2136 provider, specify the port of the DNS server").Default(strconv.Itoa(defaultConfig.RFC2136Port)).IntVar(&cfg.RFC2136Port)
app.Flag("rfc2136-zone", "When using the RFC2136 provider, specify zone entries of the DNS server to use").StringsVar(&cfg.RFC2136Zone)
app.Flag("rfc2136-create-ptr", "When using the RFC2136 provider, enable PTR management").Default(strconv.FormatBool(defaultConfig.RFC2136CreatePTR)).BoolVar(&cfg.RFC2136CreatePTR)
app.Flag("rfc2136-insecure", "When using the RFC2136 provider, specify whether to attach TSIG or not (default: false, requires --rfc2136-tsig-keyname and rfc2136-tsig-secret)").Default(strconv.FormatBool(defaultConfig.RFC2136Insecure)).BoolVar(&cfg.RFC2136Insecure)
app.Flag("rfc2136-tsig-keyname", "When using the RFC2136 provider, specify the TSIG key to attached to DNS messages (required when --rfc2136-insecure=false)").Default(defaultConfig.RFC2136TSIGKeyName).StringVar(&cfg.RFC2136TSIGKeyName)
app.Flag("rfc2136-tsig-secret", "When using the RFC2136 provider, specify the TSIG (base64) value to attached to DNS messages (required when --rfc2136-insecure=false)").Default(defaultConfig.RFC2136TSIGSecret).StringVar(&cfg.RFC2136TSIGSecret)
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/externaldns/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func TestValidateBadRfc2136Config(t *testing.T) {
cfg.Sources = []string{"test-source"}
cfg.Provider = "rfc2136"
cfg.RFC2136MinTTL = -1
cfg.RFC2136CreatePTR = false
cfg.RFC2136BatchChangeSize = 50

err := ValidateConfig(cfg)
Expand Down
47 changes: 46 additions & 1 deletion provider/rfc2136/rfc2136.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type rfc2136Provider struct {
minTTL time.Duration
batchChangeSize int
tlsConfig TLSConfig
createPTR bool

// options specific to rfc3645 gss-tsig support
gssTsig bool
Expand Down Expand Up @@ -95,7 +96,7 @@ type rfc2136Actions interface {
}

// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers
func NewRfc2136Provider(host string, port int, zoneNames []string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, gssTsig bool, krb5Username string, krb5Password string, krb5Realm string, batchChangeSize int, tlsConfig TLSConfig, actions rfc2136Actions) (provider.Provider, error) {
func NewRfc2136Provider(host string, port int, zoneNames []string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, createPTR bool, gssTsig bool, krb5Username string, krb5Password string, krb5Realm string, batchChangeSize int, tlsConfig TLSConfig, actions rfc2136Actions) (provider.Provider, error) {
secretAlgChecked, ok := tsigAlgs[secretAlg]
if !ok && !insecure && !gssTsig {
return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
Expand All @@ -120,6 +121,7 @@ func NewRfc2136Provider(host string, port int, zoneNames []string, insecure bool
zoneNames: zoneNames,
insecure: insecure,
gssTsig: gssTsig,
createPTR: createPTR,
krb5Username: krb5Username,
krb5Password: krb5Password,
krb5Realm: strings.ToUpper(krb5Realm),
Expand Down Expand Up @@ -195,6 +197,9 @@ OuterLoop:
case dns.TypeNS:
rrValues = []string{rr.(*dns.NS).Ns}
rrType = "NS"
case dns.TypePTR:
rrValues = []string{rr.(*dns.PTR).Ptr}
rrType = "PTR"
default:
continue // Unhandled record type
}
Expand Down Expand Up @@ -274,6 +279,35 @@ func (r rfc2136Provider) List() ([]dns.RR, error) {
return records, nil
}

func (r rfc2136Provider) AddReverseRecord(ip string, hostname string) error {
changes := r.GenerateReverseRecord(ip, hostname)
return r.ApplyChanges(context.Background(), &plan.Changes{Create: changes})
}

func (r rfc2136Provider) RemoveReverseRecord(ip string, hostname string) error {
changes := r.GenerateReverseRecord(ip, hostname)
return r.ApplyChanges(context.Background(), &plan.Changes{Delete: changes})
}

func (r rfc2136Provider) GenerateReverseRecord(ip string, hostname string) []*endpoint.Endpoint {
// Find the zone for the PTR record
// zone := findMsgZone(&endpoint.Endpoint{DNSName: ip}, p.ptrZoneNames)
// Generate PTR notation record starting from the IP address
var records []*endpoint.Endpoint

log.Debugf("Reverse zone is: %s %s", ip, dns.Fqdn(ip))
reverseAddress, _ := dns.ReverseAddr(ip)

// PTR
records = append(records, &endpoint.Endpoint{
DNSName: reverseAddress[:len(reverseAddress)-1],
RecordType: "PTR",
Targets: endpoint.Targets{hostname},
})

return records
}

// ApplyChanges applies a given set of changes in a given zone.
func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
log.Debugf("ApplyChanges (Create: %d, UpdateOld: %d, UpdateNew: %d, Delete: %d)", len(changes.Create), len(changes.UpdateOld), len(changes.UpdateNew), len(changes.Delete))
Expand All @@ -300,6 +334,10 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
m[zone].SetUpdate(zone)

r.AddRecord(m[zone], ep)

if r.createPTR && (ep.RecordType == "A" || ep.RecordType == "AAAA") {
r.AddReverseRecord(ep.Targets[0], ep.DNSName)
}
}

// only send if there are records available
Expand Down Expand Up @@ -335,6 +373,10 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
m[zone].SetUpdate(zone)

r.UpdateRecord(m[zone], changes.UpdateOld[i], ep)
if r.createPTR && (ep.RecordType == "A" || ep.RecordType == "AAAA") {
r.RemoveReverseRecord(changes.UpdateOld[i].Targets[0], ep.DNSName)
r.AddReverseRecord(ep.Targets[0], ep.DNSName)
}
}

// only send if there are records available
Expand Down Expand Up @@ -369,6 +411,9 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
m[zone].SetUpdate(zone)

r.RemoveRecord(m[zone], ep)
if r.createPTR && (ep.RecordType == "A" || ep.RecordType == "AAAA") {
r.RemoveReverseRecord(ep.Targets[0], ep.DNSName)
}
}

// only send if there are records available
Expand Down
42 changes: 38 additions & 4 deletions provider/rfc2136/rfc2136_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,24 @@ func createRfc2136StubProvider(stub *rfc2136Stub) (provider.Provider, error) {
ClientCertFilePath: "",
ClientCertKeyFilePath: "",
}
return NewRfc2136Provider("", 0, nil, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
return NewRfc2136Provider("", 0, nil, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, false, "", "", "", 50, tlsConfig, stub)
}

func createRfc2136TLSStubProvider(stub *rfc2136Stub, tlsConfig TLSConfig) (provider.Provider, error) {
return NewRfc2136Provider("rfc2136-host", 0, nil, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
return NewRfc2136Provider("rfc2136-host", 0, nil, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, false, "", "", "", 50, tlsConfig, stub)
}

func createRfc2136StubProviderWithReverse(stub *rfc2136Stub) (provider.Provider, error) {
tlsConfig := TLSConfig{
UseTLS: false,
SkipTLSVerify: false,
CAFilePath: "",
ClientCertFilePath: "",
ClientCertKeyFilePath: "",
}

zones := []string{"foo.com", "3.2.1.in-addr.arpa"}
return NewRfc2136Provider("", 0, zones, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{Filters: zones}, false, 300*time.Second, true, false, "", "", "", 50, tlsConfig, stub)
}

func createRfc2136StubProviderWithZones(stub *rfc2136Stub) (provider.Provider, error) {
Expand All @@ -143,7 +156,7 @@ func createRfc2136StubProviderWithZones(stub *rfc2136Stub) (provider.Provider, e
ClientCertKeyFilePath: "",
}
zones := []string{"foo.com", "foobar.com"}
return NewRfc2136Provider("", 0, zones, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
return NewRfc2136Provider("", 0, zones, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, false, "", "", "", 50, tlsConfig, stub)
}

func createRfc2136StubProviderWithZonesFilters(stub *rfc2136Stub) (provider.Provider, error) {
Expand All @@ -155,7 +168,7 @@ func createRfc2136StubProviderWithZonesFilters(stub *rfc2136Stub) (provider.Prov
ClientCertKeyFilePath: "",
}
zones := []string{"foo.com", "foobar.com"}
return NewRfc2136Provider("", 0, zones, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{Filters: zones}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
return NewRfc2136Provider("", 0, zones, false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{Filters: zones}, false, 300*time.Second, false, false, "", "", "", 50, tlsConfig, stub)
}

func extractUpdateSectionFromMessage(msg fmt.Stringer) []string {
Expand Down Expand Up @@ -196,6 +209,27 @@ func TestRfc2136GetRecordsMultipleTargets(t *testing.T) {
assert.Equal(t, 0, len(recs[0].ProviderSpecific), "expected no provider specific config")
}

func TestRfc2136PTRCreation(t *testing.T) {
stub := newStub()
provider, err := createRfc2136StubProviderWithReverse(stub)
assert.NoError(t, err)

err = provider.ApplyChanges(context.Background(), &plan.Changes{
Create: []*endpoint.Endpoint{
{
DNSName: "demo.foo.com",
RecordType: "A",
Targets: []string{"1.2.3.4"},
},
},
})
assert.NoError(t, err)
assert.Equal(t, 2, len(stub.createMsgs), "expected two records, one A and one PTR")
createMsgs := getSortedChanges(stub.createMsgs)
assert.True(t, strings.Contains(strings.Join(strings.Fields(createMsgs[0]), " "), "4.3.2.1.in-addr.arpa. 300 IN PTR demo.foo.com."), "excpeted a PTR record")
assert.True(t, strings.Contains(strings.Join(strings.Fields(createMsgs[1]), " "), "demo.foo.com. 300 IN A 1.2.3.4"), "expected an A record")
}

func TestRfc2136TLSConfig(t *testing.T) {
stub := newStub()

Expand Down
2 changes: 1 addition & 1 deletion source/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func testEndpointsFromIngress(t *testing.T) {
{
title: "invalid hostname does not generate endpoints",
ingress: fakeIngress{
dnsnames: []string{"this-is-an-exceedingly-long-label-that-external-dns-should-reject.example.org"},
dnsnames: []string{"this-is-an-exceedingly-long-label-that-external-dns-should-reject.example.org"},
},
expected: []*endpoint.Endpoint{},
},
Expand Down

0 comments on commit 8245b89

Please sign in to comment.