mirror of https://github.com/status-im/consul.git
Merge pull request #4215 from hashicorp/feature/config-node-meta-dns-txt
Add configuration entry to control including TXT records for node meta in DNS responses
This commit is contained in:
commit
0d4e8676d1
|
@ -592,6 +592,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
|
|||
DNSRecursors: dnsRecursors,
|
||||
DNSServiceTTL: dnsServiceTTL,
|
||||
DNSUDPAnswerLimit: b.intVal(c.DNS.UDPAnswerLimit),
|
||||
DNSNodeMetaTXT: b.boolValWithDefault(c.DNS.NodeMetaTXT, true),
|
||||
|
||||
// HTTP
|
||||
HTTPPort: httpPort,
|
||||
|
@ -1010,13 +1011,18 @@ func (b *Builder) serviceVal(v *ServiceDefinition) *structs.ServiceDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Builder) boolVal(v *bool) bool {
|
||||
func (b *Builder) boolValWithDefault(v *bool, default_val bool) bool {
|
||||
if v == nil {
|
||||
return false
|
||||
return default_val
|
||||
}
|
||||
|
||||
return *v
|
||||
}
|
||||
|
||||
func (b *Builder) boolVal(v *bool) bool {
|
||||
return b.boolValWithDefault(v, false)
|
||||
}
|
||||
|
||||
func (b *Builder) durationVal(name string, v *string) (d time.Duration) {
|
||||
if v == nil {
|
||||
return 0
|
||||
|
|
|
@ -360,6 +360,7 @@ type DNS struct {
|
|||
RecursorTimeout *string `json:"recursor_timeout,omitempty" hcl:"recursor_timeout" mapstructure:"recursor_timeout"`
|
||||
ServiceTTL map[string]string `json:"service_ttl,omitempty" hcl:"service_ttl" mapstructure:"service_ttl"`
|
||||
UDPAnswerLimit *int `json:"udp_answer_limit,omitempty" hcl:"udp_answer_limit" mapstructure:"udp_answer_limit"`
|
||||
NodeMetaTXT *bool `json:"enable_additional_node_meta_txt,omitempty" hcl:"enable_additional_node_meta_txt" mapstructure:"enable_additional_node_meta_txt"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
|
|
|
@ -281,6 +281,11 @@ type RuntimeConfig struct {
|
|||
// hcl: dns_config { udp_answer_limit = int }
|
||||
DNSUDPAnswerLimit int
|
||||
|
||||
// DNSNodeMetaTXT controls whether DNS queries will synthesize
|
||||
// TXT records for the node metadata and add them when not specifically
|
||||
// request (query type = TXT). If unset this will default to true
|
||||
DNSNodeMetaTXT bool
|
||||
|
||||
// DNSRecursors can be set to allow the DNS servers to recursively
|
||||
// resolve non-consul domains.
|
||||
//
|
||||
|
|
|
@ -3371,6 +3371,7 @@ func TestFullConfig(t *testing.T) {
|
|||
DNSRecursors: []string{"63.38.39.58", "92.49.18.18"},
|
||||
DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second},
|
||||
DNSUDPAnswerLimit: 29909,
|
||||
DNSNodeMetaTXT: true,
|
||||
DataDir: dataDir,
|
||||
Datacenter: "rzo029wg",
|
||||
DevMode: true,
|
||||
|
@ -4043,6 +4044,7 @@ func TestSanitize(t *testing.T) {
|
|||
"DNSDomain": "",
|
||||
"DNSEnableTruncate": false,
|
||||
"DNSMaxStale": "0s",
|
||||
"DNSNodeMetaTXT": false,
|
||||
"DNSNodeTTL": "0s",
|
||||
"DNSOnlyPassing": false,
|
||||
"DNSPort": 0,
|
||||
|
|
31
agent/dns.go
31
agent/dns.go
|
@ -51,6 +51,7 @@ type dnsConfig struct {
|
|||
ServiceTTL map[string]time.Duration
|
||||
UDPAnswerLimit int
|
||||
ARecordLimit int
|
||||
NodeMetaTXT bool
|
||||
}
|
||||
|
||||
// DNSServer is used to wrap an Agent and expose various
|
||||
|
@ -109,6 +110,7 @@ func GetDNSConfig(conf *config.RuntimeConfig) *dnsConfig {
|
|||
SegmentName: conf.SegmentName,
|
||||
ServiceTTL: conf.DNSServiceTTL,
|
||||
UDPAnswerLimit: conf.DNSUDPAnswerLimit,
|
||||
NodeMetaTXT: conf.DNSNodeMetaTXT,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,7 +376,7 @@ func (d *DNSServer) nameservers(edns bool) (ns []dns.RR, extra []dns.RR) {
|
|||
}
|
||||
ns = append(ns, nsrr)
|
||||
|
||||
glue := d.formatNodeRecord(nil, addr, fqdn, dns.TypeANY, d.config.NodeTTL, edns)
|
||||
glue := d.formatNodeRecord(nil, addr, fqdn, dns.TypeANY, d.config.NodeTTL, edns, false)
|
||||
extra = append(extra, glue...)
|
||||
|
||||
// don't provide more than 3 servers
|
||||
|
@ -582,7 +584,7 @@ RPC:
|
|||
n := out.NodeServices.Node
|
||||
edns := req.IsEdns0() != nil
|
||||
addr := d.agent.TranslateAddress(datacenter, n.Address, n.TaggedAddresses)
|
||||
records := d.formatNodeRecord(out.NodeServices.Node, addr, req.Question[0].Name, qType, d.config.NodeTTL, edns)
|
||||
records := d.formatNodeRecord(out.NodeServices.Node, addr, req.Question[0].Name, qType, d.config.NodeTTL, edns, true)
|
||||
if records != nil {
|
||||
resp.Answer = append(resp.Answer, records...)
|
||||
}
|
||||
|
@ -610,7 +612,7 @@ func encodeKVasRFC1464(key, value string) (txt string) {
|
|||
}
|
||||
|
||||
// formatNodeRecord takes a Node and returns an A, AAAA, TXT or CNAME record
|
||||
func (d *DNSServer) formatNodeRecord(node *structs.Node, addr, qName string, qType uint16, ttl time.Duration, edns bool) (records []dns.RR) {
|
||||
func (d *DNSServer) formatNodeRecord(node *structs.Node, addr, qName string, qType uint16, ttl time.Duration, edns, answer bool) (records []dns.RR) {
|
||||
// Parse the IP
|
||||
ip := net.ParseIP(addr)
|
||||
var ipv4 net.IP
|
||||
|
@ -671,7 +673,20 @@ func (d *DNSServer) formatNodeRecord(node *structs.Node, addr, qName string, qTy
|
|||
}
|
||||
}
|
||||
|
||||
if node != nil && (qType == dns.TypeANY || qType == dns.TypeTXT) {
|
||||
node_meta_txt := false
|
||||
|
||||
if node == nil {
|
||||
node_meta_txt = false
|
||||
} else if answer {
|
||||
node_meta_txt = true
|
||||
} else {
|
||||
// Use configuration when the TXT RR would
|
||||
// end up in the Additional section of the
|
||||
// DNS response
|
||||
node_meta_txt = d.config.NodeMetaTXT
|
||||
}
|
||||
|
||||
if node_meta_txt {
|
||||
for key, value := range node.Meta {
|
||||
txt := value
|
||||
if !strings.HasPrefix(strings.ToLower(key), "rfc1035-") {
|
||||
|
@ -782,8 +797,8 @@ func (d *DNSServer) trimTCPResponse(req, resp *dns.Msg) (trimmed bool) {
|
|||
originalNumRecords := len(resp.Answer)
|
||||
|
||||
// It is not possible to return more than 4k records even with compression
|
||||
// Since we are performing binary search it is not a big deal, but it
|
||||
// improves a bit performance, even with binary search
|
||||
// Since we are performing binary search it is not a big deal, but it
|
||||
// improves a bit performance, even with binary search
|
||||
truncateAt := 4096
|
||||
if req.Question[0].Qtype == dns.TypeSRV {
|
||||
// More than 1024 SRV records do not fit in 64k
|
||||
|
@ -1143,7 +1158,7 @@ func (d *DNSServer) serviceNodeRecords(dc string, nodes structs.CheckServiceNode
|
|||
handled[addr] = struct{}{}
|
||||
|
||||
// Add the node record
|
||||
records := d.formatNodeRecord(node.Node, addr, qName, qType, ttl, edns)
|
||||
records := d.formatNodeRecord(node.Node, addr, qName, qType, ttl, edns, true)
|
||||
if records != nil {
|
||||
resp.Answer = append(resp.Answer, records...)
|
||||
count++
|
||||
|
@ -1192,7 +1207,7 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes
|
|||
}
|
||||
|
||||
// Add the extra record
|
||||
records := d.formatNodeRecord(node.Node, addr, srvRec.Target, dns.TypeANY, ttl, edns)
|
||||
records := d.formatNodeRecord(node.Node, addr, srvRec.Target, dns.TypeANY, ttl, edns, false)
|
||||
if len(records) > 0 {
|
||||
// Use the node address if it doesn't differ from the service address
|
||||
if addr == node.Node.Address {
|
||||
|
|
|
@ -472,6 +472,51 @@ func TestDNS_NodeLookup_TXT(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDNS_NodeLookup_TXT_DontSuppress(t *testing.T) {
|
||||
a := NewTestAgent(t.Name(), `dns_config = { enable_additional_node_meta_txt = false }`)
|
||||
defer a.Shutdown()
|
||||
|
||||
args := &structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "google",
|
||||
Address: "127.0.0.1",
|
||||
NodeMeta: map[string]string{
|
||||
"rfc1035-00": "value0",
|
||||
"key0": "value1",
|
||||
},
|
||||
}
|
||||
|
||||
var out struct{}
|
||||
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("google.node.consul.", dns.TypeTXT)
|
||||
|
||||
c := new(dns.Client)
|
||||
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should have the 1 TXT record reply
|
||||
if len(in.Answer) != 2 {
|
||||
t.Fatalf("Bad: %#v", in)
|
||||
}
|
||||
|
||||
txtRec, ok := in.Answer[0].(*dns.TXT)
|
||||
if !ok {
|
||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||
}
|
||||
if len(txtRec.Txt) != 1 {
|
||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||
}
|
||||
if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" {
|
||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNS_NodeLookup_ANY(t *testing.T) {
|
||||
a := NewTestAgent(t.Name(), ``)
|
||||
defer a.Shutdown()
|
||||
|
@ -510,7 +555,46 @@ func TestDNS_NodeLookup_ANY(t *testing.T) {
|
|||
},
|
||||
}
|
||||
verify.Values(t, "answer", in.Answer, wantAnswer)
|
||||
}
|
||||
|
||||
func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) {
|
||||
a := NewTestAgent(t.Name(), `dns_config = { enable_additional_node_meta_txt = false }`)
|
||||
defer a.Shutdown()
|
||||
|
||||
args := &structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "bar",
|
||||
Address: "127.0.0.1",
|
||||
NodeMeta: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
var out struct{}
|
||||
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("bar.node.consul.", dns.TypeANY)
|
||||
|
||||
c := new(dns.Client)
|
||||
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
wantAnswer := []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
|
||||
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
|
||||
},
|
||||
&dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa},
|
||||
Txt: []string{"key=value"},
|
||||
},
|
||||
}
|
||||
verify.Values(t, "answer", in.Answer, wantAnswer)
|
||||
}
|
||||
|
||||
func TestDNS_EDNS0(t *testing.T) {
|
||||
|
@ -4613,6 +4697,93 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDNS_ServiceLookup_MetaTXT(t *testing.T) {
|
||||
a := NewTestAgent(t.Name(), `dns_config = { enable_additional_node_meta_txt = true }`)
|
||||
defer a.Shutdown()
|
||||
|
||||
args := &structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "bar",
|
||||
Address: "127.0.0.1",
|
||||
NodeMeta: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Service: &structs.NodeService{
|
||||
Service: "db",
|
||||
Tags: []string{"master"},
|
||||
Port: 12345,
|
||||
},
|
||||
}
|
||||
|
||||
var out struct{}
|
||||
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("db.service.consul.", dns.TypeSRV)
|
||||
|
||||
c := new(dns.Client)
|
||||
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
wantAdditional := []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
|
||||
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
|
||||
},
|
||||
&dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa},
|
||||
Txt: []string{"key=value"},
|
||||
},
|
||||
}
|
||||
verify.Values(t, "additional", in.Extra, wantAdditional)
|
||||
}
|
||||
|
||||
func TestDNS_ServiceLookup_SuppressTXT(t *testing.T) {
|
||||
a := NewTestAgent(t.Name(), `dns_config = { enable_additional_node_meta_txt = false }`)
|
||||
defer a.Shutdown()
|
||||
|
||||
// Register a node with a service.
|
||||
args := &structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "bar",
|
||||
Address: "127.0.0.1",
|
||||
NodeMeta: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Service: &structs.NodeService{
|
||||
Service: "db",
|
||||
Tags: []string{"master"},
|
||||
Port: 12345,
|
||||
},
|
||||
}
|
||||
|
||||
var out struct{}
|
||||
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("db.service.consul.", dns.TypeSRV)
|
||||
|
||||
c := new(dns.Client)
|
||||
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
wantAdditional := []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
|
||||
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
|
||||
},
|
||||
}
|
||||
verify.Values(t, "additional", in.Extra, wantAdditional)
|
||||
}
|
||||
|
||||
func TestDNS_AddressLookup(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := NewTestAgent(t.Name(), "")
|
||||
|
|
|
@ -777,6 +777,12 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
[RFC 6724](https://tools.ietf.org/html/rfc6724) and as a result it should
|
||||
be increasingly uncommon to need to change this value with modern
|
||||
resolvers).
|
||||
|
||||
* <a name="enable_additional_node_meta_txt"></a><a href="#enable_additional_node_meta_txt">`enable_additional_node_meta_txt`</a> -
|
||||
When set to true, Consul will add TXT records for Node metadata into the Additional section of the DNS responses for several
|
||||
query types such as SRV queries. When set to false those records are emitted. This does not impact the behavior of those
|
||||
same TXT records when they would be added to the Answer section of the response like when querying with type TXT or ANY. This
|
||||
defaults to true.
|
||||
|
||||
* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
|
||||
[`-domain` command-line flag](#_domain).
|
||||
|
|
Loading…
Reference in New Issue