aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Billie Cleek <bhcleek@users.noreply.github.com> 2019-05-01 07:42:38 -0700
committerGravatar Chris O'Haver <cohaver@infoblox.com> 2019-05-01 10:42:38 -0400
commite178291ed6a9eae5d24bae132b0f4c2f4d75f662 (patch)
treeb1462a41591bc60b86d4f36b895c851b36695383
parentb4485b48d9d2fdcdac0e2750398b5fe506d3fba4 (diff)
downloadcoredns-e178291ed6a9eae5d24bae132b0f4c2f4d75f662.tar.gz
coredns-e178291ed6a9eae5d24bae132b0f4c2f4d75f662.tar.zst
coredns-e178291ed6a9eae5d24bae132b0f4c2f4d75f662.zip
kubernetes: never respond with NXDOMAIN for authority label (#2769)
* kubernetes: never respond with NXDOMAIN for authority label Return a nodata response when trying to resolve the authority's label for a record type that doesn't match the record type of the authority. This guards against poisoning the authority record by requesting the wrong record type for the authority label. For instance, given an authoritative resolver that uses subdomain delegation for Kubernetes services of a cluster that's configured to use IPv4, the parent may be poisoned by querying it for the authority label of the cluster subdomain with a AAAA record type, which would otherwise (i.e. without this change) return an NXDOMAIN. That is, given cluster.example.com NS 10800 ns.dns.cluster.example.com ns.dns.cluster.example.com A 10800 10.0.1.2 The parent may be poisoned for the SOA TTL by querying it for a AAAA record of ns.dns.cluster.example.com, causing the parent to fail delegate properly until the SOA TTL lapses. * kubernetes: add tests for authority queries
-rw-r--r--plugin/kubernetes/kubernetes.go12
-rw-r--r--plugin/kubernetes/kubernetes_test.go57
2 files changed, 66 insertions, 3 deletions
diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go
index df9d67060..4e214e6bc 100644
--- a/plugin/kubernetes/kubernetes.go
+++ b/plugin/kubernetes/kubernetes.go
@@ -112,10 +112,18 @@ func (k *Kubernetes) Services(ctx context.Context, state request.Request, exact
return []msg.Service{svc}, nil
}
- if state.QType() == dns.TypeA && isDefaultNS(state.Name(), state.Zone) {
+ if isDefaultNS(state.Name(), state.Zone) {
+ ns := k.nsAddr()
+
+ isIPv4 := ns.A.To4() != nil
+
+ if !((state.QType() == dns.TypeA && isIPv4) || (state.QType() == dns.TypeAAAA && !isIPv4)) {
+ // NODATA
+ return nil, nil
+ }
+
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
// SOA records always use this hardcoded name
- ns := k.nsAddr()
svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), coredns), TTL: k.ttl}
return []msg.Service{svc}, nil
}
diff --git a/plugin/kubernetes/kubernetes_test.go b/plugin/kubernetes/kubernetes_test.go
index 6d078f67e..c04574e8c 100644
--- a/plugin/kubernetes/kubernetes_test.go
+++ b/plugin/kubernetes/kubernetes_test.go
@@ -2,6 +2,7 @@ package kubernetes
import (
"context"
+ "net"
"testing"
"github.com/coredns/coredns/plugin"
@@ -254,7 +255,6 @@ func (APIConnServiceTest) GetNamespaceByName(name string) (*api.Namespace, error
}
func TestServices(t *testing.T) {
-
k := New([]string{"interwebs.test."})
k.APIConn = &APIConnServiceTest{}
@@ -301,6 +301,61 @@ func TestServices(t *testing.T) {
}
}
+func TestServicesAuthority(t *testing.T) {
+ k := New([]string{"interwebs.test."})
+ k.APIConn = &APIConnServiceTest{}
+
+ type svcAns struct {
+ host string
+ key string
+ }
+ type svcTest struct {
+ interfaceAddrs func() net.IP
+ qname string
+ qtype uint16
+ answer *svcAns
+ }
+ tests := []svcTest{
+ {interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA, answer: &svcAns{host: "127.0.0.1", key: "/" + coredns + "/test/interwebs/dns/ns"}},
+ {interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA},
+ {interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA},
+ {interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA, answer: &svcAns{host: "::1", key: "/" + coredns + "/test/interwebs/dns/ns"}},
+ }
+
+ for i, test := range tests {
+ k.interfaceAddrsFunc = test.interfaceAddrs
+
+ state := request.Request{
+ Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
+ Zone: "interwebs.test.", // must match from k.Zones[0]
+ }
+ svcs, e := k.Services(context.TODO(), state, false, plugin.Options{})
+ if e != nil {
+ t.Errorf("Test %d: got error '%v'", i, e)
+ continue
+ }
+ if test.answer != nil && len(svcs) != 1 {
+ t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs))
+ continue
+ }
+ if test.answer == nil && len(svcs) != 0 {
+ t.Errorf("Test %d, expected no answer, got %v", i, len(svcs))
+ continue
+ }
+
+ if test.answer == nil && len(svcs) == 0 {
+ continue
+ }
+
+ if test.answer.host != svcs[0].Host {
+ t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host)
+ }
+ if test.answer.key != svcs[0].Key {
+ t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key)
+ }
+ }
+}
+
func TestServiceFQDN(t *testing.T) {
fqdn := serviceFQDN(
&object.Service{