middleware/{file, auto}: resolve external CNAMEs

Do the same thing as in etcd and give the option of externally resolving
CNAME. This is needed when CoreDNS is a proxy as well is serving zones.
This commit is contained in:
Miek Gieben 2016-11-09 21:26:49 +00:00
parent a8287bb04d
commit 0919216d3c
10 changed files with 125 additions and 7 deletions

View file

@ -13,6 +13,8 @@ zonefile. New zones or changed zone are automatically picked up from disk.
~~~ ~~~
auto [ZONES...] { auto [ZONES...] {
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]] directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
no_reload
upstream ADDRESS...
} }
~~~ ~~~
@ -26,6 +28,10 @@ are used.
name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often
CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds. CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds.
The minimum value is 1 second. The minimum value is 1 second.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
All directives from the *file* middleware are supported. Note that *auto* will load all zones found, All directives from the *file* middleware are supported. Note that *auto* will load all zones found,
even though the directive might only receive queries for a specific zone. I.e: even though the directive might only receive queries for a specific zone. I.e:

View file

@ -9,6 +9,7 @@ import (
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file" "github.com/miekg/coredns/middleware/file"
"github.com/miekg/coredns/middleware/metrics" "github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/request" "github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -33,6 +34,7 @@ type (
// In the future this should be something like ZoneMeta that contains all this stuff. // In the future this should be something like ZoneMeta that contains all this stuff.
transferTo []string transferTo []string
noReload bool noReload bool
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
duration time.Duration duration time.Duration
} }

View file

