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

fingerprint: add DNS address and port to Consul fingerprint #19969

Merged
merged 5 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
```
74 changes: 73 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,72 @@ 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"].([]string)
if ok {
for _, dnsAddr := range dnsAddrs {
dnsAddr = strings.TrimPrefix(dnsAddr, "tcp://")
dnsAddr = strings.TrimPrefix(dnsAddr, "udp://")

parsed, err := netip.ParseAddrPort(dnsAddr)
if err != nil || !parsed.IsValid() {
logger.Warn("could not parse Consul addresses.dns config", "value", dnsAddr)
tgross marked this conversation as resolved.
Show resolved Hide resolved
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()
tgross marked this conversation as resolved.
Show resolved Hide resolved
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
tgross marked this conversation as resolved.
Show resolved Hide resolved
}
}

func (cfs *consulFingerprintState) namespaces(info agentconsul.Self) (string, bool) {
return strconv.FormatBool(agentconsul.Namespaces(info)), true
}
Expand Down
62 changes: 62 additions & 0 deletions client/fingerprint/consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,64 @@ 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": []string{"tcp://192.168.1.170:8601", "udp://192.168.1.171:8601"},
},
})
must.True(t, ok)
must.Eq(t, "192.168.1.170", addr)
})

t.Run("fallback to private or public IP", func(t *testing.T) {
addr, ok := cfs.dnsAddr(testlog.HCLogger(t))(agentconsul.Self{
"DebugConfig": {
"DNSAddrs": []string{"tcp://0.0.0.0:8601", "udp://0.0.0.0:8601"},
},
})
must.True(t, ok)
must.NotEq(t, "", addr)
})
}

func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
ci.Parallel(t)

Expand All @@ -510,6 +568,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 +623,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 +660,7 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
"consul.ft.namespaces": "true",
"consul.connect": "true",
"consul.grpc": "8502",
"consul.dns_port": "8600",
"consul.partition": "default",
"unique.consul.name": "HAL9000",
}, resp.Attributes)
Expand Down Expand Up @@ -649,6 +710,7 @@ func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
"consul.ft.namespaces": "true",
"consul.connect": "true",
"consul.grpc": "8502",
"consul.dns_port": "8600",
"consul.partition": "default",
"unique.consul.name": "HAL9000",
}, resp3.Attributes)
Expand Down
Loading