kubernetes: Add zone filtering to fallthrough (#1353)

* Add zone filtering to fallthrough

* Doh. gofmt

* Update documentation
This commit is contained in:
John Belamaric 2018-01-06 14:52:09 -05:00 committed by GitHub
parent 75a8a17da4
commit 84ebbbc722
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 44 deletions

View file

@ -71,6 +71,9 @@ reverse cases and **all other** request are handled by the backing plugin. This
"fallthrough" does. To keep things explicit we've opted that plugins implement such behavior "fallthrough" does. To keep things explicit we've opted that plugins implement such behavior
should implement a `fallthrough` keyword. should implement a `fallthrough` keyword.
The `fallthrough` directive should optionally accept a list of zones. Only queries for records
in one of those zones should be allowed to fallthrough.
## Qualifying for main repo ## Qualifying for main repo
Plugins for CoreDNS can live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other Plugins for CoreDNS can live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other

View file

@ -38,7 +38,7 @@ kubernetes [ZONES...] {
endpoint_pod_names endpoint_pod_names
upstream ADDRESS... upstream ADDRESS...
ttl TTL ttl TTL
fallthrough fallthrough [ZONES...]
} }
``` ```
@ -85,9 +85,12 @@ kubernetes [ZONES...] {
to a file structured like resolv.conf. to a file structured like resolv.conf.
* `ttl` allows you to set a custom TTL for responses. The default (and allowed minimum) is to use * `ttl` allows you to set a custom TTL for responses. The default (and allowed minimum) is to use
5 seconds, the maximum is capped at 3600 seconds. 5 seconds, the maximum is capped at 3600 seconds.
* `fallthrough` If a query for a record in the cluster zone results in NXDOMAIN, normally that is * `fallthrough` **[ZONES...]** If a query for a record in the zones for which the plugin is authoritative
what the response will be. However, if you specify this option, the query will instead be passed results in NXDOMAIN, normally that is what the response will be. However, if you specify this option,
on down the plugin chain, which can include another plugin to handle the query. the query will instead be passed on down the plugin chain, which can include another plugin to handle
the query. If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin
is authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only
queries for those zones will be subject to fallthrough.
## Health ## Health

View file

@ -59,7 +59,7 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
} }
if k.IsNameError(err) { if k.IsNameError(err) {
if k.Fallthrough { if plugin.Fallthrough(k.Fallthrough, state.Name()) {
return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r) return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r)
} }
return plugin.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, plugin.Options{}) return plugin.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, plugin.Options{})

View file

