make sure that external-dns will create PTR records for records that are already managed by external dns

This commit is contained in:
Pavel Tumik 2021-08-27 18:17:57 -07:00
parent 9a6e004a2d
commit ae07a2d2cb
No known key found for this signature in database
GPG Key ID: 221BDC861516601D
5 changed files with 135 additions and 16 deletions

View File

@ -159,6 +159,7 @@ spec:
- --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443". - --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443".
- --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1" - --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1"
- --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification. - --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification.
- --infoblox-create-ptr # (optional) Use --infoblox-create-ptr to create a ptr entry in addition to an entry.
env: env:
- name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS
value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10".
@ -269,3 +270,11 @@ There is also the ability to filter results from the Infoblox zone_auth service
``` ```
--infoblox-fqdn-regex=^staging.*test.com$ --infoblox-fqdn-regex=^staging.*test.com$
``` ```
## Infoblox PTR record support
There is an option to enable PTR records support for infoblox provider. PTR records allow to do reverse dns search. To enable PTR records support, add following into arguments for external-dns:
`--infoblox-create-ptr` to allow management of PTR records.
You can also add a filter for reverse dns zone to limit PTR records to specific zones only:
`--domain-filter=10.196.0.0/16` change this to the reverse zone(s) as defined in your infoblox.
Now external-dns will manage PTR records for you.

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.9.16 github.com/Azure/go-autorest/autorest/adal v0.9.16
github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1
github.com/StackExchange/dnscontrol v0.2.8
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/colour v0.1.0 // indirect
github.com/alecthomas/kingpin v2.2.5+incompatible github.com/alecthomas/kingpin v2.2.5+incompatible

2
go.sum
View File

@ -124,6 +124,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ= github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ=
github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8=
github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=

View File

