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

Backport of fingerprint: add DNS address and port to Consul fingerprint into release/1.7.x #19984

Merged
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
3 changes: 3 additions & 0 deletions .changelog/19969.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
fingerprint: Added a fingerprint for Consul DNS address and port
```
86 changes: 85 additions & 1 deletion client/fingerprint/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package fingerprint

import (
"fmt"
"net/netip"
"strconv"
"strings"
"time"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/hashicorp/go-hclog"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/go-version"
agentconsul "github.com/hashicorp/nomad/command/agent/consul"
"github.com/hashicorp/nomad/helper"
Expand Down Expand Up @@ -165,6 +167,8 @@ func (cfs *consulFingerprintState) initialize(cfg *config.ConsulConfig, logger h
"consul.grpc": cfs.grpc(consulConfig.Scheme, logger),
"consul.ft.namespaces": cfs.namespaces,
"consul.partition": cfs.partition,
"consul.dns.port": cfs.dnsPort,
"consul.dns.addr": cfs.dnsAddr(logger),
}
} else {
cfs.extractors = map[string]consulExtractor{
Expand All @@ -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.ft.namespaces", cfg.Name): cfs.namespaces,
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),
}
}

Expand All @@ -191,7 +197,7 @@ func (cfs *consulFingerprintState) query(logger hclog.Logger) agentconsul.Self {
if err != nil {
// indicate consul no longer available
if cfs.isAvailable {
logger.Info("consul agent is unavailable: %v", err)
logger.Info("consul agent is unavailable", "error", err)
}
cfs.isAvailable = false
cfs.nextCheck = time.Time{} // force check on next interval
Expand Down Expand Up @@ -298,6 +304,84 @@ func (cfs *consulFingerprintState) grpcTLSPort(info agentconsul.Self) (string, b
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) {
return strconv.FormatBool(agentconsul.Namespaces(info)), true
}
Expand Down
105 changes: 105 additions & 0 deletions client/fingerprint/consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
ci.Parallel(t)

Expand All @@ -510,6 +609,7 @@ func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
must.NoError(t, err)
must.Eq(t, map[string]string{
"consul.datacenter": "dc1",
"consul.dns.port": "8600",
"consul.revision": "3c1c22679",
"consul.segment": "seg1",
"consul.server": "true",
Expand Down Expand Up @@ -564,6 +664,7 @@ func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
"consul.version": "1.9.5",
"consul.connect": "true",
"consul.grpc": "8502",
"consul.dns.port": "8600",
"consul.ft.namespaces": "false",
"unique.consul.name": "HAL9000",
}, resp3.Attributes)
Expand Down Expand Up @@ -600,6 +701,8 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
"consul.ft.namespaces": "true",
"consul.connect": "true",
"consul.grpc": "8502",
"consul.dns.addr": "192.168.1.117",
"consul.dns.port": "8600",
"consul.partition": "default",
"unique.consul.name": "HAL9000",
}, resp.Attributes)
Expand Down Expand Up @@ -649,6 +752,8 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
"consul.ft.namespaces": "true",
"consul.connect": "true",
"consul.grpc": "8502",
"consul.dns.addr": "192.168.1.117",
"consul.dns.port": "8600",
"consul.partition": "default",
"unique.consul.name": "HAL9000",
}, resp3.Attributes)
Expand Down
4 changes: 2 additions & 2 deletions client/fingerprint/test_fixtures/consul/agent_self_ent.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@
"ConsulServerHealthInterval": "10ms",
"DNSARecordLimit": 0,
"DNSAddrs": [
"tcp://127.0.0.1:8600",
"udp://127.0.0.1:8600"
"tcp://192.168.1.117:8600",
"udp://192.168.1.117:8600"
],
"DNSAllowStale": true,
"DNSAltDomain": "",
Expand Down
Loading