@ -40,7 +40,7 @@ type Kubernetes struct {
Namespaces map[string]bool Namespaces map[string]bool
podMode string podMode string
endpointNameMode bool endpointNameMode bool
Fallthrough bool Fallthrough *[]string // nil = disabled, empty = all zones, o/w zones
ttl uint32 ttl uint32
primaryZoneIndex int primaryZoneIndex int

View file

@ -172,12 +172,8 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
} }
return nil, opts, c.ArgErr() return nil, opts, c.ArgErr()
case "fallthrough": case "fallthrough":
args := c.RemainingArgs() zones := c.RemainingArgs()
if len(args) == 0 { k8s.Fallthrough = &zones
k8s.Fallthrough = true
continue
}
return nil, opts, c.ArgErr()
case "upstream": case "upstream":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {

View file

@ -19,7 +19,7 @@ func TestKubernetesParse(t *testing.T) {
expectedResyncPeriod time.Duration // expected resync period value expectedResyncPeriod time.Duration // expected resync period value
expectedLabelSelector string // expected label selector value expectedLabelSelector string // expected label selector value
expectedPodMode string expectedPodMode string
expectedFallthrough bool expectedFallthrough *[]string
expectedUpstreams []string expectedUpstreams []string
}{ }{
// positive // positive
@ -32,7 +32,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -44,7 +44,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -57,7 +57,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -71,7 +71,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -85,7 +85,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -99,7 +99,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -113,7 +113,7 @@ func TestKubernetesParse(t *testing.T) {
30 * time.Second, 30 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -127,7 +127,7 @@ func TestKubernetesParse(t *testing.T) {
15 * time.Minute, 15 * time.Minute,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -141,7 +141,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"environment=prod", "environment=prod",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -155,7 +155,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -173,7 +173,7 @@ func TestKubernetesParse(t *testing.T) {
15 * time.Minute, 15 * time.Minute,
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
podModeDisabled, podModeDisabled,
true, &[]string{},
nil, nil,
}, },
// negative // negative
@ -188,7 +188,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -202,7 +202,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -216,7 +216,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Minute, 0 * time.Minute,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -230,7 +230,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -244,7 +244,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -258,7 +258,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
{ {
@ -272,7 +272,7 @@ func TestKubernetesParse(t *testing.T) {
0 * time.Second, 0 * time.Second,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
// pods disabled // pods disabled
@ -287,7 +287,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
// pods insecure // pods insecure
@ -302,7 +302,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeInsecure, podModeInsecure,
false, nil,
nil, nil,
}, },
// pods verified // pods verified
@ -317,7 +317,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeVerified, podModeVerified,
false, nil,
nil, nil,
}, },
// pods invalid // pods invalid
@ -332,22 +332,22 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeVerified, podModeVerified,
false, nil,
nil, nil,
}, },
// fallthrough invalid // fallthrough with zones
{ {
`kubernetes coredns.local { `kubernetes coredns.local {
fallthrough junk fallthrough ip6.arpa inaddr.arpa foo.com
}`, }`,
true, false,
"rong argument count", "rong argument count",
-1, 1,
0, 0,
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, &[]string{"ip6.arpa", "inaddr.arpa", "foo.com"},
nil, nil,
}, },
// Valid upstream // Valid upstream
@ -362,7 +362,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
[]string{"13.14.15.16:53"}, []string{"13.14.15.16:53"},
}, },
// Invalid upstream // Invalid upstream
@ -377,7 +377,7 @@ func TestKubernetesParse(t *testing.T) {
defaultResyncPeriod, defaultResyncPeriod,
"", "",
podModeDisabled, podModeDisabled,
false, nil,
nil, nil,
}, },
} }
@ -444,9 +444,23 @@ func TestKubernetesParse(t *testing.T) {
// fallthrough // fallthrough
foundFallthrough := k8sController.Fallthrough foundFallthrough := k8sController.Fallthrough
if foundFallthrough != test.expectedFallthrough { if foundFallthrough != nil {
failed := false
if test.expectedFallthrough == nil {
failed = true
} else if len(*foundFallthrough) != len(*test.expectedFallthrough) {
failed = true
} else {
for i := range *foundFallthrough {
if (*foundFallthrough)[i] != (*test.expectedFallthrough)[i] {
failed = true
}
}
}
if failed {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input) t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input)
} }
}
// upstream // upstream
foundUpstreams := k8sController.Proxy.Upstreams foundUpstreams := k8sController.Proxy.Upstreams
if test.expectedUpstreams == nil { if test.expectedUpstreams == nil {

View file

@ -83,6 +83,21 @@ func NextOrFailure(name string, next Handler, ctx context.Context, w dns.Respons
return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found")) return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found"))
} }
// Fallthrough handles the fallthrough logic used in plugins that support it
func Fallthrough(ftzones *[]string, qname string) bool {
if ftzones == nil {
return false
}
if len(*ftzones) == 0 {
return true
}
zone := Zones(*ftzones).Matches(qname)
if zone != "" {
return true
}
return false
}
// ClientWrite returns true if the response has been written to the client. // ClientWrite returns true if the response has been written to the client.
// Each plugin to adhire to this protocol. // Each plugin to adhire to this protocol.
func ClientWrite(rcode int) bool { func ClientWrite(rcode int) bool {

View file

@ -1 +1,21 @@
package plugin package plugin
import "testing"
func TestFallthrough(t *testing.T) {
if Fallthrough(nil, "foo.com.") {
t.Errorf("Expected false, got true for nil fallthrough")
}
if !Fallthrough(&[]string{}, "foo.net.") {
t.Errorf("Expected true, got false for all zone fallthrough")
}
if Fallthrough(&[]string{"foo.com", "bar.com"}, "foo.net") {
t.Errorf("Expected false, got true for non-matching fallthrough zone")
}
if !Fallthrough(&[]string{"foo.com.", "bar.com."}, "bar.com.") {
t.Errorf("Expected true, got false for matching fallthrough zone")
}
}