@ -26,6 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
transform "github.com/StackExchange/dnscontrol/pkg/transform"
ibclient "github.com/infobloxopen/infoblox-go-client" ibclient "github.com/infobloxopen/infoblox-go-client"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -34,6 +35,11 @@ import (
"sigs.k8s.io/external-dns/provider" "sigs.k8s.io/external-dns/provider"
) )
const (
// provider specific key to track if PTR record was already created or not for A records
providerSpecificInfobloxPtrRecord = "infoblox-ptr-record-exists"
)
// InfobloxConfig clarifies the method signature // InfobloxConfig clarifies the method signature
type InfobloxConfig struct { type InfobloxConfig struct {
DomainFilter endpoint.DomainFilter DomainFilter endpoint.DomainFilter
@ -174,6 +180,9 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
} }
for _, res := range resA { for _, res := range resA {
newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, res.Ipv4Addr) newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, res.Ipv4Addr)
if p.createPTR {
newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "false")
}
// Check if endpoint already exists and add to existing endpoint if it does // Check if endpoint already exists and add to existing endpoint if it does
foundExisting := false foundExisting := false
for _, ep := range endpoints { for _, ep := range endpoints {
@ -207,7 +216,13 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
} }
for _, res := range resH { for _, res := range resH {
for _, ip := range res.Ipv4Addrs { for _, ip := range res.Ipv4Addrs {
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr)) // host record is an abstraction in infoblox that combines A and PTR records
// for any host record we already should have a PTR record in infoblox, so mark it as created
newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr)
if p.createPTR {
newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true")
}
endpoints = append(endpoints, newEndpoint)
} }
} }
@ -227,19 +242,25 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
} }
if p.createPTR { if p.createPTR {
var resP []ibclient.RecordPTR // infoblox doesn't accept reverse zone's fqdn, and instead expects .in-addr.arpa zone
objP := ibclient.NewRecordPTR( // so convert our zone fqdn (if it is a correct cidr block) into in-addr.arpa address and pass that into infoblox
ibclient.RecordPTR{ // example: 10.196.38.0/24 becomes 38.196.10.in-addr.arpa
Zone: zone.Fqdn, arpaZone, err := transform.ReverseDomainName(zone.Fqdn)
View: p.view, if err == nil {
}, var resP []ibclient.RecordPTR
) objP := ibclient.NewRecordPTR(
err = p.client.GetObject(objP, "", &resP) ibclient.RecordPTR{
if err != nil { Zone: arpaZone,
return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %s", zone.Fqdn, err) View: p.view,
} },
for _, res := range resP { )
endpoints = append(endpoints, endpoint.NewEndpoint(res.PtrdName, endpoint.RecordTypePTR, res.Ipv4Addr)) err = p.client.GetObject(objP, "", &resP)
if err != nil {
return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %s", zone.Fqdn, err)
}
for _, res := range resP {
endpoints = append(endpoints, endpoint.NewEndpoint(res.PtrdName, endpoint.RecordTypePTR, res.Ipv4Addr))
}
} }
} }
@ -263,10 +284,66 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text)) endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text))
} }
} }
// update A records that have PTR record created for them already
if p.createPTR {
// save all ptr records into map for a quick look up
ptrRecordsMap := make(map[string]bool)
for _, ptrRecord := range endpoints {
if ptrRecord.RecordType != endpoint.RecordTypePTR {
continue
}
ptrRecordsMap[ptrRecord.DNSName] = true
}
for i := range endpoints {
if endpoints[i].RecordType != endpoint.RecordTypeA {
continue
}
// if PTR record already exists for A record, then mark it as such
if ptrRecordsMap[endpoints[i].DNSName] {
found := false
for j := range endpoints[i].ProviderSpecific {
if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord {
endpoints[i].ProviderSpecific[j].Value = "true"
found = true
}
}
if !found {
endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true")
}
}
}
}
logrus.Debugf("fetched %d records from infoblox", len(endpoints)) logrus.Debugf("fetched %d records from infoblox", len(endpoints))
return endpoints, nil return endpoints, nil
} }
func (p *InfobloxProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
if !p.createPTR {
return endpoints
}
// for all A records, we want to create PTR records
// so add provider specific property to track if the record was created or not
for i := range endpoints {
if endpoints[i].RecordType == endpoint.RecordTypeA {
found := false
for j := range endpoints[i].ProviderSpecific {
if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord {
endpoints[i].ProviderSpecific[j].Value = "true"
found = true
}
}
if !found {
endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true")
}
}
}
return endpoints
}
// ApplyChanges applies the given changes. // ApplyChanges applies the given changes.
func (p *InfobloxProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { func (p *InfobloxProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
zones, err := p.zones() zones, err := p.zones()
@ -418,7 +495,7 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targ
obj := ibclient.NewRecordPTR( obj := ibclient.NewRecordPTR(
ibclient.RecordPTR{ ibclient.RecordPTR{
PtrdName: ep.DNSName, PtrdName: ep.DNSName,
Ipv4Addr: ep.Targets[0], Ipv4Addr: ep.Targets[targetIndex],
View: p.view, View: p.view,
}, },
) )

View File

@ -91,7 +91,6 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string,
obj.(*ibclient.RecordTXT).Ref = ref obj.(*ibclient.RecordTXT).Ref = ref
ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordTXT).Name)), obj.(*ibclient.RecordTXT).Name) ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordTXT).Name)), obj.(*ibclient.RecordTXT).Name)
case "record:ptr": case "record:ptr":
fmt.Printf("create ptr record\n")
client.createdEndpoints = append( client.createdEndpoints = append(
client.createdEndpoints, client.createdEndpoints,
endpoint.NewEndpoint( endpoint.NewEndpoint(
@ -459,7 +458,38 @@ func TestInfobloxRecords(t *testing.T) {
validateEndpoints(t, actual, expected) validateEndpoints(t, actual, expected)
} }
func TestInfobloxAdjustEndpoints(t *testing.T) {
client := mockIBConnector{
mockInfobloxZones: &[]ibclient.ZoneAuth{
createMockInfobloxZone("example.com"),
createMockInfobloxZone("other.com"),
},
mockInfobloxObjects: &[]ibclient.IBObject{
createMockInfobloxObject("example.com", endpoint.RecordTypeA, "123.123.123.122"),
createMockInfobloxObject("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"),
createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"),
},
}
provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, true, &client)
actual, err := provider.Records(context.Background())
if err != nil {
t.Fatal(err)
}
provider.AdjustEndpoints(actual)
expected := []*endpoint.Endpoint{
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"),
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""),
endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"),
endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"),
}
validateEndpoints(t, actual, expected)
}
func TestInfobloxRecordsReverse(t *testing.T) { func TestInfobloxRecordsReverse(t *testing.T) {
client := mockIBConnector{ client := mockIBConnector{
mockInfobloxZones: &[]ibclient.ZoneAuth{ mockInfobloxZones: &[]ibclient.ZoneAuth{
createMockInfobloxZone("10.0.0.0/24"), createMockInfobloxZone("10.0.0.0/24"),