remove wildcard query functionality (#5019)

Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
This commit is contained in:
Chris O'Haver 2022-02-09 09:25:10 -05:00 committed by GitHub
parent 40a526b27f
commit abaf938623
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 30 additions and 178 deletions

View file

@ -196,27 +196,6 @@ packet received by CoreDNS must be the IP address of the Pod that sent the reque
} }
} }
## Wildcards
**NOTE: Wildcard queries are deprecated** and will no longer be supported in the next minor release.
Some query labels accept a wildcard value to match any value. If a label is a valid wildcard (\*,
or the word "any"), then that label will match all values. The labels that accept wildcards are:
* _endpoint_ in an `A` record request: _endpoint_.service.namespace.svc.zone, e.g., `*.nginx.ns.svc.cluster.local`
* _service_ in an `A` record request: _service_.namespace.svc.zone, e.g., `*.ns.svc.cluster.local`
* _namespace_ in an `A` record request: service._namespace_.svc.zone, e.g., `nginx.*.svc.cluster.local`
* _port and/or protocol_ in an `SRV` request: __port_.__protocol_.service.namespace.svc.zone.,
e.g., `_http.*.service.ns.svc.cluster.local`
* multiple wildcards are allowed in a single query, e.g., `A` Request `*.*.svc.zone.` or `SRV` request `*.*.*.*.svc.zone.`
For example, wildcards can be used to resolve all Endpoints for a Service as `A` records. e.g.: `*.service.ns.svc.myzone.local` will return the Endpoint IPs in the Service `service` in namespace `default`:
```
*.service.default.svc.cluster.local. 5 IN A 192.168.10.10
*.service.default.svc.cluster.local. 5 IN A 192.168.25.15
```
## Metadata ## Metadata
The kubernetes plugin will publish the following metadata, if the *metadata* The kubernetes plugin will publish the following metadata, if the *metadata*

View file

@ -24,11 +24,7 @@ func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
// We are dealing with a fairly normal domain name here, but we still need to have the service // We are dealing with a fairly normal domain name here, but we still need to have the service
// and the namespace: // and the namespace:
// service.namespace.<base> // service.namespace.<base>
// var port, protocol string
// for service (and SRV) you can also say _tcp, and port (i.e. _http), we need those be picked
// up, unless they are not specified, then we use an internal wildcard.
port := "*"
protocol := "*"
namespace := segs[last] namespace := segs[last]
if !k.namespaceExposed(namespace) { if !k.namespaceExposed(namespace) {
return nil, dns.RcodeNameError return nil, dns.RcodeNameError
@ -69,7 +65,7 @@ func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
for _, ip := range svc.ExternalIPs { for _, ip := range svc.ExternalIPs {
for _, p := range svc.Ports { for _, p := range svc.Ports {
if !(match(port, p.Name) && match(protocol, string(p.Protocol))) { if !(matchPortAndProtocol(port, p.Name, protocol, string(p.Protocol))) {
continue continue
} }
rcode = dns.RcodeSuccess rcode = dns.RcodeSuccess

View file

@ -31,9 +31,6 @@ var extCases = []struct {
{Host: "1:2::5", Port: 80, TTL: 5, Key: "/c/org/example/testns/svc1"}, {Host: "1:2::5", Port: 80, TTL: 5, Key: "/c/org/example/testns/svc1"},
}, },
}, },
{
Qname: "*._not-udp-or-tcp.svc1.testns.example.com.", Rcode: dns.RcodeSuccess,
},
{ {
Qname: "_http._tcp.svc1.testns.example.com.", Rcode: dns.RcodeSuccess, Qname: "_http._tcp.svc1.testns.example.com.", Rcode: dns.RcodeSuccess,
Msg: []msg.Service{ Msg: []msg.Service{

View file

@ -30,14 +30,6 @@ var dnsTestCases = []test.Case{
test.A("svcempty.testns.svc.cluster.local. 5 IN A 10.0.0.1"), test.A("svcempty.testns.svc.cluster.local. 5 IN A 10.0.0.1"),
}, },
}, },
// A Service (wildcard)
{
Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("svc1.*.svc.cluster.local. 5 IN A 10.0.0.1"),
},
},
{ {
Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess, Rcode: dns.RcodeSuccess,
@ -56,42 +48,6 @@ var dnsTestCases = []test.Case{
Answer: []dns.RR{test.SRV("svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.")}, Answer: []dns.RR{test.SRV("svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.")},
Extra: []dns.RR{test.AAAA("svc6.testns.svc.cluster.local. 5 IN AAAA 1234:abcd::1")}, Extra: []dns.RR{test.AAAA("svc6.testns.svc.cluster.local. 5 IN AAAA 1234:abcd::1")},
}, },
// SRV Service (wildcard)
{
Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{test.SRV("svc1.*.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")},
Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1")},
},
{
Qname: "svcempty.*.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{test.SRV("svcempty.*.svc.cluster.local. 5 IN SRV 0 100 80 svcempty.testns.svc.cluster.local.")},
Extra: []dns.RR{test.A("svcempty.testns.svc.cluster.local. 5 IN A 10.0.0.1")},
},
// SRV Service (wildcards)
{
Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{test.SRV("*.any.svc1.*.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")},
Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1")},
},
// A Service (wildcards)
{
Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("*.any.svc1.*.svc.cluster.local. 5 IN A 10.0.0.1"),
},
},
// SRV Service Not udp/tcp
{
Qname: "*._not-udp-or-tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
},
},
// SRV Service // SRV Service
{ {
Qname: "_http._tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, Qname: "_http._tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
@ -195,14 +151,6 @@ var dnsTestCases = []test.Case{
test.A("hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5"), test.A("hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5"),
}, },
}, },
// SRV Service (Headless and portless)
{
Qname: "*.*.hdlsprtls.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
},
},
// AAAA // AAAA
{ {
Qname: "5678-abcd--2.hdls1.testns.svc.cluster.local", Qtype: dns.TypeAAAA, Qname: "5678-abcd--2.hdls1.testns.svc.cluster.local", Qtype: dns.TypeAAAA,
@ -406,7 +354,6 @@ var dnsTestCases = []test.Case{
} }
func TestServeDNS(t *testing.T) { func TestServeDNS(t *testing.T) {
k := New([]string{"cluster.local."}) k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{} k.APIConn = &APIConnServeTest{}
k.Next = test.NextHandler(dns.RcodeSuccess, nil) k.Next = test.NextHandler(dns.RcodeSuccess, nil)
@ -434,11 +381,11 @@ func TestServeDNS(t *testing.T) {
// Before sorting, make sure that CNAMES do not appear after their target records // Before sorting, make sure that CNAMES do not appear after their target records
if err := test.CNAMEOrder(resp); err != nil { if err := test.CNAMEOrder(resp); err != nil {
t.Error(err) t.Errorf("Test %d, %v",i, err)
} }
if err := test.SortAndCheck(resp, tc); err != nil { if err := test.SortAndCheck(resp, tc); err != nil {
t.Error(err) t.Errorf("Test %d, %v",i, err)
} }
} }
} }

