mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-03 18:53:13 -05:00 
			
		
		
		
	middleware/proxy: config syntax cleanups (#435)
* middleware/proxy: config syntax cleanups Allow port numbers to be used in the transfer statements and clean up the proxy stanza parsing. Also allow, when specifying an upstream, /etc/resolv.conf (or any other file) to be used for getting the upstream nameserver. Add tests and fix the documentation to make clear what is allowed. * Fix the other upstream parse as well
This commit is contained in:
		@@ -37,7 +37,8 @@ etcd [ZONES...] {
 | 
			
		||||
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
 | 
			
		||||
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
 | 
			
		||||
  pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add
 | 
			
		||||
  the proxy middleware.
 | 
			
		||||
  the proxy middleware. **ADDRESS* can be an IP address, and IP:port or a string pointing to a file
 | 
			
		||||
  that is structured as /etc/resolv.conf.
 | 
			
		||||
* `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
 | 
			
		||||
  additional section of the reply in the form of TXT records.
 | 
			
		||||
@@ -61,6 +62,21 @@ This is the default SkyDNS setup, with everying specified in full:
 | 
			
		||||
}
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
Or a setup where we use `/etc/resolv.conf` as the basis for the proxy and the upstream
 | 
			
		||||
when resolving external pointing CNAMEs.
 | 
			
		||||
 | 
			
		||||
~~~
 | 
			
		||||
.:53 {
 | 
			
		||||
    etcd skydns.local {
 | 
			
		||||
        path /skydns
 | 
			
		||||
        upstream /etc/resolv.conf
 | 
			
		||||
    }
 | 
			
		||||
    cache 160 skydns.local
 | 
			
		||||
    proxy . /etc/resolv.conf
 | 
			
		||||
}
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Reverse zones
 | 
			
		||||
 | 
			
		||||
Reverse zones are supported. You need to make CoreDNS aware of the fact that you are also
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/miekg/coredns/core/dnsserver"
 | 
			
		||||
	"github.com/miekg/coredns/middleware"
 | 
			
		||||
	"github.com/miekg/coredns/middleware/pkg/dnsutil"
 | 
			
		||||
	"github.com/miekg/coredns/middleware/pkg/singleflight"
 | 
			
		||||
	"github.com/miekg/coredns/middleware/proxy"
 | 
			
		||||
 | 
			
		||||
@@ -93,13 +94,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
 | 
			
		||||
					if len(args) == 0 {
 | 
			
		||||
						return &Etcd{}, 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"
 | 
			
		||||
						}
 | 
			
		||||
					ups, err := dnsutil.ParseHostPortOrFile(args...)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return &Etcd{}, false, err
 | 
			
		||||
					}
 | 
			
		||||
					etc.Proxy = proxy.New(args)
 | 
			
		||||
					etc.Proxy = proxy.New(ups)
 | 
			
		||||
				case "tls": // cert key cacertfile
 | 
			
		||||
					args := c.RemainingArgs()
 | 
			
		||||
					if len(args) != 3 {
 | 
			
		||||
@@ -133,13 +132,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
 | 
			
		||||
						if len(args) == 0 {
 | 
			
		||||
							return &Etcd{}, 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"
 | 
			
		||||
							}
 | 
			
		||||
						ups, err := dnsutil.ParseHostPortOrFile(args...)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							return &Etcd{}, false, c.ArgErr()
 | 
			
		||||
						}
 | 
			
		||||
						etc.Proxy = proxy.New(args)
 | 
			
		||||
						etc.Proxy = proxy.New(ups)
 | 
			
		||||
					case "tls": // cert key cacertfile
 | 
			
		||||
						args := c.RemainingArgs()
 | 
			
		||||
						if len(args) != 3 {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,12 @@ package file
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"github.com/miekg/coredns/core/dnsserver"
 | 
			
		||||
	"github.com/miekg/coredns/middleware"
 | 
			
		||||
	"github.com/miekg/coredns/middleware/pkg/dnsutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
)
 | 
			
		||||
@@ -125,24 +125,26 @@ func TransferParse(c *caddy.Controller, secondary bool) (tos, froms []string, er
 | 
			
		||||
			tos = c.RemainingArgs()
 | 
			
		||||
			for i := range tos {
 | 
			
		||||
				if tos[i] != "*" {
 | 
			
		||||
					if x := net.ParseIP(tos[i]); x == nil {
 | 
			
		||||
						return nil, nil, fmt.Errorf("must specify an IP address: `%s'", tos[i])
 | 
			
		||||
					normalized, err := dnsutil.ParseHostPort(tos[i], "53")
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, nil, err
 | 
			
		||||
					}
 | 
			
		||||
					tos[i] = middleware.Addr(tos[i]).Normalize()
 | 
			
		||||
					tos[i] = normalized
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if value == "from" {
 | 
			
		||||
			if !secondary {
 | 
			
		||||
				return nil, nil, fmt.Errorf("can't use `transfer from` when not being a seconary")
 | 
			
		||||
				return nil, nil, fmt.Errorf("can't use `transfer from` when not being a secondary")
 | 
			
		||||
			}
 | 
			
		||||
			froms = c.RemainingArgs()
 | 
			
		||||
			for i := range froms {
 | 
			
		||||
				if froms[i] != "*" {
 | 
			
		||||
					if x := net.ParseIP(froms[i]); x == nil {
 | 
			
		||||
						return nil, nil, fmt.Errorf("must specify an IP address: `%s'", froms[i])
 | 
			
		||||
					normalized, err := dnsutil.ParseHostPort(froms[i], "53")
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, nil, err
 | 
			
		||||
					}
 | 
			
		||||
					froms[i] = middleware.Addr(froms[i]).Normalize()
 | 
			
		||||
					froms[i] = normalized
 | 
			
		||||
				} else {
 | 
			
		||||
					return nil, nil, fmt.Errorf("can't use '*' in transfer from")
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										82
									
								
								middleware/pkg/dnsutil/host.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								middleware/pkg/dnsutil/host.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
package dnsutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/miekg/dns"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PorseHostPortOrFile parses the strings in s, each string can either be a address,
 | 
			
		||||
// address:port or a filename. The address part is checked and the filename case a
 | 
			
		||||
// resolv.conf like file is parsed and the nameserver found are returned.
 | 
			
		||||
func ParseHostPortOrFile(s ...string) ([]string, error) {
 | 
			
		||||
	var servers []string
 | 
			
		||||
	for _, host := range s {
 | 
			
		||||
		addr, _, err := net.SplitHostPort(host)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Parse didn't work, it is not a addr:port combo
 | 
			
		||||
			if net.ParseIP(host) == nil {
 | 
			
		||||
				// Not an IP address.
 | 
			
		||||
				ss, err := tryFile(host)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					servers = append(servers, ss...)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				return servers, fmt.Errorf("not an IP address or file: %q", host)
 | 
			
		||||
			}
 | 
			
		||||
			ss := net.JoinHostPort(host, "53")
 | 
			
		||||
			servers = append(servers, ss)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if net.ParseIP(addr) == nil {
 | 
			
		||||
			// No an IP address.
 | 
			
		||||
			ss, err := tryFile(host)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				servers = append(servers, ss...)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return servers, fmt.Errorf("not an IP address or file: %q", host)
 | 
			
		||||
		}
 | 
			
		||||
		servers = append(servers, host)
 | 
			
		||||
	}
 | 
			
		||||
	return servers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Try to open this is a file first.
 | 
			
		||||
func tryFile(s string) ([]string, error) {
 | 
			
		||||
	c, err := dns.ClientConfigFromFile(s)
 | 
			
		||||
	if err == os.ErrNotExist {
 | 
			
		||||
		return nil, fmt.Errorf("failed to open file %q: %q", s, err)
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	servers := []string{}
 | 
			
		||||
	for _, s := range c.Servers {
 | 
			
		||||
		servers = append(servers, net.JoinHostPort(s, c.Port))
 | 
			
		||||
	}
 | 
			
		||||
	return servers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseHostPort will check if the host part is a valid IP address, if the
 | 
			
		||||
// IP address is valid, but no port is found, defaultPort is added.
 | 
			
		||||
func ParseHostPort(s, defaultPort string) (string, error) {
 | 
			
		||||
	addr, port, err := net.SplitHostPort(s)
 | 
			
		||||
	if port == "" {
 | 
			
		||||
		port = defaultPort
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if net.ParseIP(s) == nil {
 | 
			
		||||
			return "", fmt.Errorf("must specify an IP address: `%s'", s)
 | 
			
		||||
		}
 | 
			
		||||
		return net.JoinHostPort(s, port), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if net.ParseIP(addr) == nil {
 | 
			
		||||
		return "", fmt.Errorf("must specify an IP address: `%s'", addr)
 | 
			
		||||
	}
 | 
			
		||||
	return net.JoinHostPort(addr, port), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								middleware/pkg/dnsutil/host_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								middleware/pkg/dnsutil/host_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
package dnsutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseHostPortOrFile(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		in        string
 | 
			
		||||
		expected  string
 | 
			
		||||
		shouldErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"8.8.8.8",
 | 
			
		||||
			"8.8.8.8:53",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"8.8.8.8:153",
 | 
			
		||||
			"8.8.8.8:153",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"/etc/resolv.conf:53",
 | 
			
		||||
			"",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"resolv.conf",
 | 
			
		||||
			"127.0.0.1:53",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := ioutil.WriteFile("resolv.conf", []byte("nameserver 127.0.0.1\n"), 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Failed to write test resolv.conf")
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove("resolv.conf")
 | 
			
		||||
 | 
			
		||||
	for i, tc := range tests {
 | 
			
		||||
		got, err := ParseHostPortOrFile(tc.in)
 | 
			
		||||
		if err == nil && tc.shouldErr {
 | 
			
		||||
			t.Errorf("Test %d, expected error, got nil", i)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil && tc.shouldErr {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if got[0] != tc.expected {
 | 
			
		||||
			t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseHostPort(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		in        string
 | 
			
		||||
		expected  string
 | 
			
		||||
		shouldErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"8.8.8.8:53", "8.8.8.8:53", false},
 | 
			
		||||
		{"a.a.a.a:153", "", true},
 | 
			
		||||
		{"8.8.8.8", "8.8.8.8:53", false},
 | 
			
		||||
		{"8.8.8.8:", "8.8.8.8:53", false},
 | 
			
		||||
		{"8.8.8.8::53", "", true},
 | 
			
		||||
		{"resolv.conf", "", true},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tc := range tests {
 | 
			
		||||
		got, err := ParseHostPort(tc.in, "53")
 | 
			
		||||
		if err == nil && tc.shouldErr {
 | 
			
		||||
			t.Errorf("Test %d, expected error, got nil", i)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil && !tc.shouldErr {
 | 
			
		||||
			t.Errorf("Test %d, expected no error, got %q", i, err)
 | 
			
		||||
		}
 | 
			
		||||
		if got != tc.expected {
 | 
			
		||||
			t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
In its most basic form, a simple reverse proxy uses this syntax:
 | 
			
		||||
 | 
			
		||||
~~~
 | 
			
		||||
proxy FROM To
 | 
			
		||||
proxy FROM TO
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
* **FROM** is the base path to match for the request to be proxied
 | 
			
		||||
@@ -68,13 +68,13 @@ proxy example.org localhost:9005
 | 
			
		||||
Load-balance all requests between three backends (using random policy):
 | 
			
		||||
 | 
			
		||||
~~~
 | 
			
		||||
proxy . web1.local:53 web2.local:1053 web3.local
 | 
			
		||||
proxy . dns1.local:53 dns2.local:1053 dns3.local
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
Same as above, but round-robin style:
 | 
			
		||||
 | 
			
		||||
~~~
 | 
			
		||||
proxy . web1.local:53 web2.local:1053 web3.local {
 | 
			
		||||
proxy . dns1.local:53 dns2.local:1053 dns3.local {
 | 
			
		||||
	policy round_robin
 | 
			
		||||
}
 | 
			
		||||
~~~
 | 
			
		||||
@@ -82,7 +82,7 @@ proxy . web1.local:53 web2.local:1053 web3.local {
 | 
			
		||||
With health checks and proxy headers to pass hostname, IP, and scheme upstream:
 | 
			
		||||
 | 
			
		||||
~~~
 | 
			
		||||
proxy . web1.local:53 web2.local:53 web3.local:53 {
 | 
			
		||||
proxy . dns1.local:53 dns2.local:53 dns3.local:53 {
 | 
			
		||||
	policy round_robin
 | 
			
		||||
	health_check /health:8080
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/miekg/coredns/middleware"
 | 
			
		||||
	"github.com/miekg/coredns/middleware/pkg/dnsutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddyfile"
 | 
			
		||||
	"github.com/miekg/dns"
 | 
			
		||||
@@ -68,26 +67,9 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// process the host list, substituting in any nameservers in files
 | 
			
		||||
		var toHosts []string
 | 
			
		||||
		for _, host := range to {
 | 
			
		||||
			h, _, err := net.SplitHostPort(host)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				h = host
 | 
			
		||||
			}
 | 
			
		||||
			if x := net.ParseIP(h); x == nil {
 | 
			
		||||
				// it's a file, parse as resolv.conf
 | 
			
		||||
				c, err := dns.ClientConfigFromFile(host)
 | 
			
		||||
				if err == os.ErrNotExist {
 | 
			
		||||
					return upstreams, fmt.Errorf("not an IP address or file: `%s'", h)
 | 
			
		||||
				} else if err != nil {
 | 
			
		||||
					return upstreams, err
 | 
			
		||||
				}
 | 
			
		||||
				for _, s := range c.Servers {
 | 
			
		||||
					toHosts = append(toHosts, net.JoinHostPort(s, c.Port))
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				toHosts = append(toHosts, host)
 | 
			
		||||
			}
 | 
			
		||||
		toHosts, err := dnsutil.ParseHostPortOrFile(to...)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return upstreams, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for c.NextBlock() {
 | 
			
		||||
@@ -99,7 +81,7 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
 | 
			
		||||
		upstream.Hosts = make([]*UpstreamHost, len(toHosts))
 | 
			
		||||
		for i, host := range toHosts {
 | 
			
		||||
			uh := &UpstreamHost{
 | 
			
		||||
				Name:        defaultHostPort(host),
 | 
			
		||||
				Name:        host,
 | 
			
		||||
				Conns:       0,
 | 
			
		||||
				Fails:       0,
 | 
			
		||||
				FailTimeout: upstream.FailTimeout,
 | 
			
		||||
@@ -297,11 +279,3 @@ func (u *staticUpstream) IsAllowedPath(name string) bool {
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func defaultHostPort(s string) string {
 | 
			
		||||
	_, _, e := net.SplitHostPort(s)
 | 
			
		||||
	if e == nil {
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
	return net.JoinHostPort(s, "53")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user