@ -2,6 +2,7 @@ package auto
import ( import (
"log" "log"
"net"
"os" "os"
"path" "path"
"regexp" "regexp"
@ -12,6 +13,7 @@ import (
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file" "github.com/miekg/coredns/middleware/file"
"github.com/miekg/coredns/middleware/metrics" "github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/coredns/middleware/proxy"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
@ -142,6 +144,19 @@ func autoParse(c *caddy.Controller) (Auto, error) {
case "no_reload": case "no_reload":
a.loader.noReload = true a.loader.noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return a, false, c.ArgErr()
}
for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i])
if e != nil && p == "" {
args[i] = h + ":53"
}
}
a.loader.Proxy = proxy.New(args)
default: default:
t, _, e := file.TransferParse(c, false) t, _, e := file.TransferParse(c, false)
if e != nil { if e != nil {

View file

@ -35,8 +35,8 @@ etcd [ZONES...] {
under the *first* zone specified. under the *first* zone specified.
* **PATH** the path inside etcd. Defaults to "/skydns". * **PATH** the path inside etcd. Defaults to "/skydns".
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397". * **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs) * `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add pointing to external names. If you want CoreDNS also to act as a proxy for clients, you'll need to add
the proxy middleware. the proxy middleware.
* `tls` followed the cert, key and the CA's cert filenames. * `tls` followed the cert, key and the CA's cert filenames.
* `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the * `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the

View file

@ -9,7 +9,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/mholt/caddy"
"github.com/miekg/coredns/middleware/etcd/msg" "github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/coredns/middleware/pkg/dnsrecorder" "github.com/miekg/coredns/middleware/pkg/dnsrecorder"
"github.com/miekg/coredns/middleware/pkg/singleflight" "github.com/miekg/coredns/middleware/pkg/singleflight"
@ -17,6 +16,7 @@ import (
"github.com/miekg/coredns/middleware/test" "github.com/miekg/coredns/middleware/test"
etcdc "github.com/coreos/etcd/client" etcdc "github.com/coreos/etcd/client"
"github.com/mholt/caddy"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/context" "golang.org/x/net/context"
) )

View file

@ -27,6 +27,7 @@ TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]]
file DBFILE [ZONES... ] { file DBFILE [ZONES... ] {
transfer to ADDRESS... transfer to ADDRESS...
no_reload no_reload
upstream ADDRESS...
} }
~~~ ~~~
@ -36,6 +37,8 @@ file DBFILE [ZONES... ] {
When an address is specified a notify message will be send whenever the zone is reloaded. When an address is specified a notify message will be send whenever the zone is reloaded.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the * `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior. file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
## Examples ## Examples

View file

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/miekg/coredns/middleware/pkg/dnsrecorder" "github.com/miekg/coredns/middleware/pkg/dnsrecorder"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/middleware/test" "github.com/miekg/coredns/middleware/test"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -68,6 +69,12 @@ var cnameTestCases = []test.Case{
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."), test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."),
}, },
}, },
{
Qname: "dangling.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("dangling.example.org. 1800 IN CNAME foo.example.org."),
},
},
{ {
Qname: "www3.example.org.", Qtype: dns.TypeA, Qname: "www3.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{ Answer: []dns.RR{
@ -80,6 +87,59 @@ var cnameTestCases = []test.Case{
}, },
} }
func TestLookupCNAMEExternal(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin")
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
zone.Proxy = proxy.New([]string{"8.8.8.8:53"}) // TODO(point to local instance)
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range exernalTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
var exernalTestCases = []test.Case{
{
Qname: "external.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("external.example.org. 1800 CNAME www.example.net."),
},
},
}
const dbExampleCNAME = ` const dbExampleCNAME = `
$TTL 30M $TTL 30M
$ORIGIN example.org. $ORIGIN example.org.
@ -95,4 +155,5 @@ www3 IN CNAME www2
www2 IN CNAME www1 www2 IN CNAME www1
www1 IN CNAME www www1 IN CNAME www
www IN CNAME a www IN CNAME a
dangling IN CNAME foo` dangling IN CNAME foo
external IN CNAME www.example.net.`

View file

@ -2,6 +2,7 @@ package file
import ( import (
"github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -118,7 +119,7 @@ func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR,
// Found entire name. // Found entire name.
if found && shot { if found && shot {
// DNAME... // DNAME...?
if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME { if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME {
return z.searchCNAME(elem, rrs, qtype, do) return z.searchCNAME(elem, rrs, qtype, do)
} }
@ -260,8 +261,16 @@ func (z *Zone) searchCNAME(elem *tree.Elem, rrs []dns.RR, qtype uint16, do bool)
} }
} }
elem, _ = z.Tree.Search(rrs[0].(*dns.CNAME).Target) targetName := rrs[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
println(targetName)
if elem == nil { if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
println(targetName, "is not a child of", z.origin)
}
st := request.Request{}
z.Proxy.Lookup(st, targetName, qtype)
return rrs, nil, nil, Success return rrs, nil, nil, Success
} }
@ -279,8 +288,12 @@ Redo:
rrs = append(rrs, sigs...) rrs = append(rrs, sigs...)
} }
} }
elem, _ = z.Tree.Search(cname[0].(*dns.CNAME).Target) targetName := cname[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
if elem == nil { if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
println(targetName, "is not a child of", z.origin)
}
return rrs, nil, nil, Success return rrs, nil, nil, Success
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/proxy"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
@ -90,6 +91,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
} }
noReload := false noReload := false
prxy := proxy.Proxy{}
for c.NextBlock() { for c.NextBlock() {
t, _, e := TransferParse(c, false) t, _, e := TransferParse(c, false)
if e != nil { if e != nil {
@ -98,6 +100,19 @@ func fileParse(c *caddy.Controller) (Zones, error) {
switch c.Val() { switch c.Val() {
case "no_reload": case "no_reload":
noReload = true noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return Zones{}, c.ArgErr()
}
for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i])
if e != nil && p == "" {
args[i] = h + ":53"
}
}
prxy = proxy.New(args)
} }
for _, origin := range origins { for _, origin := range origins {
@ -105,6 +120,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
z[origin].TransferTo = append(z[origin].TransferTo, t...) z[origin].TransferTo = append(z[origin].TransferTo, t...)
} }
z[origin].NoReload = noReload z[origin].NoReload = noReload
z[origin].Proxy = prxy
} }
} }
} }

View file

@ -9,6 +9,7 @@ import (
"sync" "sync"
"github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/request" "github.com/miekg/coredns/request"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
@ -31,6 +32,7 @@ type Zone struct {
NoReload bool NoReload bool
reloadMu sync.RWMutex reloadMu sync.RWMutex
ReloadShutdown chan bool ReloadShutdown chan bool
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
} }
// Apex contains the apex records of a zone: SOA, NS and their potential signatures. // Apex contains the apex records of a zone: SOA, NS and their potential signatures.