View file

@ -360,7 +360,7 @@ func (k *Kubernetes) Records(ctx context.Context, state request.Request, exact b
return nil, errNoItems return nil, errNoItems
} }
if !wildcard(r.namespace) && !k.namespaceExposed(r.namespace) { if !k.namespaceExposed(r.namespace) {
return nil, errNsNotExposed return nil, errNsNotExposed
} }
@ -395,7 +395,7 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
} }
namespace := r.namespace namespace := r.namespace
if !wildcard(namespace) && !k.namespaceExposed(namespace) { if !k.namespaceExposed(namespace) {
return nil, errNoItems return nil, errNoItems
} }
@ -403,7 +403,7 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
// handle empty pod name // handle empty pod name
if podname == "" { if podname == "" {
if k.namespaceExposed(namespace) || wildcard(namespace) { if k.namespaceExposed(namespace) {
// NODATA // NODATA
return nil, nil return nil, nil
} }
@ -420,7 +420,7 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
} }
if k.podMode == podModeInsecure { if k.podMode == podModeInsecure {
if !wildcard(namespace) && !k.namespaceExposed(namespace) { // no wildcard, but namespace does not exist if !k.namespaceExposed(namespace) { // namespace does not exist
return nil, errNoItems return nil, errNoItems
} }
@ -434,19 +434,8 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
// PodModeVerified // PodModeVerified
err = errNoItems err = errNoItems
if wildcard(podname) && !wildcard(namespace) {
// If namespace exists, err should be nil, so that we return NODATA instead of NXDOMAIN
if k.namespaceExposed(namespace) {
err = nil
}
}
for _, p := range k.APIConn.PodIndex(ip) { for _, p := range k.APIConn.PodIndex(ip) {
// If namespace has a wildcard, filter results against Corefile namespace list.
if wildcard(namespace) && !k.namespaceExposed(p.Namespace) {
continue
}
// check for matching ip and namespace // check for matching ip and namespace
if ip == p.PodIP && match(namespace, p.Namespace) { if ip == p.PodIP && match(namespace, p.Namespace) {
s := msg.Service{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip, TTL: k.ttl} s := msg.Service{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip, TTL: k.ttl}
@ -460,13 +449,13 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
// findServices returns the services matching r from the cache. // findServices returns the services matching r from the cache.
func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.Service, err error) { func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.Service, err error) {
if !wildcard(r.namespace) && !k.namespaceExposed(r.namespace) { if !k.namespaceExposed(r.namespace) {
return nil, errNoItems return nil, errNoItems
} }
// handle empty service name // handle empty service name
if r.service == "" { if r.service == "" {
if k.namespaceExposed(r.namespace) || wildcard(r.namespace) { if k.namespaceExposed(r.namespace) {
// NODATA // NODATA
return nil, nil return nil, nil
} }
@ -475,12 +464,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
} }
err = errNoItems err = errNoItems
if wildcard(r.service) && !wildcard(r.namespace) {
// If namespace exists, err should be nil, so that we return NODATA instead of NXDOMAIN
if k.namespaceExposed(r.namespace) {
err = nil
}
}
var ( var (
endpointsListFunc func() []*object.Endpoints endpointsListFunc func() []*object.Endpoints
@ -488,14 +471,11 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
serviceList []*object.Service serviceList []*object.Service
) )
if wildcard(r.service) || wildcard(r.namespace) {
serviceList = k.APIConn.ServiceList() idx := object.ServiceKey(r.service, r.namespace)
endpointsListFunc = func() []*object.Endpoints { return k.APIConn.EndpointsList() } serviceList = k.APIConn.SvcIndex(idx)
} else { endpointsListFunc = func() []*object.Endpoints { return k.APIConn.EpIndex(idx) }
idx := object.ServiceKey(r.service, r.namespace)
serviceList = k.APIConn.SvcIndex(idx)
endpointsListFunc = func() []*object.Endpoints { return k.APIConn.EpIndex(idx) }
}
zonePath := msg.Path(zone, coredns) zonePath := msg.Path(zone, coredns)
for _, svc := range serviceList { for _, svc := range serviceList {
@ -503,12 +483,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
continue continue
} }
// If request namespace is a wildcard, filter results against Corefile namespace list.
// (Namespaces without a wildcard were filtered before the call to this function.)
if wildcard(r.namespace) && !k.namespaceExposed(svc.Namespace) {
continue
}
// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless // If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless
// it's a headless or externalName service (covered below). // it's a headless or externalName service (covered below).
if k.opts.ignoreEmptyService && svc.Type != api.ServiceTypeExternalName && !svc.Headless() { // serve NXDOMAIN if no endpoint is able to answer if k.opts.ignoreEmptyService && svc.Type != api.ServiceTypeExternalName && !svc.Headless() { // serve NXDOMAIN if no endpoint is able to answer
@ -558,7 +532,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
} }
for _, p := range eps.Ports { for _, p := range eps.Ports {
if !(match(r.port, p.Name) && match(r.protocol, p.Protocol)) { if !(matchPortAndProtocol(r.port, p.Name, r.protocol, p.Protocol)) {
continue continue
} }
s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl}
@ -576,7 +550,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
// ClusterIP service // ClusterIP service
for _, p := range svc.Ports { for _, p := range svc.Ports {
if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { if !(matchPortAndProtocol(r.port, p.Name, r.protocol, string(p.Protocol))) {
continue continue
} }
@ -598,20 +572,14 @@ func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIC
// MinTTL returns the minimal TTL. // MinTTL returns the minimal TTL.
func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl } func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl }
// match checks if a and b are equal taking wildcards into account. // match checks if a and b are equal.
func match(a, b string) bool { func match(a, b string) bool {
if wildcard(a) {
return true
}
if wildcard(b) {
return true
}
return strings.EqualFold(a, b) return strings.EqualFold(a, b)
} }
// wildcard checks whether s contains a wildcard value defined as "*" or "any". // matchPortAndProtocol matches port and protocol, permitting the the 'a' inputs to be wild
func wildcard(s string) bool { func matchPortAndProtocol(aPort, bPort, aProtocol, bProtocol string) bool {
return s == "*" || s == "any" return (match(aPort, bPort) || aPort == "") && (match(aProtocol, bProtocol) || aProtocol == "")
} }
const coredns = "c" // used as a fake key prefix in msg.Service const coredns = "c" // used as a fake key prefix in msg.Service

View file

@ -14,27 +14,6 @@ import (
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
func TestWildcard(t *testing.T) {
var tests = []struct {
s string
expected bool
}{
{"mynamespace", false},
{"*", true},
{"any", true},
{"my*space", false},
{"*space", false},
{"myname*", false},
}
for _, te := range tests {
got := wildcard(te.s)
if got != te.expected {
t.Errorf("Expected Wildcard result '%v' for example '%v', got '%v'.", te.expected, te.s, got)
}
}
}
func TestEndpointHostname(t *testing.T) { func TestEndpointHostname(t *testing.T) {
var tests = []struct { var tests = []struct {
ip string ip string

View file

@ -29,8 +29,8 @@ var metadataCases = []struct {
"kubernetes/endpoint": "", "kubernetes/endpoint": "",
"kubernetes/kind": "pod", "kubernetes/kind": "pod",
"kubernetes/namespace": "podns", "kubernetes/namespace": "podns",
"kubernetes/port-name": "*", "kubernetes/port-name": "",
"kubernetes/protocol": "*", "kubernetes/protocol": "",
"kubernetes/service": "10-240-0-1", "kubernetes/service": "10-240-0-1",
}, },
}, },
@ -40,8 +40,8 @@ var metadataCases = []struct {
"kubernetes/endpoint": "", "kubernetes/endpoint": "",
"kubernetes/kind": "svc", "kubernetes/kind": "svc",
"kubernetes/namespace": "ns", "kubernetes/namespace": "ns",
"kubernetes/port-name": "*", "kubernetes/port-name": "",
"kubernetes/protocol": "*", "kubernetes/protocol": "",
"kubernetes/service": "s", "kubernetes/service": "s",
}, },
}, },
@ -52,8 +52,8 @@ var metadataCases = []struct {
"kubernetes/endpoint": "", "kubernetes/endpoint": "",
"kubernetes/kind": "svc", "kubernetes/kind": "svc",
"kubernetes/namespace": "ns", "kubernetes/namespace": "ns",
"kubernetes/port-name": "*", "kubernetes/port-name": "",
"kubernetes/protocol": "*", "kubernetes/protocol": "",
"kubernetes/service": "s", "kubernetes/service": "s",
}, },
}, },
@ -76,8 +76,8 @@ var metadataCases = []struct {
"kubernetes/endpoint": "ep", "kubernetes/endpoint": "ep",
"kubernetes/kind": "svc", "kubernetes/kind": "svc",
"kubernetes/namespace": "ns", "kubernetes/namespace": "ns",
"kubernetes/port-name": "*", "kubernetes/port-name": "",
"kubernetes/protocol": "*", "kubernetes/protocol": "",
"kubernetes/service": "s", "kubernetes/service": "s",
}, },
}, },

