mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
fingerprint: add DNS address and port to Consul fingerprint (#19969)
In order to provide a DNS address and port to Connect tasks configured for transparent proxy, we need to fingerprint the Consul DNS address and port. The client will pass this address/port to the iptables configuration provided to the `consul-cni` plugin. Ref: https://github.com/hashicorp/nomad/issues/10628
This commit is contained in:
3
.changelog/19969.txt
Normal file
3
.changelog/19969.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
fingerprint: Added a fingerprint for Consul DNS address and port
|
||||||
|
```
|
||||||
@@ -5,6 +5,7 @@ package fingerprint
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/go-sockaddr"
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
agentconsul "github.com/hashicorp/nomad/command/agent/consul"
|
agentconsul "github.com/hashicorp/nomad/command/agent/consul"
|
||||||
"github.com/hashicorp/nomad/helper"
|
"github.com/hashicorp/nomad/helper"
|
||||||
@@ -165,6 +167,8 @@ func (cfs *consulFingerprintState) initialize(cfg *config.ConsulConfig, logger h
|
|||||||
"consul.grpc": cfs.grpc(consulConfig.Scheme, logger),
|
"consul.grpc": cfs.grpc(consulConfig.Scheme, logger),
|
||||||
"consul.ft.namespaces": cfs.namespaces,
|
"consul.ft.namespaces": cfs.namespaces,
|
||||||
"consul.partition": cfs.partition,
|
"consul.partition": cfs.partition,
|
||||||
|
"consul.dns.port": cfs.dnsPort,
|
||||||
|
"consul.dns.addr": cfs.dnsAddr(logger),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cfs.extractors = map[string]consulExtractor{
|
cfs.extractors = map[string]consulExtractor{
|
||||||
@@ -178,6 +182,8 @@ func (cfs *consulFingerprintState) initialize(cfg *config.ConsulConfig, logger h
|
|||||||
fmt.Sprintf("consul.%s.grpc", cfg.Name): cfs.grpc(consulConfig.Scheme, logger),
|
fmt.Sprintf("consul.%s.grpc", cfg.Name): cfs.grpc(consulConfig.Scheme, logger),
|
||||||
fmt.Sprintf("consul.%s.ft.namespaces", cfg.Name): cfs.namespaces,
|
fmt.Sprintf("consul.%s.ft.namespaces", cfg.Name): cfs.namespaces,
|
||||||
fmt.Sprintf("consul.%s.partition", cfg.Name): cfs.partition,
|
fmt.Sprintf("consul.%s.partition", cfg.Name): cfs.partition,
|
||||||
|
fmt.Sprintf("consul.%s.dns.port", cfg.Name): cfs.dnsPort,
|
||||||
|
fmt.Sprintf("consul.%s.dns.addr", cfg.Name): cfs.dnsAddr(logger),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +197,7 @@ func (cfs *consulFingerprintState) query(logger hclog.Logger) agentconsul.Self {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// indicate consul no longer available
|
// indicate consul no longer available
|
||||||
if cfs.isAvailable {
|
if cfs.isAvailable {
|
||||||
logger.Info("consul agent is unavailable: %v", err)
|
logger.Info("consul agent is unavailable", "error", err)
|
||||||
}
|
}
|
||||||
cfs.isAvailable = false
|
cfs.isAvailable = false
|
||||||
cfs.nextCheck = time.Time{} // force check on next interval
|
cfs.nextCheck = time.Time{} // force check on next interval
|
||||||
@@ -298,6 +304,84 @@ func (cfs *consulFingerprintState) grpcTLSPort(info agentconsul.Self) (string, b
|
|||||||
return fmt.Sprintf("%d", int(p)), ok
|
return fmt.Sprintf("%d", int(p)), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfs *consulFingerprintState) dnsPort(info agentconsul.Self) (string, bool) {
|
||||||
|
p, ok := info["DebugConfig"]["DNSPort"].(float64)
|
||||||
|
return fmt.Sprintf("%d", int(p)), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsAddr fingerprints the Consul DNS address, but only if Nomad can use it
|
||||||
|
// usefully to provide an iptables rule to a task
|
||||||
|
func (cfs *consulFingerprintState) dnsAddr(logger hclog.Logger) func(info agentconsul.Self) (string, bool) {
|
||||||
|
return func(info agentconsul.Self) (string, bool) {
|
||||||
|
|
||||||
|
var listenOnEveryIP bool
|
||||||
|
|
||||||
|
dnsAddrs, ok := info["DebugConfig"]["DNSAddrs"].([]any)
|
||||||
|
if !ok {
|
||||||
|
logger.Warn("Consul returned invalid addresses.dns config",
|
||||||
|
"value", info["DebugConfig"]["DNSAddrs"])
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dnsAddrs {
|
||||||
|
dnsAddr, ok := d.(string)
|
||||||
|
if !ok {
|
||||||
|
logger.Warn("Consul returned invalid addresses.dns config",
|
||||||
|
"value", info["DebugConfig"]["DNSAddrs"])
|
||||||
|
return "", false
|
||||||
|
|
||||||
|
}
|
||||||
|
dnsAddr = strings.TrimPrefix(dnsAddr, "tcp://")
|
||||||
|
dnsAddr = strings.TrimPrefix(dnsAddr, "udp://")
|
||||||
|
|
||||||
|
parsed, err := netip.ParseAddrPort(dnsAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("could not parse Consul addresses.dns config",
|
||||||
|
"value", dnsAddr, "error", err)
|
||||||
|
return "", false // response is somehow malformed
|
||||||
|
}
|
||||||
|
|
||||||
|
// only addresses we can use for an iptables rule from a
|
||||||
|
// container to the host will be fingerprinted
|
||||||
|
if parsed.Addr().IsUnspecified() {
|
||||||
|
listenOnEveryIP = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !parsed.Addr().IsLoopback() {
|
||||||
|
return parsed.Addr().String(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if Consul DNS is bound on 0.0.0.0, we want to fingerprint the private
|
||||||
|
// IP (or at worst, the public IP) of the host so that we have a valid
|
||||||
|
// IP address for the iptables rule
|
||||||
|
if listenOnEveryIP {
|
||||||
|
|
||||||
|
privateIP, err := sockaddr.GetPrivateIP()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("could not query network interfaces", "error", err)
|
||||||
|
return "", false // something is very wrong, so bail out
|
||||||
|
}
|
||||||
|
if privateIP != "" {
|
||||||
|
return privateIP, true
|
||||||
|
}
|
||||||
|
publicIP, err := sockaddr.GetPublicIP()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("could not query network interfaces", "error", err)
|
||||||
|
return "", false // something is very wrong, so bail out
|
||||||
|
}
|
||||||
|
if publicIP != "" {
|
||||||
|
return publicIP, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we've hit here, Consul is bound on localhost and we won't be able
|
||||||
|
// to configure container DNS to use it, but we also don't want to have
|
||||||
|
// the fingerprinter return an error
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfs *consulFingerprintState) namespaces(info agentconsul.Self) (string, bool) {
|
func (cfs *consulFingerprintState) namespaces(info agentconsul.Self) (string, bool) {
|
||||||
return strconv.FormatBool(agentconsul.Namespaces(info)), true
|
return strconv.FormatBool(agentconsul.Namespaces(info)), true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -491,6 +491,105 @@ func TestConsulFingerprint_partition(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConsulFingerprint_dns(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
cfs := consulFingerprintState{}
|
||||||
|
|
||||||
|
t.Run("dns port not enabled", func(t *testing.T) {
|
||||||
|
port, ok := cfs.dnsPort(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSPort": -1.0}, // JSON numbers are floats
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "-1", port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-default port value", func(t *testing.T) {
|
||||||
|
port, ok := cfs.dnsPort(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSPort": 8601.0}, // JSON numbers are floats
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "8601", port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing port", func(t *testing.T) {
|
||||||
|
port, ok := cfs.dnsPort(agentconsul.Self{
|
||||||
|
"DebugConfig": {},
|
||||||
|
})
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "0", port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("malformed port", func(t *testing.T) {
|
||||||
|
port, ok := cfs.dnsPort(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSPort": "A"},
|
||||||
|
})
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "0", port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get first IP", func(t *testing.T) {
|
||||||
|
addr, ok := cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {
|
||||||
|
"DNSAddrs": []any{"tcp://192.168.1.170:8601", "udp://192.168.1.171:8601"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "192.168.1.170", addr)
|
||||||
|
|
||||||
|
addr, ok = cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSAddrs": []any{"tcp://[2001:0db8:85a3::8a2e:0370:7334]:8601"}},
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "2001:db8:85a3::8a2e:370:7334", addr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("loopback address", func(t *testing.T) {
|
||||||
|
addr, ok := cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {
|
||||||
|
"DNSAddrs": []any{"tcp://127.0.0.1:8601", "udp://127.0.0.1:8601"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "", addr)
|
||||||
|
|
||||||
|
addr, ok = cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSAddrs": []any{"tcp://[::1]:8601"}},
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.Eq(t, "", addr)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fallback to private or public IP", func(t *testing.T) {
|
||||||
|
addr, ok := cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {
|
||||||
|
"DNSAddrs": []any{"tcp://0.0.0.0:8601", "udp://0.0.0.0:8601"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
must.True(t, ok)
|
||||||
|
must.NotEq(t, "", addr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("malformed DNSAddrs", func(t *testing.T) {
|
||||||
|
addr, ok := cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSAddrs": []int{0}}})
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "", addr)
|
||||||
|
|
||||||
|
addr, ok = cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSAddrs": []any{0}}})
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "", addr)
|
||||||
|
|
||||||
|
addr, ok = cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
|
||||||
|
"DebugConfig": {"DNSAddrs": []any{"tcp://XXXXX"}}})
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "", addr)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
|
func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
|
|
||||||
@@ -510,6 +609,7 @@ func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
|
|||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
must.Eq(t, map[string]string{
|
must.Eq(t, map[string]string{
|
||||||
"consul.datacenter": "dc1",
|
"consul.datacenter": "dc1",
|
||||||
|
"consul.dns.port": "8600",
|
||||||
"consul.revision": "3c1c22679",
|
"consul.revision": "3c1c22679",
|
||||||
"consul.segment": "seg1",
|
"consul.segment": "seg1",
|
||||||
"consul.server": "true",
|
"consul.server": "true",
|
||||||
@@ -564,6 +664,7 @@ func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
|
|||||||
"consul.version": "1.9.5",
|
"consul.version": "1.9.5",
|
||||||
"consul.connect": "true",
|
"consul.connect": "true",
|
||||||
"consul.grpc": "8502",
|
"consul.grpc": "8502",
|
||||||
|
"consul.dns.port": "8600",
|
||||||
"consul.ft.namespaces": "false",
|
"consul.ft.namespaces": "false",
|
||||||
"unique.consul.name": "HAL9000",
|
"unique.consul.name": "HAL9000",
|
||||||
}, resp3.Attributes)
|
}, resp3.Attributes)
|
||||||
@@ -600,6 +701,8 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
|
|||||||
"consul.ft.namespaces": "true",
|
"consul.ft.namespaces": "true",
|
||||||
"consul.connect": "true",
|
"consul.connect": "true",
|
||||||
"consul.grpc": "8502",
|
"consul.grpc": "8502",
|
||||||
|
"consul.dns.addr": "192.168.1.117",
|
||||||
|
"consul.dns.port": "8600",
|
||||||
"consul.partition": "default",
|
"consul.partition": "default",
|
||||||
"unique.consul.name": "HAL9000",
|
"unique.consul.name": "HAL9000",
|
||||||
}, resp.Attributes)
|
}, resp.Attributes)
|
||||||
@@ -649,6 +752,8 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
|
|||||||
"consul.ft.namespaces": "true",
|
"consul.ft.namespaces": "true",
|
||||||
"consul.connect": "true",
|
"consul.connect": "true",
|
||||||
"consul.grpc": "8502",
|
"consul.grpc": "8502",
|
||||||
|
"consul.dns.addr": "192.168.1.117",
|
||||||
|
"consul.dns.port": "8600",
|
||||||
"consul.partition": "default",
|
"consul.partition": "default",
|
||||||
"unique.consul.name": "HAL9000",
|
"unique.consul.name": "HAL9000",
|
||||||
}, resp3.Attributes)
|
}, resp3.Attributes)
|
||||||
|
|||||||
@@ -129,8 +129,8 @@
|
|||||||
"ConsulServerHealthInterval": "10ms",
|
"ConsulServerHealthInterval": "10ms",
|
||||||
"DNSARecordLimit": 0,
|
"DNSARecordLimit": 0,
|
||||||
"DNSAddrs": [
|
"DNSAddrs": [
|
||||||
"tcp://127.0.0.1:8600",
|
"tcp://192.168.1.117:8600",
|
||||||
"udp://127.0.0.1:8600"
|
"udp://192.168.1.117:8600"
|
||||||
],
|
],
|
||||||
"DNSAllowStale": true,
|
"DNSAllowStale": true,
|
||||||
"DNSAltDomain": "",
|
"DNSAltDomain": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user