mirror of
https://github.com/coredns/coredns.git
synced 2026-08-28 07:37:04 -04:00
plugin/etcd: allow disabling legacy apex fallback (#8468)
Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
This commit is contained in:
@@ -37,4 +37,7 @@ type ServiceBackend interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options are extra options that can be specified for a lookup.
|
// Options are extra options that can be specified for a lookup.
|
||||||
type Options struct{}
|
type Options struct {
|
||||||
|
// NoApexFallback disables the legacy zone-root lookup when apex records are absent.
|
||||||
|
NoApexFallback bool
|
||||||
|
}
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ func checkForApex(ctx context.Context, b ServiceBackend, zone string, state requ
|
|||||||
|
|
||||||
services, err := b.Services(ctx, state, false, opt)
|
services, err := b.Services(ctx, state, false, opt)
|
||||||
state.Req.Question[0].Name = old
|
state.Req.Question[0].Name = old
|
||||||
if err == nil || !b.IsNameError(err) {
|
if err == nil || !b.IsNameError(err) || opt.NoApexFallback {
|
||||||
return services, err
|
return services, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -604,6 +604,37 @@ func TestCheckForApexFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckForApexFallbackDisabled(t *testing.T) {
|
||||||
|
errName := errors.New("name not found")
|
||||||
|
calls := 0
|
||||||
|
b := &mockBackend{
|
||||||
|
mockServices: func(_ctx context.Context, state request.Request, _exact bool, _opt Options) ([]msg.Service, error) {
|
||||||
|
calls++
|
||||||
|
if state.QName() != "apex.dns.example.org." {
|
||||||
|
t.Fatalf("unexpected fallback lookup for %s", state.QName())
|
||||||
|
}
|
||||||
|
return nil, errName
|
||||||
|
},
|
||||||
|
mockNameError: func(err error) bool { return errors.Is(err, errName) },
|
||||||
|
}
|
||||||
|
req := new(dns.Msg)
|
||||||
|
req.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
state := request.Request{Req: req, W: &test.ResponseWriter{}}
|
||||||
|
services, err := checkForApex(context.Background(), b, "example.org.", state, Options{NoApexFallback: true})
|
||||||
|
if !errors.Is(err, errName) {
|
||||||
|
t.Fatalf("expected name error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(services) != 0 {
|
||||||
|
t.Fatalf("expected no services, got %+v", services)
|
||||||
|
}
|
||||||
|
if calls != 1 {
|
||||||
|
t.Fatalf("expected one apex lookup, got %d calls", calls)
|
||||||
|
}
|
||||||
|
if state.QName() != "example.org." {
|
||||||
|
t.Fatalf("query name was not restored: %s", state.QName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckForApexBackendError(t *testing.T) {
|
func TestCheckForApexBackendError(t *testing.T) {
|
||||||
errBackend := context.DeadlineExceeded
|
errBackend := context.DeadlineExceeded
|
||||||
calls := 0
|
calls := 0
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ etcd [ZONES...] {
|
|||||||
endpoint ENDPOINT...
|
endpoint ENDPOINT...
|
||||||
credentials USERNAME PASSWORD
|
credentials USERNAME PASSWORD
|
||||||
tls CERT KEY CACERT
|
tls CERT KEY CACERT
|
||||||
|
no_apex_fallback
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
@@ -55,6 +56,10 @@ etcd [ZONES...] {
|
|||||||
* three arguments - path to cert PEM file, path to client private key PEM file, path to CA PEM
|
* three arguments - path to cert PEM file, path to client private key PEM file, path to CA PEM
|
||||||
file - if the server certificate is not signed by a system-installed CA and client certificate
|
file - if the server certificate is not signed by a system-installed CA and client certificate
|
||||||
is needed.
|
is needed.
|
||||||
|
* `no_apex_fallback` disables the legacy zone-root lookup used by address queries and apex-existence
|
||||||
|
checks at a configured zone apex. When `apex.dns.ZONE` does not exist, the lookup returns a name
|
||||||
|
error instead of issuing a prefix range over the complete zone subtree. Migrate zone-apex address
|
||||||
|
records to the `dns/apex` layout before enabling this option.
|
||||||
|
|
||||||
CoreDNS sets the minimum TLS version to TLS 1.2. The maximum TLS version, TLS 1.2 cipher suites, and
|
CoreDNS sets the minimum TLS version to TLS 1.2. The maximum TLS version, TLS 1.2 cipher suites, and
|
||||||
key exchange mechanisms use the Go `crypto/tls` defaults.
|
key exchange mechanisms use the Go `crypto/tls` defaults.
|
||||||
@@ -93,10 +98,11 @@ zone's root prefix for compatibility with older SkyDNS layouts. On a large zone,
|
|||||||
the entire zone subtree.
|
the entire zone subtree.
|
||||||
|
|
||||||
Store zone-apex address records below the `dns/apex` path, as shown in the examples below, to avoid
|
Store zone-apex address records below the `dns/apex` path, as shown in the examples below, to avoid
|
||||||
the zone-wide fallback. The *cache* plugin reduces repeated backend reads for the same DNS question,
|
the zone-wide fallback. Once all apex records use that layout, enable `no_apex_fallback` to prevent a
|
||||||
but does not reduce the size of the first prefix response. Use the *log* plugin to identify the client
|
missing or deleted apex entry from triggering a zone-root scan. The *cache* plugin reduces repeated
|
||||||
and question name that trigger a lookup, and `coredns_dns_requests_total` from the *prometheus* plugin
|
backend reads for the same DNS question, but does not reduce the size of the first prefix response.
|
||||||
to measure query volume by zone and type.
|
Use the *log* plugin to identify the client and question name that trigger a lookup, and
|
||||||
|
`coredns_dns_requests_total` from the *prometheus* plugin to measure query volume by zone and type.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ type Etcd struct {
|
|||||||
Client *etcdcv3.Client
|
Client *etcdcv3.Client
|
||||||
MinLeaseTTL uint32 // minimum TTL for lease-based records
|
MinLeaseTTL uint32 // minimum TTL for lease-based records
|
||||||
MaxLeaseTTL uint32 // maximum TTL for lease-based records
|
MaxLeaseTTL uint32 // maximum TTL for lease-based records
|
||||||
|
// NoApexFallback disables the legacy zone-root lookup when apex.dns records are absent.
|
||||||
|
NoApexFallback bool
|
||||||
|
|
||||||
endpoints []string // Stored here as well, to aid in testing.
|
endpoints []string // Stored here as well, to aid in testing.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// ServeDNS implements the plugin.Handler interface.
|
// ServeDNS implements the plugin.Handler interface.
|
||||||
func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
opt := plugin.Options{}
|
opt := plugin.Options{NoApexFallback: e.NoApexFallback}
|
||||||
state := request.Request{W: w, Req: r}
|
state := request.Request{W: w, Req: r}
|
||||||
|
|
||||||
zone := plugin.Zones(e.Zones).Matches(state.Name())
|
zone := plugin.Zones(e.Zones).Matches(state.Name())
|
||||||
|
|||||||
@@ -352,4 +352,46 @@ func TestLookup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLookupNoApexFallback(t *testing.T) {
|
||||||
|
etc := newEtcdPlugin()
|
||||||
|
etc.Zones = []string{"strict.test."}
|
||||||
|
|
||||||
|
child := &msg.Service{Host: "10.0.0.1", Key: "x.child.strict.test."}
|
||||||
|
set(t, etc, child.Key, 0, child)
|
||||||
|
defer delete(t, etc, child.Key)
|
||||||
|
|
||||||
|
query := func() *dns.Msg {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("strict.test.", dns.TypeA)
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
if _, err := etc.ServeDNS(ctxt, rec, m); err != nil {
|
||||||
|
t.Fatalf("unexpected lookup error: %v", err)
|
||||||
|
}
|
||||||
|
return rec.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy := query()
|
||||||
|
if legacy.Rcode != dns.RcodeSuccess || len(legacy.Answer) != 1 {
|
||||||
|
t.Fatalf("legacy fallback returned rcode %s with %d answers", dns.RcodeToString[legacy.Rcode], len(legacy.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
etc.NoApexFallback = true
|
||||||
|
strict := query()
|
||||||
|
if strict.Rcode != dns.RcodeNameError || len(strict.Answer) != 0 {
|
||||||
|
t.Fatalf("disabled fallback returned rcode %s with %d answers", dns.RcodeToString[strict.Rcode], len(strict.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
apex := &msg.Service{Host: "192.0.2.1", Key: "x.apex.dns.strict.test."}
|
||||||
|
set(t, etc, apex.Key, 0, apex)
|
||||||
|
defer delete(t, etc, apex.Key)
|
||||||
|
|
||||||
|
strictWithApex := query()
|
||||||
|
if strictWithApex.Rcode != dns.RcodeSuccess || len(strictWithApex.Answer) != 1 {
|
||||||
|
t.Fatalf("dedicated apex lookup returned rcode %s with %d answers", dns.RcodeToString[strictWithApex.Rcode], len(strictWithApex.Answer))
|
||||||
|
}
|
||||||
|
if got := strictWithApex.Answer[0].String(); got != "strict.test.\t300\tIN\tA\t192.0.2.1" {
|
||||||
|
t.Fatalf("unexpected apex answer: %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ctxt context.Context
|
var ctxt context.Context
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, error) {
|
|||||||
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
|
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
|
||||||
case "debug":
|
case "debug":
|
||||||
/* it is a noop now */
|
/* it is a noop now */
|
||||||
|
case "no_apex_fallback":
|
||||||
|
if len(c.RemainingArgs()) != 0 {
|
||||||
|
return &Etcd{}, c.ArgErr()
|
||||||
|
}
|
||||||
|
etc.NoApexFallback = true
|
||||||
case "path":
|
case "path":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return &Etcd{}, c.ArgErr()
|
return &Etcd{}, c.ArgErr()
|
||||||
|
|||||||
@@ -162,6 +162,38 @@ func TestSetupEtcd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetupNoApexFallback(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "default", input: `etcd`, expected: false},
|
||||||
|
{name: "disabled", input: "etcd {\nno_apex_fallback\n}", expected: true},
|
||||||
|
{name: "reject arguments", input: "etcd {\nno_apex_fallback unexpected\n}", wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
c := caddy.NewTestController("dns", tc.input)
|
||||||
|
etcd, err := etcdParse(c)
|
||||||
|
if tc.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if etcd.NoApexFallback != tc.expected {
|
||||||
|
t.Fatalf("NoApexFallback = %t, want %t", etcd.NoApexFallback, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseTTL(t *testing.T) {
|
func TestParseTTL(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
|
|||||||
Reference in New Issue
Block a user