View file

@ -38,18 +38,6 @@ func parseRequest(name, zone string) (r recordRequest, err error) {
} }
segs := dns.SplitDomainName(base) segs := dns.SplitDomainName(base)
r.port = "*"
r.protocol = "*"
// for r.name, r.namespace and r.endpoint, we need to know if they have been set or not...
// For endpoint: if empty we should skip the endpoint check in k.get(). Hence we cannot set if to "*".
// For name: myns.svc.cluster.local != *.myns.svc.cluster.local
// For namespace: svc.cluster.local != *.svc.cluster.local
// start at the right and fill out recordRequest with the bits we find, so we look for
// pod|svc.namespace.service and then either
// * endpoint
// *_protocol._port
last := len(segs) - 1 last := len(segs) - 1
if last < 0 { if last < 0 {
return r, nil return r, nil

View file

@ -15,10 +15,8 @@ func TestParseRequest(t *testing.T) {
}{ }{
// valid SRV request // valid SRV request
{"_http._tcp.webs.mynamespace.svc.inter.webs.tests.", "http.tcp..webs.mynamespace.svc"}, {"_http._tcp.webs.mynamespace.svc.inter.webs.tests.", "http.tcp..webs.mynamespace.svc"},
// wildcard acceptance
{"*.any.*.any.svc.inter.webs.tests.", "*.any..*.any.svc"},
// A request of endpoint // A request of endpoint
{"1-2-3-4.webs.mynamespace.svc.inter.webs.tests.", "*.*.1-2-3-4.webs.mynamespace.svc"}, {"1-2-3-4.webs.mynamespace.svc.inter.webs.tests.", "..1-2-3-4.webs.mynamespace.svc"},
// bare zone // bare zone
{"inter.webs.tests.", "....."}, {"inter.webs.tests.", "....."},
// bare svc type // bare svc type