plugin/k8s_external: Add support for PTR requests (#5435)

* Exclude External IP addresses from being added to the existing kubernetes' plugin IP->Service index
* Add support for PTR requests on External IPs of Services to the k8s_external plugin

Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
This commit is contained in:
Chris O'Haver 2022-07-06 13:55:15 -04:00 committed by GitHub
parent d903a963ee
commit e80d696502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 163 additions and 44 deletions

View file

@ -10,7 +10,7 @@ This plugin allows an additional zone to resolve the external IP address(es) of
service. This plugin is only useful if the *kubernetes* plugin is also loaded.
The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A,
AAAA and SRV records; all others result in NODATA responses. To make it a proper DNS zone, it handles
AAAA, SRV, and PTR records; all others result in NODATA responses. To make it a proper DNS zone, it handles
SOA and NS queries for the apex of the zone.
By default the apex of the zone will look like the following (assuming the zone used is `example.org`):
@ -101,6 +101,3 @@ zone transfers. Notifies are not supported.
For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242).
And [A records for services with Load Balancer IP](https://github.com/coredns/coredns/issues/1851).
# Bugs
PTR queries for the reverse zone is not supported.

View file

@ -105,6 +105,8 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
m.Answer, m.Truncated = e.aaaa(ctx, svc, state)
case dns.TypeSRV:
m.Answer, m.Extra = e.srv(ctx, svc, state)
case dns.TypePTR:
m.Answer = e.ptr(svc, state)
default:
m.Ns = []dns.RR{e.soa(state)}
}

View file

@ -20,7 +20,7 @@ func TestExternal(t *testing.T) {
k.APIConn = &external{}
e := New()
e.Zones = []string{"example.com."}
e.Zones = []string{"example.com.", "in-addr.arpa."}
e.Next = test.NextHandler(dns.RcodeSuccess, nil)
e.externalFunc = k.External
e.externalAddrFunc = externalAddress // internal test function
@ -49,12 +49,33 @@ func TestExternal(t *testing.T) {
t.Error("Expected authoritative answer")
}
if err = test.SortAndCheck(resp, tc); err != nil {
t.Error(err)
t.Errorf("Test %d: %v", i, err)
}
}
}
var tests = []test.Case{
// PTR reverse lookup
{
Qname: "4.3.2.1.in-addr.arpa.", Qtype: dns.TypePTR, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.PTR("4.3.2.1.in-addr.arpa. 5 IN PTR svc1.testns.example.com."),
},
},
// Bad PTR reverse lookup using existing service name
{
Qname: "svc1.testns.example.com.", Qtype: dns.TypePTR, Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Bad PTR reverse lookup using non-existing service name
{
Qname: "not-existing.testns.example.com.", Qtype: dns.TypePTR, Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// A Service
{
Qname: "svc1.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
@ -155,7 +176,7 @@ var tests = []test.Case{
{
Qname: "svc11.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"),
test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
},
},
{
@ -164,7 +185,7 @@ var tests = []test.Case{
test.SRV("_http._tcp.svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
},
Extra: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"),
test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
},
},
{
@ -173,7 +194,7 @@ var tests = []test.Case{
test.SRV("svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
},
Extra: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"),
test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
},
},
// svc12
@ -211,6 +232,20 @@ func (external) GetNodeByName(ctx context.Context, name string) (*api.Node, erro
func (external) SvcIndex(s string) []*object.Service { return svcIndexExternal[s] }
func (external) PodIndex(string) []*object.Pod { return nil }
func (external) SvcExtIndexReverse(ip string) (result []*object.Service) {
for _, svcs := range svcIndexExternal {
for _, svc := range svcs {
for _, exIp := range svc.ExternalIPs {
if exIp != ip {
continue
}
result = append(result, svc)
}
}
}
return result
}
func (external) GetNamespaceByName(name string) (*object.Namespace, error) {
return &object.Namespace{
Name: name,
@ -243,7 +278,7 @@ var svcIndexExternal = map[string][]*object.Service{
Name: "svc11",
Namespace: "testns",
Type: api.ServiceTypeLoadBalancer,
ExternalIPs: []string{"1.2.3.4"},
ExternalIPs: []string{"2.3.4.5"},
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
},
},

View file

@ -5,6 +5,7 @@ import (
"math"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
@ -76,6 +77,19 @@ func (e *External) aaaa(ctx context.Context, services []msg.Service, state reque
return records, truncated
}
func (e *External) ptr(services []msg.Service, state request.Request) (records []dns.RR) {
dup := make(map[string]struct{})
for _, s := range services {
if _, ok := dup[s.Host]; !ok {
dup[s.Host] = struct{}{}
rr := s.NewPTR(state.QName(), dnsutil.Join(s.Host, e.Zones[0]))
rr.Hdr.Ttl = e.ttl
records = append(records, rr)
}
}
return records
}
func (e *External) srv(ctx context.Context, services []msg.Service, state request.Request) (records, extra []dns.RR) {
dup := make(map[item]struct{})

View file

@ -59,6 +59,11 @@ func TestTransferAXFR(t *testing.T) {
if ans.Header().Rrtype == dns.TypeTXT {
continue
}
// Exclude PTR records
if ans.Header().Rrtype == dns.TypePTR {
continue
}
expect = append(expect, ans)
}
}

View file

@ -25,6 +25,7 @@ const (
podIPIndex = "PodIP"
svcNameNamespaceIndex = "ServiceNameNamespace"
svcIPIndex = "ServiceIP"
svcExtIPIndex = "ServiceExternalIP"
epNameNamespaceIndex = "EndpointNameNamespace"
epIPIndex = "EndpointsIP"
)
@ -34,6 +35,7 @@ type dnsController interface {
EndpointsList() []*object.Endpoints
SvcIndex(string) []*object.Service
SvcIndexReverse(string) []*object.Service
SvcExtIndexReverse(string) []*object.Service
PodIndex(string) []*object.Pod
EpIndex(string) []*object.Endpoints
EpIndexReverse(string) []*object.Endpoints
@ -122,7 +124,7 @@ func newdnsController(ctx context.Context, kubeClient kubernetes.Interface, opts
},
&api.Service{},
cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete},
cache.Indexers{svcNameNamespaceIndex: svcNameNamespaceIndexFunc, svcIPIndex: svcIPIndexFunc},
cache.Indexers{svcNameNamespaceIndex: svcNameNamespaceIndexFunc, svcIPIndex: svcIPIndexFunc, svcExtIPIndex: svcExtIPIndexFunc},
object.DefaultProcessor(object.ToService, nil),
)
@ -232,12 +234,18 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) {
if !ok {
return nil, errObj
}
idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs))
idx := make([]string, len(svc.ClusterIPs))
copy(idx, svc.ClusterIPs)
if len(svc.ExternalIPs) == 0 {
return idx, nil
return idx, nil
}
func svcExtIPIndexFunc(obj interface{}) ([]string, error) {
svc, ok := obj.(*object.Service)
if !ok {
return nil, errObj
}
copy(idx[len(svc.ClusterIPs):], svc.ExternalIPs)
idx := make([]string, len(svc.ExternalIPs))
copy(idx, svc.ExternalIPs)
return idx, nil
}
@ -502,6 +510,22 @@ func (dns *dnsControl) SvcIndexReverse(ip string) (svcs []*object.Service) {
return svcs
}
func (dns *dnsControl) SvcExtIndexReverse(ip string) (svcs []*object.Service) {
os, err := dns.svcLister.ByIndex(svcExtIPIndex, ip)
if err != nil {
return nil
}
for _, o := range os {
s, ok := o.(*object.Service)
if !ok {
continue
}
svcs = append(svcs, s)
}
return svcs
}
func (dns *dnsControl) EpIndex(idx string) (ep []*object.Endpoints) {
dns.epLock.RLock()
defer dns.epLock.RUnlock()

View file

@ -14,6 +14,18 @@ import (
// External implements the ExternalFunc call from the external plugin.
// It returns any services matching in the services' ExternalIPs.
func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
if state.QType() == dns.TypePTR {
ip := dnsutil.ExtractAddressFromReverse(state.Name())
if ip != "" {
svcs, err := k.ExternalReverse(ip)
if err != nil {
return nil, dns.RcodeNameError
}
return svcs, dns.RcodeSuccess
}
// for invalid reverse names, fall through to determine proper nxdomain/nodata response
}
base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
segs := dns.SplitDomainName(base)
@ -76,6 +88,10 @@ func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
}
}
}
if state.QType() == dns.TypePTR {
// if this was a PTR request, return empty service list, but retain rcode for proper nxdomain/nodata response
return nil, rcode
}
return services, rcode
}
@ -111,3 +127,24 @@ func (k *Kubernetes) ExternalServices(zone string) (services []msg.Service) {
func (k *Kubernetes) ExternalSerial(string) uint32 {
return uint32(k.APIConn.Modified(true))
}
// ExternalReverse does a reverse lookup for the external IPs
func (k *Kubernetes) ExternalReverse(ip string) ([]msg.Service, error) {
records := k.serviceRecordForExternalIP(ip)
if len(records) == 0 {
return records, errNoItems
}
return records, nil
}
func (k *Kubernetes) serviceRecordForExternalIP(ip string) []msg.Service {
var svcs []msg.Service
for _, service := range k.APIConn.SvcExtIndexReverse(ip) {
if len(k.Namespaces) > 0 && !k.namespaceExposed(service.Namespace) {
continue
}
domain := strings.Join([]string{service.Name, service.Namespace}, ".")
svcs = append(svcs, msg.Service{Host: domain, TTL: k.ttl})
}
return svcs
}

View file

@ -80,6 +80,7 @@ func (external) Run()
func (external) Stop() error { return nil }
func (external) EpIndexReverse(string) []*object.Endpoints { return nil }
func (external) SvcIndexReverse(string) []*object.Service { return nil }
func (external) SvcExtIndexReverse(string) []*object.Service { return nil }
func (external) Modified(bool) int64 { return 0 }
func (external) EpIndex(s string) []*object.Endpoints { return nil }
func (external) EndpointsList() []*object.Endpoints { return nil }

View file

@ -536,12 +536,13 @@ type APIConnServeTest struct {
notSynced bool
}
func (a APIConnServeTest) HasSynced() bool { return !a.notSynced }
func (APIConnServeTest) Run() {}
func (APIConnServeTest) Stop() error { return nil }
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified(bool) int64 { return int64(3) }
func (a APIConnServeTest) HasSynced() bool { return !a.notSynced }
func (APIConnServeTest) Run() {}
func (APIConnServeTest) Stop() error { return nil }
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified(bool) int64 { return int64(3) }
func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
if ip != "10.240.0.1" {

View file

@ -39,13 +39,14 @@ func TestEndpointHostname(t *testing.T) {
type APIConnServiceTest struct{}
func (APIConnServiceTest) HasSynced() bool { return true }
func (APIConnServiceTest) Run() {}
func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 }
func (APIConnServiceTest) HasSynced() bool { return true }
func (APIConnServiceTest) Run() {}
func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 }
func (APIConnServiceTest) SvcIndex(string) []*object.Service {
svcs := []*object.Service{

View file

@ -14,14 +14,15 @@ import (
type APIConnTest struct{}
func (APIConnTest) HasSynced() bool { return true }
func (APIConnTest) Run() {}
func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 }
func (APIConnTest) HasSynced() bool { return true }
func (APIConnTest) Run() {}
func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 }
func (a APIConnTest) SvcIndex(s string) []*object.Service {
switch s {

View file

@ -15,14 +15,15 @@ import (
type APIConnReverseTest struct{}
func (APIConnReverseTest) HasSynced() bool { return true }
func (APIConnReverseTest) Run() {}
func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 }
func (APIConnReverseTest) HasSynced() bool { return true }
func (APIConnReverseTest) Run() {}
func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 }
func (APIConnReverseTest) SvcIndex(svc string) []*object.Service {
if svc != "svc1.testns" {