Commit f697b332 authored by Miek Gieben's avatar Miek Gieben Committed by GitHub

return an error for multiple use of some plugins (#1559)

* plugins: Return error for multiple use of some

Return plugin.ErrOnce when a plugin that doesn't support it, is called
mutliple times.

This now adds it for: cache, dnssec, errors, forward, hosts, nsid.
And changes it slightly in kubernetes, pprof, reload, root.

* more tests
parent 5faa9e7b
...@@ -10,6 +10,8 @@ With *cache* enabled, all records except zone transfers and metadata records wil ...@@ -10,6 +10,8 @@ With *cache* enabled, all records except zone transfers and metadata records wil
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream, 3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
database, etc.) is expensive. database, etc.) is expensive.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ txt ~~~ txt
......
...@@ -61,7 +61,13 @@ func setup(c *caddy.Controller) error { ...@@ -61,7 +61,13 @@ func setup(c *caddy.Controller) error {
func cacheParse(c *caddy.Controller) (*Cache, error) { func cacheParse(c *caddy.Controller) (*Cache, error) {
ca := New() ca := New()
j := 0
for c.Next() { for c.Next() {
if j > 0 {
return nil, plugin.ErrOnce
}
j++
// cache [ttl] [zones..] // cache [ttl] [zones..]
origins := make([]string, len(c.ServerBlockKeys)) origins := make([]string, len(c.ServerBlockKeys))
copy(origins, c.ServerBlockKeys) copy(origins, c.ServerBlockKeys)
...@@ -180,9 +186,7 @@ func cacheParse(c *caddy.Controller) (*Cache, error) { ...@@ -180,9 +186,7 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
ca.pcache = cache.New(ca.pcap) ca.pcache = cache.New(ca.pcap)
ca.ncache = cache.New(ca.ncap) ca.ncache = cache.New(ca.ncap)
return ca, nil
} }
return nil, nil return ca, nil
} }
...@@ -20,46 +20,48 @@ func TestSetup(t *testing.T) { ...@@ -20,46 +20,48 @@ func TestSetup(t *testing.T) {
{`cache`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0},
{`cache {}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache {}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0},
{`cache example.nl { {`cache example.nl {
success 10 success 10
}`, false, defaultCap, 10, maxNTTL, maxTTL, 0}, }`, false, defaultCap, 10, maxNTTL, maxTTL, 0},
{`cache example.nl { {`cache example.nl {
success 10 success 10
denial 10 15 denial 10 15
}`, false, 10, 10, 15 * time.Second, maxTTL, 0}, }`, false, 10, 10, 15 * time.Second, maxTTL, 0},
{`cache 25 example.nl { {`cache 25 example.nl {
success 10 success 10
denial 10 15 denial 10 15
}`, false, 10, 10, 15 * time.Second, 25 * time.Second, 0}, }`, false, 10, 10, 15 * time.Second, 25 * time.Second, 0},
{`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0},
{`cache { {`cache {
prefetch 10 prefetch 10
}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 10}, }`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 10},
// fails // fails
{`cache example.nl { {`cache example.nl {
success success
denial 10 15 denial 10 15
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache example.nl { {`cache example.nl {
success 15 success 15
denial aaa denial aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache example.nl { {`cache example.nl {
positive 15 positive 15
negative aaa negative aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, {`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, {`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
positive 0 positive 0
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
positive 0 positive 0
prefetch -1 prefetch -1
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
prefetch 0 blurp prefetch 0 blurp
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
{`cache
cache`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0},
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("dns", test.input) c := caddy.NewTestController("dns", test.input)
......
...@@ -10,6 +10,8 @@ With *dnssec* any reply that doesn't (or can't) do DNSSEC will get signed on the ...@@ -10,6 +10,8 @@ With *dnssec* any reply that doesn't (or can't) do DNSSEC will get signed on the
denial of existence is implemented with NSEC black lies. Using ECDSA as an algorithm is preferred as denial of existence is implemented with NSEC black lies. Using ECDSA as an algorithm is preferred as
this leads to smaller signatures (compared to RSA). NSEC3 is *not* supported. this leads to smaller signatures (compared to RSA). NSEC3 is *not* supported.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ ~~~
...@@ -74,20 +76,3 @@ cluster.local { ...@@ -74,20 +76,3 @@ cluster.local {
} }
} }
~~~ ~~~
## Bugs
Multiple *dnssec* plugins inside one server stanza will silently overwrite earlier ones, here
`example.org` will overwrite the one for `cluster.local`.
~~~
. {
kubernetes cluster.local
dnssec cluster.local {
key file Kcluster.local+013+45129
}
dnssec example.org {
key file Kexample.org.+013+45330
}
}
~~~
...@@ -59,7 +59,14 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) { ...@@ -59,7 +59,14 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
keys := []*DNSKEY{} keys := []*DNSKEY{}
capacity := defaultCap capacity := defaultCap
i := 0
for c.Next() { for c.Next() {
if i > 0 {
return nil, nil, 0, plugin.ErrOnce
}
i++
// dnssec [zones...] // dnssec [zones...]
zones = make([]string, len(c.ServerBlockKeys)) zones = make([]string, len(c.ServerBlockKeys))
copy(zones, c.ServerBlockKeys) copy(zones, c.ServerBlockKeys)
...@@ -69,7 +76,8 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) { ...@@ -69,7 +76,8 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
} }
for c.NextBlock() { for c.NextBlock() {
switch c.Val() {
switch x := c.Val(); x {
case "key": case "key":
k, e := keyParse(c) k, e := keyParse(c)
if e != nil { if e != nil {
...@@ -86,6 +94,8 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) { ...@@ -86,6 +94,8 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
return nil, nil, 0, err return nil, nil, 0, err
} }
capacity = cacheCap capacity = cacheCap
default:
return nil, nil, 0, c.Errf("unknown property '%s'", x)
} }
} }
......
...@@ -61,6 +61,8 @@ func TestSetupDnssec(t *testing.T) { ...@@ -61,6 +61,8 @@ func TestSetupDnssec(t *testing.T) {
key file key file
}`, true, []string{"example.org."}, nil, defaultCap, "argument count", }`, true, []string{"example.org."}, nil, defaultCap, "argument count",
}, },
{`dnssec
dnssec`, true, nil, nil, defaultCap, ""},
} }
for i, test := range tests { for i, test := range tests {
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
Any errors encountered during the query processing will be printed to standard output. Any errors encountered during the query processing will be printed to standard output.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ ~~~
......
...@@ -37,7 +37,13 @@ func setup(c *caddy.Controller) error { ...@@ -37,7 +37,13 @@ func setup(c *caddy.Controller) error {
func errorsParse(c *caddy.Controller) (errorHandler, error) { func errorsParse(c *caddy.Controller) (errorHandler, error) {
handler := errorHandler{} handler := errorHandler{}
i := 0
for c.Next() { for c.Next() {
if i > 0 {
return handler, plugin.ErrOnce
}
i++
args := c.RemainingArgs() args := c.RemainingArgs()
switch len(args) { switch len(args) {
case 0: case 0:
......
...@@ -12,21 +12,14 @@ func TestErrorsParse(t *testing.T) { ...@@ -12,21 +12,14 @@ func TestErrorsParse(t *testing.T) {
shouldErr bool shouldErr bool
expectedErrorHandler errorHandler expectedErrorHandler errorHandler
}{ }{
{`errors`, false, errorHandler{ {`errors`, false, errorHandler{LogFile: "stdout"}},
LogFile: "stdout", {`errors stdout`, false, errorHandler{LogFile: "stdout"}},
}}, {`errors errors.txt`, true, errorHandler{LogFile: ""}},
{`errors stdout`, false, errorHandler{ {`errors visible`, true, errorHandler{LogFile: ""}},
LogFile: "stdout", {`errors { log visible }`, true, errorHandler{LogFile: "stdout"}},
}}, {`errors
{`errors errors.txt`, true, errorHandler{ errors `, true, errorHandler{LogFile: "stdout"}},
LogFile: "", {`errors a b`, true, errorHandler{LogFile: ""}},
}},
{`errors visible`, true, errorHandler{
LogFile: "",
}},
{`errors { log visible }`, true, errorHandler{
LogFile: "stdout",
}},
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("dns", test.inputErrorsRules) c := caddy.NewTestController("dns", test.inputErrorsRules)
......
...@@ -19,6 +19,8 @@ is performed and upstreams will always be considered healthy. ...@@ -19,6 +19,8 @@ is performed and upstreams will always be considered healthy.
When *all* upstreams are down it assumes health checking as a mechanism has failed and will try to When *all* upstreams are down it assumes health checking as a mechanism has failed and will try to
connect to a random upstream (which may or may not work). connect to a random upstream (which may or may not work).
This plugin can only be used once per Server Block.
## Syntax ## Syntax
In its most basic form, a simple forwarder uses this syntax: In its most basic form, a simple forwarder uses this syntax:
......
...@@ -84,7 +84,13 @@ func parseForward(c *caddy.Controller) (*Forward, error) { ...@@ -84,7 +84,13 @@ func parseForward(c *caddy.Controller) (*Forward, error) {
protocols := map[int]int{} protocols := map[int]int{}
i := 0
for c.Next() { for c.Next() {
if i > 0 {
return nil, plugin.ErrOnce
}
i++
if !c.Args(&f.from) { if !c.Args(&f.from) {
return f, c.ArgErr() return f, c.ArgErr()
} }
......
...@@ -30,6 +30,8 @@ func TestSetup(t *testing.T) { ...@@ -30,6 +30,8 @@ func TestSetup(t *testing.T) {
// negative // negative
{"forward . a27.0.0.1", true, "", nil, 0, false, "not an IP"}, {"forward . a27.0.0.1", true, "", nil, 0, false, "not an IP"},
{"forward . 127.0.0.1 {\nblaatl\n}\n", true, "", nil, 0, false, "unknown property"}, {"forward . 127.0.0.1 {\nblaatl\n}\n", true, "", nil, 0, false, "unknown property"},
{`forward . ::1
forward com ::2`, true, "", nil, 0, false, "plugin"},
} }
for i, test := range tests { for i, test := range tests {
......
...@@ -11,6 +11,8 @@ file that exists on disk. It checks the file for changes and updates the zones a ...@@ -11,6 +11,8 @@ file that exists on disk. It checks the file for changes and updates the zones a
plugin only supports A, AAAA, and PTR records. The hosts plugin can be used with readily plugin only supports A, AAAA, and PTR records. The hosts plugin can be used with readily
available hosts files that block access to advertising servers. available hosts files that block access to advertising servers.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ ~~~
......
...@@ -69,7 +69,13 @@ func hostsParse(c *caddy.Controller) (Hosts, error) { ...@@ -69,7 +69,13 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
config := dnsserver.GetConfig(c) config := dnsserver.GetConfig(c)
inline := []string{} inline := []string{}
i := 0
for c.Next() { for c.Next() {
if i > 0 {
return h, plugin.ErrOnce
}
i++
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) >= 1 { if len(args) >= 1 {
h.path = args[0] h.path = args[0]
......
...@@ -57,6 +57,15 @@ func TestHostsParse(t *testing.T) { ...@@ -57,6 +57,15 @@ func TestHostsParse(t *testing.T) {
}`, }`,
false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, fall.Root, false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, fall.Root,
}, },
{
`hosts /etc/hosts {
fallthrough
}
hosts /etc/hosts {
fallthrough
}`,
true, "/etc/hosts", nil, fall.Root,
},
} }
for i, test := range tests { for i, test := range tests {
......
...@@ -16,6 +16,8 @@ to deploy CoreDNS in Kubernetes](https://github.com/coredns/deployment/tree/mast ...@@ -16,6 +16,8 @@ to deploy CoreDNS in Kubernetes](https://github.com/coredns/deployment/tree/mast
[stubDomains and upstreamNameservers](http://blog.kubernetes.io/2017/04/configuring-private-dns-zones-upstream-nameservers-kubernetes.html) [stubDomains and upstreamNameservers](http://blog.kubernetes.io/2017/04/configuring-private-dns-zones-upstream-nameservers-kubernetes.html)
are implemented via the *proxy* plugin and kubernetes *upstream*. See example below. are implemented via the *proxy* plugin and kubernetes *upstream*. See example below.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ ~~~
......
...@@ -71,12 +71,18 @@ func (k *Kubernetes) RegisterKubeCache(c *caddy.Controller) { ...@@ -71,12 +71,18 @@ func (k *Kubernetes) RegisterKubeCache(c *caddy.Controller) {
} }
func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
var k8s *Kubernetes var (
var err error k8s *Kubernetes
for i := 1; c.Next(); i++ { err error
if i > 1 { )
return nil, fmt.Errorf("only one kubernetes section allowed per server block")
i := 0
for c.Next() {
if i > 0 {
return nil, plugin.ErrOnce
} }
i++
k8s, err = ParseStanza(c) k8s, err = ParseStanza(c)
if err != nil { if err != nil {
return k8s, err return k8s, err
......
...@@ -388,7 +388,7 @@ func TestKubernetesParse(t *testing.T) { ...@@ -388,7 +388,7 @@ func TestKubernetesParse(t *testing.T) {
`kubernetes coredns.local `kubernetes coredns.local
kubernetes cluster.local`, kubernetes cluster.local`,
true, true,
"only one kubernetes section allowed per server block", "this plugin",
-1, -1,
0, 0,
defaultResyncPeriod, defaultResyncPeriod,
......
...@@ -6,9 +6,12 @@ ...@@ -6,9 +6,12 @@
## Description ## Description
This plugin implements RFC 5001 and adds an EDNS0 OPT resource record to replies that uniquely This plugin implements [RFC 5001](https://tools.ietf.org/html/rfc5001) and adds an EDNS0 OPT
identify the server. This is useful in anycast setups to see which server was responsible for resource record to replies that uniquely identify the server. This is useful in anycast setups to
generating the reply and for debugging. see which server was responsible for generating the reply and for debugging.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
...@@ -48,3 +51,7 @@ And now a client with NSID support will see an OPT record with the NSID option: ...@@ -48,3 +51,7 @@ And now a client with NSID support will see an OPT record with the NSID option:
;; QUESTION SECTION: ;; QUESTION SECTION:
;whoami.example.org. IN A ;whoami.example.org. IN A
~~~ ~~~
## Also See
[RFC 5001](https://tools.ietf.org/html/rfc5001)
...@@ -36,13 +36,16 @@ func nsidParse(c *caddy.Controller) (string, error) { ...@@ -36,13 +36,16 @@ func nsidParse(c *caddy.Controller) (string, error) {
if err != nil { if err != nil {
nsid = "localhost" nsid = "localhost"
} }
i := 0
for c.Next() { for c.Next() {
if i > 0 {
return nsid, plugin.ErrOnce
}
i++
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) > 0 {
return nsid, nil nsid = strings.Join(args, " ")
} }
nsid = strings.Join(args, " ")
return nsid, nil
} }
return nsid, nil return nsid, nil
} }
...@@ -19,18 +19,12 @@ func TestSetupNsid(t *testing.T) { ...@@ -19,18 +19,12 @@ func TestSetupNsid(t *testing.T) {
expectedData string expectedData string
expectedErrContent string // substring from the expected error. Empty for positive cases. expectedErrContent string // substring from the expected error. Empty for positive cases.
}{ }{
{ {`nsid`, false, defaultNsid, ""},
`nsid`, false, defaultNsid, "", {`nsid "ps0"`, false, "ps0", ""},
}, {`nsid "worker1"`, false, "worker1", ""},
{ {`nsid "tf 2"`, false, "tf 2", ""},
`nsid "ps0"`, false, "ps0", "", {`nsid
}, nsid`, true, "", "plugin"},
{
`nsid "worker1"`, false, "worker1", "",
},
{
`nsid "tf 2"`, false, "tf 2", "",
},
} }
for i, test := range tests { for i, test := range tests {
......
...@@ -104,3 +104,6 @@ const Namespace = "coredns" ...@@ -104,3 +104,6 @@ const Namespace = "coredns"
// TimeBuckets is based on Prometheus client_golang prometheus.DefBuckets // TimeBuckets is based on Prometheus client_golang prometheus.DefBuckets
var TimeBuckets = prometheus.ExponentialBuckets(0.00025, 2, 16) // from 0.25ms to 8 seconds var TimeBuckets = prometheus.ExponentialBuckets(0.00025, 2, 16) // from 0.25ms to 8 seconds
// ErrOnce is returned when a plugin doesn't support multiple setups per server.
var ErrOnce = errors.New("this plugin can only be used once per Server Block")
...@@ -16,6 +16,8 @@ For more information, please see [Go's pprof ...@@ -16,6 +16,8 @@ For more information, please see [Go's pprof
documentation](https://golang.org/pkg/net/http/pprof/) and read documentation](https://golang.org/pkg/net/http/pprof/) and read
[Profiling Go Programs](https://blog.golang.org/profiling-go-programs). [Profiling Go Programs](https://blog.golang.org/profiling-go-programs).
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ ~~~
......
...@@ -19,12 +19,15 @@ func init() { ...@@ -19,12 +19,15 @@ func init() {
} }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
found := false
h := &handler{addr: defaultAddr} h := &handler{addr: defaultAddr}
i := 0
for c.Next() { for c.Next() {
if found { if i > 0 {
return plugin.Error("pprof", c.Err("pprof can only be specified once")) return plugin.Error("pprof", plugin.ErrOnce)
} }
i++
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 1 { if len(args) == 1 {
h.addr = args[0] h.addr = args[0]
...@@ -39,7 +42,6 @@ func setup(c *caddy.Controller) error { ...@@ -39,7 +42,6 @@ func setup(c *caddy.Controller) error {
if c.NextBlock() { if c.NextBlock() {
return plugin.Error("pprof", c.ArgErr()) return plugin.Error("pprof", c.ArgErr())
} }
found = true
} }
pprofOnce.Do(func() { pprofOnce.Do(func() {
......
...@@ -26,6 +26,8 @@ reloads are graceful, and can be disabled by setting the jitter to `0s`. ...@@ -26,6 +26,8 @@ reloads are graceful, and can be disabled by setting the jitter to `0s`.
Jitter is re-calculated whenever the Corefile is reloaded. Jitter is re-calculated whenever the Corefile is reloaded.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ txt ~~~ txt
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
The default root is the current working directory of CoreDNS. The *root* plugin allows you to change The default root is the current working directory of CoreDNS. The *root* plugin allows you to change
this. A relative root path is relative to the current working directory. this. A relative root path is relative to the current working directory.
This plugin can only be used once per Server Block.
## Syntax ## Syntax
~~~ txt ~~~ txt
......
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
## Description ## Description
The route53 plugin is useful for serving zones from resource record sets in AWS route53. The route53 plugin is useful for serving zones from resource record sets in AWS route53. This plugin
This plugin only supports A and AAAA records. The route53 plugin can be used when only supports A and AAAA records. The route53 plugin can be used when coredns is deployed on AWS.
coredns is deployed on AWS.
## Syntax ## Syntax
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment