Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dns): Support alt domains #5940

Merged
merged 15 commits into from
Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions agent/config/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,9 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
}
}

datacenter := strings.ToLower(b.stringVal(c.Datacenter))
altDomain := b.stringVal(c.DNSAltDomain)

akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved
// Create the default set of tagged addresses.
if c.TaggedAddresses == nil {
c.TaggedAddresses = make(map[string]string)
Expand Down Expand Up @@ -588,8 +591,6 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
})
}

datacenter := strings.ToLower(b.stringVal(c.Datacenter))

aclsEnabled := false
primaryDatacenter := strings.ToLower(b.stringVal(c.PrimaryDatacenter))
if c.ACLDatacenter != nil {
Expand Down Expand Up @@ -727,6 +728,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
DNSARecordLimit: b.intVal(c.DNS.ARecordLimit),
DNSDisableCompression: b.boolVal(c.DNS.DisableCompression),
DNSDomain: b.stringVal(c.DNSDomain),
DNSAltDomain: altDomain,
DNSEnableTruncate: b.boolVal(c.DNS.EnableTruncate),
DNSMaxStale: b.durationVal("dns_config.max_stale", c.DNS.MaxStale),
DNSNodeTTL: b.durationVal("dns_config.node_ttl", c.DNS.NodeTTL),
Expand Down Expand Up @@ -964,6 +966,9 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
return fmt.Errorf("DNS recursor address cannot be 0.0.0.0, :: or [::]")
}
}
if !isValidAltDomain(rt.DNSAltDomain, rt.Datacenter) {
return fmt.Errorf("alt_domain cannot start with {service,connect,node,query,addr,%s}", rt.Datacenter)
}
if rt.Bootstrap && !rt.ServerMode {
return fmt.Errorf("'bootstrap = true' requires 'server = true'")
}
Expand Down Expand Up @@ -1686,6 +1691,18 @@ func isUnixAddr(a net.Addr) bool {
return ok
}

// isValidAltDomain returns true if the given domain is not prefixed
// by keywords used when dispatching DNS requests
func isValidAltDomain(domain, datacenter string) bool {
hanshasselberg marked this conversation as resolved.
Show resolved Hide resolved
reAltDomain := regexp.MustCompile(
fmt.Sprintf(
"^(service|connect|node|query|addr|%s)\\.(%s\\.)?",
datacenter, datacenter,
),
)
return !reAltDomain.MatchString(domain)
}

// UIPathBuilder checks to see if there was a path set
// If so, adds beginning and trailing slashes to UI path
func UIPathBuilder(UIContentString string) string {
Expand All @@ -1697,5 +1714,4 @@ func UIPathBuilder(UIContentString string) string {

}
return "/ui/"

}
1 change: 1 addition & 0 deletions agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ type Config struct {
Connect Connect `json:"connect,omitempty" hcl:"connect" mapstructure:"connect"`
DNS DNS `json:"dns_config,omitempty" hcl:"dns_config" mapstructure:"dns_config"`
DNSDomain *string `json:"domain,omitempty" hcl:"domain" mapstructure:"domain"`
DNSAltDomain *string `json:"alt_domain,omitempty" hcl:"alt_domain" mapstructure:"alt_domain"`
DNSRecursors []string `json:"recursors,omitempty" hcl:"recursors" mapstructure:"recursors"`
DataDir *string `json:"data_dir,omitempty" hcl:"data_dir" mapstructure:"data_dir"`
Datacenter *string `json:"datacenter,omitempty" hcl:"datacenter" mapstructure:"datacenter"`
Expand Down
1 change: 1 addition & 0 deletions agent/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) {
add(&f.Config.DisableKeyringFile, "disable-keyring-file", "Disables the backing up of the keyring to a file.")
add(&f.Config.Ports.DNS, "dns-port", "DNS port to use.")
add(&f.Config.DNSDomain, "domain", "Domain to use for DNS interface.")
add(&f.Config.DNSAltDomain, "alt-domain", "Alternate domain to use for DNS interface.")
add(&f.Config.EnableScriptChecks, "enable-script-checks", "Enables health check scripts.")
add(&f.Config.EnableLocalScriptChecks, "enable-local-script-checks", "Enables health check scripts from configuration file.")
add(&f.Config.HTTPConfig.AllowWriteHTTPFrom, "allow-write-http-from", "Only allow write endpoint calls from given network. CIDR format, can be specified multiple times.")
Expand Down
8 changes: 8 additions & 0 deletions agent/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,14 @@ type RuntimeConfig struct {
// flag: -domain string
DNSDomain string

// DNSAltDomain can be set to support resolution on an additional
// consul domain. Should end with a dot.
// If left blank, only the primary domain will be used.
//
// hcl: alt_domain = string
// flag: -alt-domain string
DNSAltDomain string

// DNSEnableTruncate is used to enable setting the truncate
// flag for UDP DNS queries. This allows unmodified
// clients to re-query the consul server using TCP
Expand Down
51 changes: 51 additions & 0 deletions agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,53 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
rt.DataDir = dataDir
},
},
{
desc: "-alt-domain",
args: []string{
`-alt-domain=alt`,
`-data-dir=` + dataDir,
},
patch: func(rt *RuntimeConfig) {
rt.DNSAltDomain = "alt"
rt.DataDir = dataDir
},
},
{
desc: "-alt-domain can't be prefixed by DC",
args: []string{
`-datacenter=a`,
`-alt-domain=a.alt`,
`-data-dir=` + dataDir,
},
err: "alt_domain cannot start with {service,connect,node,query,addr,a}",
},
{
desc: "-alt-domain can't be prefixed by service",
args: []string{
`-alt-domain=service.alt`,
`-data-dir=` + dataDir,
},
err: "alt_domain cannot start with {service,connect,node,query,addr,dc1}",
},
{
desc: "-alt-domain can be prefixed by non-keywords",
args: []string{
`-alt-domain=mydomain.alt`,
`-data-dir=` + dataDir,
},
patch: func(rt *RuntimeConfig) {
rt.DNSAltDomain = "mydomain.alt"
rt.DataDir = dataDir
},
},
{
desc: "-alt-domain can't be prefixed by DC",
args: []string{
`-alt-domain=dc1.alt`,
`-data-dir=` + dataDir,
},
err: "alt_domain cannot start with {service,connect,node,query,addr,dc1}",
},
{
desc: "-enable-script-checks",
args: []string{
Expand Down Expand Up @@ -3163,6 +3210,7 @@ func TestFullConfig(t *testing.T) {
"discard_check_output": true,
"discovery_max_stale": "5s",
"domain": "7W1xXSqd",
"alt_domain": "1789hsd",
"dns_config": {
"allow_stale": true,
"a_record_limit": 29907,
Expand Down Expand Up @@ -3741,6 +3789,7 @@ func TestFullConfig(t *testing.T) {
discard_check_output = true
discovery_max_stale = "5s"
domain = "7W1xXSqd"
alt_domain = "1789hsd"
dns_config {
allow_stale = true
a_record_limit = 29907
Expand Down Expand Up @@ -4396,6 +4445,7 @@ func TestFullConfig(t *testing.T) {
DNSAllowStale: true,
DNSDisableCompression: true,
DNSDomain: "7W1xXSqd",
DNSAltDomain: "1789hsd",
DNSEnableTruncate: true,
DNSMaxStale: 29685 * time.Second,
DNSNodeTTL: 7084 * time.Second,
Expand Down Expand Up @@ -5203,6 +5253,7 @@ func TestSanitize(t *testing.T) {
"DNSAllowStale": false,
"DNSDisableCompression": false,
"DNSDomain": "",
"DNSAltDomain": "",
"DNSEnableTruncate": false,
"DNSMaxStale": "0s",
"DNSNodeMetaTXT": false,
Expand Down
44 changes: 32 additions & 12 deletions agent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ type dnsConfig struct {
// service discovery endpoints using a DNS interface.
type DNSServer struct {
*dns.Server
agent *Agent
mux *dns.ServeMux
domain string
logger *log.Logger
agent *Agent
mux *dns.ServeMux
domain string
altDomain string
logger *log.Logger

// config stores the config as an atomic value (for hot-reloading). It is always of type *dnsConfig
config atomic.Value
Expand All @@ -92,13 +93,15 @@ type DNSServer struct {
}

func NewDNSServer(a *Agent) (*DNSServer, error) {
// Make sure domain is FQDN, make it case insensitive for ServeMux
// Make sure domains are FQDN, make them case insensitive for ServeMux
domain := dns.Fqdn(strings.ToLower(a.config.DNSDomain))
altDomain := dns.Fqdn(strings.ToLower(a.config.DNSAltDomain))

srv := &DNSServer{
agent: a,
domain: domain,
logger: a.logger,
agent: a,
domain: domain,
altDomain: altDomain,
logger: a.logger,
}
cfg, err := GetDNSConfig(a.config)
if err != nil {
Expand Down Expand Up @@ -183,6 +186,9 @@ func (d *DNSServer) ListenAndServe(network, addr string, notif func()) error {
d.mux = dns.NewServeMux()
d.mux.HandleFunc("arpa.", d.handlePtr)
d.mux.HandleFunc(d.domain, d.handleQuery)
if d.altDomain != "" {
d.mux.HandleFunc(d.altDomain, d.handleQuery)
}
d.toggleRecursorHandlerFromConfig(cfg)

d.Server = &dns.Server{
Expand Down Expand Up @@ -530,7 +536,7 @@ func (d *DNSServer) doDispatch(network string, remoteAddr net.Addr, req, resp *d

// Get the QName without the domain suffix
qName := strings.ToLower(dns.Fqdn(req.Question[0].Name))
qName = strings.TrimSuffix(qName, d.domain)
akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved
qName = d.trimDomain(qName)

// Split into the label parts
labels := dns.SplitDomainName(qName)
Expand Down Expand Up @@ -684,6 +690,20 @@ INVALID:
return
}

func (d *DNSServer) trimDomain(query string) string {
longer := d.domain
shorter := d.altDomain

if len(shorter) > len(longer) {
longer, shorter = shorter, longer
}

if strings.HasSuffix(query, longer) {
return strings.TrimSuffix(query, longer)
}
return strings.TrimSuffix(query, shorter)
}

// nodeLookup is used to handle a node query
func (d *DNSServer) nodeLookup(cfg *dnsConfig, network, datacenter, node string, req, resp *dns.Msg, maxRecursionLevel int) {
// Only handle ANY, A, AAAA, and TXT type requests
Expand Down Expand Up @@ -1602,10 +1622,10 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {
// resolveCNAME is used to recursively resolve CNAME records
func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel int) []dns.RR {
// If the CNAME record points to a Consul address, resolve it internally
// Convert query to lowercase because DNS is case insensitive; d.domain is
// already converted
// Convert query to lowercase because DNS is case insensitive; d.domain and
// d.altDomain are already converted

if strings.HasSuffix(strings.ToLower(name), "."+d.domain) {
akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved
if ln := strings.ToLower(name); strings.HasSuffix(ln, "."+d.domain) || strings.HasSuffix(ln, "."+d.altDomain) {
if maxRecursionLevel < 1 {
d.logger.Printf("[ERR] dns: Infinite recursion detected for %s, won't perform any CNAME resolution.", name)
return nil
Expand Down
Loading