mirror of
https://github.com/coredns/coredns.git
synced 2026-08-26 22:57:13 -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.
|
||||
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)
|
||||
state.Req.Question[0].Name = old
|
||||
if err == nil || !b.IsNameError(err) {
|
||||
if err == nil || !b.IsNameError(err) || opt.NoApexFallback {
|
||||
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) {
|
||||
errBackend := context.DeadlineExceeded
|
||||
calls := 0
|
||||
|
||||
@@ -37,6 +37,7 @@ etcd [ZONES...] {
|
||||
endpoint ENDPOINT...
|
||||
credentials USERNAME PASSWORD
|
||||
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
|
||||
file - if the server certificate is not signed by a system-installed CA and client certificate
|
||||
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
|
||||
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.
|
||||
|
||||
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,
|
||||
but does not reduce the size of the first prefix response. 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.
|
||||
the zone-wide fallback. Once all apex records use that layout, enable `no_apex_fallback` to prevent a
|
||||
missing or deleted apex entry from triggering a zone-root scan. The *cache* plugin reduces repeated
|
||||
backend reads for the same DNS question, but does not reduce the size of the first prefix response.
|
||||
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
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ type Etcd struct {
|
||||
Client *etcdcv3.Client
|
||||
MinLeaseTTL uint32 // minimum 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.
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// ServeDNS implements the plugin.Handler interface.
|
||||
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}
|
||||
|
||||
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
|
||||
|
||||
@@ -62,6 +62,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, error) {
|
||||
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
|
||||
case "debug":
|
||||
/* it is a noop now */
|
||||
case "no_apex_fallback":
|
||||
if len(c.RemainingArgs()) != 0 {
|
||||
return &Etcd{}, c.ArgErr()
|
||||
}
|
||||
etc.NoApexFallback = true
|
||||
case "path":
|
||||
if !c.NextArg() {
|
||||
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) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
Reference in New Issue
Block a user