diff --git a/README.md b/README.md index e352b56c4..e8c1dea5e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ [![Code Coverage](https://img.shields.io/codecov/c/github/coredns/coredns/master.svg?style=flat-square)](https://codecov.io/github/coredns/coredns?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/coredns/coredns?style=flat-square)](https://goreportcard.com/report/coredns/coredns) -CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has the -same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type plugin for -Caddy. CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception level project. +CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has +the same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type +plugin for Caddy. CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception +level project. CoreDNS is the successor to [SkyDNS](https://github.com/skynetservices/skydns). SkyDNS is a thin layer that exposes services in etcd in the DNS. CoreDNS builds on this idea and is a generic DNS @@ -16,6 +17,11 @@ server that can talk to multiple backends (etcd, kubernetes, etc.). CoreDNS aims to be a fast and flexible DNS server. The keyword here is *flexible*: with CoreDNS you are able to do what you want with your DNS data. And if not: write some middleware! +CoreDNS can listen for DNS request coming in over UDP/TCP (go'old DNS), TLS +([RFC 7858](https://tools.ietf.org/html/rfc7858)) and gRPC (not +a standard. + + Currently CoreDNS is able to: * Serve zone data from a file; both DNSSEC (NSEC only) and DNS are supported (*file*). @@ -44,8 +50,8 @@ Each of the middlewares has a README.md of its own. CoreDNS can be used as an authoritative nameserver for your domains, and should be stable enough to provide you with good DNS(SEC) service. -There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing on making -things fast and to reduce the memory usage. +There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing +on making things fast and to reduce the memory usage. All in all, CoreDNS should be able to provide you with enough functionality to replace parts of BIND 9, Knot, NSD or PowerDNS and SkyDNS. Most documentation is in the source and some blog articles can @@ -169,6 +175,24 @@ example.org { } ~~~ +Listening on TLS and for gRPC? Use: + +~~~ txt +tls://example.org grpc://example.org { + # ... +} +~~~ + +Specifying ports works in the same way: + +~~~ txt +grpc://example.org:1443 { + # ... +} +~~~ + +When no transport protocol is specified the default `dns://` is assumed. + ## Blog and Contact Website: diff --git a/core/dnsserver/address.go b/core/dnsserver/address.go index fc51aed96..48dcee3cf 100644 --- a/core/dnsserver/address.go +++ b/core/dnsserver/address.go @@ -9,12 +9,26 @@ import ( ) type zoneAddr struct { - Zone string - Port string + Zone string + Port string + Transport string // dns, tls or grpc } -// String return z.Zone + ":" + z.Port as a string. -func (z zoneAddr) String() string { return z.Zone + ":" + z.Port } +// String return the string represenation of z. +func (z zoneAddr) String() string { return z.Transport + "://" + z.Zone + ":" + z.Port } + +// Transport returns the protocol of the string s +func Transport(s string) string { + switch { + case strings.HasPrefix(s, TransportTLS+"://"): + return TransportTLS + case strings.HasPrefix(s, TransportDNS+"://"): + return TransportDNS + case strings.HasPrefix(s, TransportGRPC+"://"): + return TransportGRPC + } + return TransportDNS +} // normalizeZone parses an zone string into a structured format with separate // host, and port portions, as well as the original input string. @@ -23,14 +37,28 @@ func (z zoneAddr) String() string { return z.Zone + ":" + z.Port } func normalizeZone(str string) (zoneAddr, error) { var err error - // separate host and port + // Default to DNS if there isn't a transport protocol prefix. + trans := TransportDNS + + switch { + case strings.HasPrefix(str, TransportTLS+"://"): + trans = TransportTLS + str = str[len(TransportTLS+"://"):] + case strings.HasPrefix(str, TransportDNS+"://"): + trans = TransportDNS + str = str[len(TransportDNS+"://"):] + case strings.HasPrefix(str, TransportGRPC+"://"): + trans = TransportGRPC + str = str[len(TransportGRPC+"://"):] + } + host, port, err := net.SplitHostPort(str) if err != nil { host, port, err = net.SplitHostPort(str + ":") // no error check here; return err at end of function } - if len(host) > 255 { + if len(host) > 255 { // TODO(miek): this should take escaping into account. return zoneAddr{}, fmt.Errorf("specified zone is too long: %d > 255", len(host)) } _, d := dns.IsDomainName(host) @@ -39,8 +67,23 @@ func normalizeZone(str string) (zoneAddr, error) { } if port == "" { - port = Port + if trans == TransportDNS { + port = Port + } + if trans == TransportTLS { + port = TLSPort + } + if trans == TransportGRPC { + port = GRPCPort + } } - return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port}, err + return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port, Transport: trans}, err } + +// Supported transports. +const ( + TransportDNS = "dns" + TransportTLS = "tls" + TransportGRPC = "grpc" +) diff --git a/core/dnsserver/address_test.go b/core/dnsserver/address_test.go index 2aca80c70..84747d872 100644 --- a/core/dnsserver/address_test.go +++ b/core/dnsserver/address_test.go @@ -8,10 +8,10 @@ func TestNormalizeZone(t *testing.T) { expected string shouldErr bool }{ - {".", ".:53", false}, - {".:54", ".:54", false}, - {"..", ":", true}, - {"..", ":", true}, + {".", "dns://.:53", false}, + {".:54", "dns://.:54", false}, + {"..", "://:", true}, + {"..", "://:", true}, } { addr, err := normalizeZone(test.input) actual := addr.String() diff --git a/core/dnsserver/config.go b/core/dnsserver/config.go index ac623838a..942d43d58 100644 --- a/core/dnsserver/config.go +++ b/core/dnsserver/config.go @@ -1,6 +1,8 @@ package dnsserver import ( + "crypto/tls" + "github.com/coredns/coredns/middleware" "github.com/mholt/caddy" @@ -21,8 +23,12 @@ type Config struct { // First consumer is the file middleware to looks for zone files in this place. Root string - // Server is the server that handles this config - Server *Server + // The transport we implement, normally just "dns" over TCP/UDP, but could be + // DNS-over-TLS or DNS-over-gRPC. + Transport string + + // TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS). + TLSConfig *tls.Config // Middleware stack. Middleware []middleware.Middleware @@ -50,7 +56,6 @@ func GetConfig(c *caddy.Controller) *Config { // Note that this is order dependent and the order is defined in directives.go, i.e. if your middleware // comes before the middleware you are checking; it will not be there (yet). func GetMiddleware(c *caddy.Controller, name string) middleware.Handler { - // TODO(miek): calling the handler h(nil) should be a noop... conf := GetConfig(c) for _, h := range conf.Middleware { x := h(nil) diff --git a/core/dnsserver/register.go b/core/dnsserver/register.go index ef73180bc..4b0ec5945 100644 --- a/core/dnsserver/register.go +++ b/core/dnsserver/register.go @@ -61,15 +61,16 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy return nil, err } s.Keys[i] = za.String() - if v, ok := dups[za.Zone]; ok { + if v, ok := dups[za.String()]; ok { return nil, fmt.Errorf("cannot serve %s - zone already defined for %v", za, v) } - dups[za.Zone] = za.String() + dups[za.String()] = za.String() // Save the config to our master list, and key it for lookups cfg := &Config{ - Zone: za.Zone, - Port: za.Port, + Zone: za.Zone, + Port: za.Port, + Transport: za.Transport, } h.saveConfig(za.String(), cfg) } @@ -88,11 +89,31 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) { // then we create a server for each group var servers []caddy.Server for addr, group := range groups { - s, err := NewServer(addr, group) - if err != nil { - return nil, err + // switch on addr + switch Transport(addr) { + case TransportDNS: + s, err := NewServer(addr, group) + if err != nil { + return nil, err + } + servers = append(servers, s) + + case TransportTLS: + s, err := NewServerTLS(addr, group) + if err != nil { + return nil, err + } + servers = append(servers, s) + + case TransportGRPC: + s, err := NewServergRPC(addr, group) + if err != nil { + return nil, err + } + servers = append(servers, s) + } - servers = append(servers, s) + } return servers, nil @@ -112,14 +133,11 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) { groups := make(map[string][]*Config) for _, conf := range configs { - if conf.Port == "" { - conf.Port = Port - } addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Port)) if err != nil { return nil, err } - addrstr := addr.String() + addrstr := conf.Transport + "://" + addr.String() groups[addrstr] = append(groups[addrstr], conf) } @@ -129,6 +147,10 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) { const ( // DefaultPort is the default port. DefaultPort = "53" + // TLSPort is the default port for DNS-over-TLS. + TLSPort = "853" + // GRPCPort is the default port for DNS-over-gRPC. + GRPCPort = "443" ) // These "soft defaults" are configurable by diff --git a/core/dnsserver/server-grpc.go b/core/dnsserver/server-grpc.go new file mode 100644 index 000000000..5eed5fc54 --- /dev/null +++ b/core/dnsserver/server-grpc.go @@ -0,0 +1,165 @@ +package dnsserver + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + + "github.com/miekg/dns" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/peer" + + "github.com/coredns/coredns/pb" +) + +// servergRPC represents an instance of a DNS-over-gRPC server. +type servergRPC struct { + *Server + grpcServer *grpc.Server + + listenAddr net.Addr +} + +// NewGRPCServer returns a new CoreDNS GRPC server and compiles all middleware in to it. +func NewServergRPC(addr string, group []*Config) (*servergRPC, error) { + + s, err := NewServer(addr, group) + if err != nil { + return nil, err + } + gs := &servergRPC{Server: s} + gs.grpcServer = grpc.NewServer() + // trace foo... TODO(miek) + pb.RegisterDnsServiceServer(gs.grpcServer, gs) + + return gs, nil +} + +// Serve implements caddy.TCPServer interface. +func (s *servergRPC) Serve(l net.Listener) error { + s.m.Lock() + s.listenAddr = l.Addr() + s.m.Unlock() + + return s.grpcServer.Serve(l) +} + +// ServePacket implements caddy.UDPServer interface. +func (s *servergRPC) ServePacket(p net.PacketConn) error { return nil } + +// Listen implements caddy.TCPServer interface. +func (s *servergRPC) Listen() (net.Listener, error) { + + // The *tls* middleware must make sure that multiple conflicting + // TLS configuration return an error: it can only be specified once. + tlsConfig := new(tls.Config) + for _, conf := range s.zones { + // Should we error if some configs *don't* have TLS? + tlsConfig = conf.TLSConfig + } + + var ( + l net.Listener + err error + ) + + if tlsConfig == nil { + l, err = net.Listen("tcp", s.Addr[len(TransportGRPC+"://"):]) + } else { + l, err = tls.Listen("tcp", s.Addr[len(TransportGRPC+"://"):], tlsConfig) + } + + if err != nil { + return nil, err + } + return l, nil +} + +// ListenPacket implements caddy.UDPServer interface. +func (s *servergRPC) ListenPacket() (net.PacketConn, error) { return nil, nil } + +// OnStartupComplete lists the sites served by this server +// and any relevant information, assuming Quiet is false. +func (s *servergRPC) OnStartupComplete() { + if Quiet { + return + } + + for zone, config := range s.zones { + fmt.Println(TransportGRPC + "://" + zone + ":" + config.Port) + } +} + +func (s *servergRPC) Stop() (err error) { + s.m.Lock() + defer s.m.Unlock() + if s.grpcServer != nil { + s.grpcServer.GracefulStop() + } + return +} + +// Query is the main entry-point into the gRPC server. From here we call ServeDNS like +// any normal server. We use a custom responseWriter to pick up the bytes we need to write +// back to the client as a protobuf. +func (s *servergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket, error) { + msg := new(dns.Msg) + err := msg.Unpack(in.Msg) + if err != nil { + return nil, err + } + + p, ok := peer.FromContext(ctx) + if !ok { + return nil, errors.New("no peer in gRPC context") + } + + a, ok := p.Addr.(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("no TCP peer in gRPC context: %v", p.Addr) + } + + r := &net.IPAddr{IP: a.IP} + w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: r, Msg: msg} + + s.ServeDNS(ctx, w, msg) + + packed, err := w.Msg.Pack() + if err != nil { + return nil, err + } + + return &pb.DnsPacket{Msg: packed}, nil +} + +func (g *servergRPC) Shutdown() error { + if g.grpcServer != nil { + g.grpcServer.Stop() + } + return nil +} + +type gRPCresponse struct { + localAddr net.Addr + remoteAddr net.Addr + Msg *dns.Msg +} + +// Write is the hack that makes this work. It does not actually write the message +// but returns the bytes we need to to write in r. We can then pick this up in Query +// and write a proper protobuf back to the client. +func (r *gRPCresponse) Write(b []byte) (int, error) { + r.Msg = new(dns.Msg) + return len(b), r.Msg.Unpack(b) +} + +// These methods implement the dns.ResponseWriter interface from Go DNS. +func (r *gRPCresponse) Close() error { return nil } +func (r *gRPCresponse) TsigStatus() error { return nil } +func (r *gRPCresponse) TsigTimersOnly(b bool) { return } +func (r *gRPCresponse) Hijack() { return } +func (r *gRPCresponse) LocalAddr() net.Addr { return r.localAddr } +func (r *gRPCresponse) RemoteAddr() net.Addr { return r.remoteAddr } +func (r *gRPCresponse) WriteMsg(m *dns.Msg) error { r.Msg = m; return nil } diff --git a/core/dnsserver/server-tls.go b/core/dnsserver/server-tls.go new file mode 100644 index 000000000..9d0a81876 --- /dev/null +++ b/core/dnsserver/server-tls.go @@ -0,0 +1,85 @@ +package dnsserver + +import ( + "context" + "crypto/tls" + "fmt" + "net" + + "github.com/miekg/dns" +) + +// serverTLS represents an instance of a TLS-over-DNS-server. +type serverTLS struct { + *Server +} + +// NewTLSServer returns a new CoreDNS TLS server and compiles all middleware in to it. +func NewServerTLS(addr string, group []*Config) (*serverTLS, error) { + + s, err := NewServer(addr, group) + if err != nil { + return nil, err + } + + return &serverTLS{Server: s}, nil +} + +// Serve implements caddy.TCPServer interface. +func (s *serverTLS) Serve(l net.Listener) error { + s.m.Lock() + + // Only fill out the TCP server for this one. + s.server[tcp] = &dns.Server{Listener: l, Net: "tcp-tls", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { + ctx := context.Background() + s.ServeDNS(ctx, w, r) + })} + s.m.Unlock() + + return s.server[tcp].ActivateAndServe() +} + +// ServePacket implements caddy.UDPServer interface. +func (s *serverTLS) ServePacket(p net.PacketConn) error { return nil } + +// Listen implements caddy.TCPServer interface. +func (s *serverTLS) Listen() (net.Listener, error) { + // The *tls* middleware must make sure that multiple conflicting + // TLS configuration return an error: it can only be specified once. + tlsConfig := new(tls.Config) + for _, conf := range s.zones { + // Should we error if some configs *don't* have TLS? + tlsConfig = conf.TLSConfig + } + + var ( + l net.Listener + err error + ) + + if tlsConfig == nil { + l, err = net.Listen("tcp", s.Addr[len(TransportTLS+"://"):]) + } else { + l, err = tls.Listen("tcp", s.Addr[len(TransportTLS+"://"):], tlsConfig) + } + + if err != nil { + return nil, err + } + return l, nil +} + +// ListenPacket implements caddy.UDPServer interface. +func (s *serverTLS) ListenPacket() (net.PacketConn, error) { return nil, nil } + +// OnStartupComplete lists the sites served by this server +// and any relevant information, assuming Quiet is false. +func (s *serverTLS) OnStartupComplete() { + if Quiet { + return + } + + for zone, config := range s.zones { + fmt.Println(TransportTLS + "://" + zone + ":" + config.Port) + } +} diff --git a/core/dnsserver/server.go b/core/dnsserver/server.go index 1d9b5b39b..89060bc78 100644 --- a/core/dnsserver/server.go +++ b/core/dnsserver/server.go @@ -61,7 +61,6 @@ func NewServer(addr string, group []*Config) (*Server, error) { stack = site.Middleware[i](stack) } site.middlewareChain = stack - site.Server = s } return s, nil @@ -95,7 +94,7 @@ func (s *Server) ServePacket(p net.PacketConn) error { // Listen implements caddy.TCPServer interface. func (s *Server) Listen() (net.Listener, error) { - l, err := net.Listen("tcp", s.Addr) + l, err := net.Listen("tcp", s.Addr[len(TransportDNS+"://"):]) if err != nil { return nil, err } @@ -104,7 +103,7 @@ func (s *Server) Listen() (net.Listener, error) { // ListenPacket implements caddy.UDPServer interface. func (s *Server) ListenPacket() (net.PacketConn, error) { - p, err := net.ListenPacket("udp", s.Addr) + p, err := net.ListenPacket("udp", s.Addr[len(TransportDNS+"://"):]) if err != nil { return nil, err } diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go index 48e862bfd..ab1fb6488 100644 --- a/core/dnsserver/zdirectives.go +++ b/core/dnsserver/zdirectives.go @@ -11,6 +11,7 @@ package dnsserver // care what middleware above them are doing. var directives = []string{ + "tls", "root", "bind", "trace", diff --git a/core/zmiddleware.go b/core/zmiddleware.go index e77d800cf..3c4b5edc9 100644 --- a/core/zmiddleware.go +++ b/core/zmiddleware.go @@ -24,6 +24,7 @@ import ( _ "github.com/coredns/coredns/middleware/rewrite" _ "github.com/coredns/coredns/middleware/root" _ "github.com/coredns/coredns/middleware/secondary" + _ "github.com/coredns/coredns/middleware/tls" _ "github.com/coredns/coredns/middleware/trace" _ "github.com/coredns/coredns/middleware/whoami" ) diff --git a/middleware.cfg b/middleware.cfg index d516e44d9..75985cf22 100644 --- a/middleware.cfg +++ b/middleware.cfg @@ -19,6 +19,7 @@ # Local middleware example: # 80:log:log +1:tls:tls 10:root:root 20:bind:bind 30:trace:trace diff --git a/middleware/erratic/erratic.go b/middleware/erratic/erratic.go index 3aec36798..a3fbb2e8e 100644 --- a/middleware/erratic/erratic.go +++ b/middleware/erratic/erratic.go @@ -38,7 +38,7 @@ func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg m.Authoritative = true // small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername - // in a non-racy manor. + // in a non-racy way. switch state.QType() { case dns.TypeA: rr := *(rrA.(*dns.A)) diff --git a/middleware/normalize.go b/middleware/normalize.go index 87f3ce703..77ef97993 100644 --- a/middleware/normalize.go +++ b/middleware/normalize.go @@ -7,6 +7,8 @@ import ( "github.com/miekg/dns" ) +// See core/dnsserver/address.go - we should unify these two impls. + // Zones respresents a lists of zone names. type Zones []string @@ -56,12 +58,24 @@ type ( ) // Normalize will return the host portion of host, stripping -// of any port. The host will also be fully qualified and lowercased. +// of any port or transport. The host will also be fully qualified and lowercased. func (h Host) Normalize() string { + + s := string(h) + + switch { + case strings.HasPrefix(s, TransportTLS+"://"): + s = s[len(TransportTLS+"://"):] + case strings.HasPrefix(s, TransportDNS+"://"): + s = s[len(TransportDNS+"://"):] + case strings.HasPrefix(s, TransportGRPC+"://"): + s = s[len(TransportGRPC+"://"):] + } + // separate host and port - host, _, err := net.SplitHostPort(string(h)) + host, _, err := net.SplitHostPort(s) if err != nil { - host, _, _ = net.SplitHostPort(string(h) + ":") + host, _, _ = net.SplitHostPort(s + ":") } return Name(host).Normalize() } @@ -77,3 +91,10 @@ func (a Addr) Normalize() string { // TODO(miek): lowercase it? return net.JoinHostPort(addr, port) } + +// Duplicated from core/dnsserver/address.go ! +const ( + TransportDNS = "dns" + TransportTLS = "tls" + TransportGRPC = "grpc" +) diff --git a/middleware/proxy/grpc.go b/middleware/proxy/grpc.go index c480d3cf2..d3ed14ccd 100644 --- a/middleware/proxy/grpc.go +++ b/middleware/proxy/grpc.go @@ -5,16 +5,13 @@ import ( "crypto/tls" "log" - "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" - - "github.com/coredns/coredns/middleware/proxy/pb" "github.com/coredns/coredns/middleware/trace" + "github.com/coredns/coredns/pb" "github.com/coredns/coredns/request" + "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" "github.com/miekg/dns" - opentracing "github.com/opentracing/opentracing-go" - "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) diff --git a/middleware/reverse/setup.go b/middleware/reverse/setup.go index ea94aea7d..56ab620fa 100644 --- a/middleware/reverse/setup.go +++ b/middleware/reverse/setup.go @@ -40,8 +40,7 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) { zones := make([]string, len(c.ServerBlockKeys)) for i, str := range c.ServerBlockKeys { - host, _, _ := net.SplitHostPort(str) - zones[i] = strings.ToLower(host) + zones[i] = middleware.Host(str).Normalize() } for c.Next() { diff --git a/middleware/root/root.go b/middleware/root/root.go index 6a26fbd1a..d03ecb8a4 100644 --- a/middleware/root/root.go +++ b/middleware/root/root.go @@ -5,6 +5,7 @@ import ( "os" "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/middleware" "github.com/mholt/caddy" ) @@ -21,7 +22,7 @@ func setup(c *caddy.Controller) error { for c.Next() { if !c.NextArg() { - return c.ArgErr() + return middleware.Error("root", c.ArgErr()) } config.Root = c.Val() } @@ -34,7 +35,7 @@ func setup(c *caddy.Controller) error { // But make sure the user knows! log.Printf("[WARNING] Root path does not exist: %s", config.Root) } else { - return c.Errf("Unable to access root path '%s': %v", config.Root, err) + return middleware.Error("root", c.Errf("unable to access root path '%s': %v", config.Root, err)) } } diff --git a/middleware/root/root_test.go b/middleware/root/root_test.go index 0a58c69bf..6823301f8 100644 --- a/middleware/root/root_test.go +++ b/middleware/root/root_test.go @@ -19,7 +19,7 @@ func TestRoot(t *testing.T) { // Predefined error substrings parseErrContent := "Parse error:" - unableToAccessErrContent := "Unable to access root path" + unableToAccessErrContent := "unable to access root path" existingDirPath, err := getTempDirPath() if err != nil { diff --git a/middleware/tls/README.md b/middleware/tls/README.md new file mode 100644 index 000000000..6070257d3 --- /dev/null +++ b/middleware/tls/README.md @@ -0,0 +1,13 @@ +# tls + +*tls* extra TLS configuration. + +## Syntax + +~~~ txt +tls [STUFF] +~~~ + +**STUFF** is things you'll need to configure TLS. + +## Examples diff --git a/middleware/tls/tls.go b/middleware/tls/tls.go new file mode 100644 index 000000000..2e2586ce5 --- /dev/null +++ b/middleware/tls/tls.go @@ -0,0 +1,37 @@ +package tls + +import ( + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/middleware" + "github.com/coredns/coredns/middleware/pkg/tls" + + "github.com/mholt/caddy" +) + +func init() { + caddy.RegisterPlugin("tls", caddy.Plugin{ + ServerType: "dns", + Action: setup, + }) +} + +func setup(c *caddy.Controller) error { + config := dnsserver.GetConfig(c) + + if config.TLSConfig != nil { + return middleware.Error("tls", c.Errf("TLS already configured for this server instance")) + } + + for c.Next() { + args := c.RemainingArgs() + if len(args) != 3 { + return middleware.Error("tls", c.ArgErr()) + } + tls, err := tls.NewTLSConfig(args[0], args[1], args[2]) + if err != nil { + return middleware.Error("tls", c.ArgErr()) + } + config.TLSConfig = tls + } + return nil +} diff --git a/middleware/tls/tls_test.go b/middleware/tls/tls_test.go new file mode 100644 index 000000000..2374d772c --- /dev/null +++ b/middleware/tls/tls_test.go @@ -0,0 +1,44 @@ +package tls + +import ( + "io/ioutil" + "log" + "strings" + "testing" + + "github.com/mholt/caddy" +) + +func TestTLS(t *testing.T) { + log.SetOutput(ioutil.Discard) + + tests := []struct { + input string + shouldErr bool + expectedRoot string // expected root, set to the controller. Empty for negative cases. + expectedErrContent string // substring from the expected error. Empty for positive cases. + }{ + // positive + // negative + } + + for i, test := range tests { + c := caddy.NewTestController("dns", test.input) + err := setup(c) + //cfg := dnsserver.GetConfig(c) + + if test.shouldErr && err == nil { + t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) + } + + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) + } + + if !strings.Contains(err.Error(), test.expectedErrContent) { + t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) + } + } + } +} diff --git a/middleware/proxy/pb/dns.pb.go b/pb/dns.pb.go similarity index 100% rename from middleware/proxy/pb/dns.pb.go rename to pb/dns.pb.go diff --git a/middleware/proxy/pb/dns.proto b/pb/dns.proto similarity index 100% rename from middleware/proxy/pb/dns.proto rename to pb/dns.proto diff --git a/test/grpc_test.go b/test/grpc_test.go new file mode 100644 index 000000000..f7c47b57c --- /dev/null +++ b/test/grpc_test.go @@ -0,0 +1,62 @@ +package test + +import ( + "io/ioutil" + "log" + "testing" + "time" + + "github.com/miekg/dns" + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/coredns/coredns/pb" +) + +func TestGrpc(t *testing.T) { + log.SetOutput(ioutil.Discard) + + corefile := `grpc://.:0 { + whoami +} +` + g, err := CoreDNSServer(corefile) + if err != nil { + t.Fatalf("Could not get CoreDNS serving instance: %s", err) + } + + _, tcp := CoreDNSServerPorts(g, 0) + defer g.Stop() + + conn, err := grpc.Dial(tcp, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(5*time.Second)) + if err != nil { + t.Fatalf("Expected no error but got: %s", err) + } + defer conn.Close() + + client := pb.NewDnsServiceClient(conn) + + m := new(dns.Msg) + m.SetQuestion("whoami.example.org.", dns.TypeA) + msg, _ := m.Pack() + + reply, err := client.Query(context.TODO(), &pb.DnsPacket{Msg: msg}) + if err != nil { + t.Errorf("Expected no error but got: %s", err) + } + + d := new(dns.Msg) + err = d.Unpack(reply.Msg) + if err != nil { + t.Errorf("Expected no error but got: %s", err) + } + + if d.Rcode != dns.RcodeSuccess { + t.Errorf("Expected success but got %s", d.Rcode) + } + + if len(d.Extra) != 2 { + t.Errorf("Expected 2 RRs in additional section, but got %s", len(d.Extra)) + } + t.Logf("Message %v\n", d) +} diff --git a/test/proxy_test.go b/test/proxy_test.go index 7b96582c5..cb9f1b298 100644 --- a/test/proxy_test.go +++ b/test/proxy_test.go @@ -46,7 +46,7 @@ func TestLookupProxy(t *testing.T) { } // expect answer section with A record in it if len(resp.Answer) == 0 { - t.Error("Expected to at least one RR in the answer section, got none") + t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp) } if resp.Answer[0].Header().Rrtype != dns.TypeA { t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)