Files
tailscale/wgengine/wgcfg/nmcfg/nmcfg.go
Brad Fitzpatrick 653d0738f9 types/netmap: remove PrivateKey from NetworkMap
It's an unnecessary nuisance having it. We go out of our way to redact
it in so many places when we don't even need it there anyway.

Updates #12639

Change-Id: I5fc72e19e9cf36caeb42cf80ba430873f67167c3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-11-16 15:32:51 -08:00

143 lines
4.3 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package nmcfg converts a controlclient.NetMap into a wgcfg config.
package nmcfg
import (
"bufio"
"cmp"
"fmt"
"net/netip"
"strings"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/wgcfg"
)
func nodeDebugName(n tailcfg.NodeView) string {
name, _, _ := strings.Cut(cmp.Or(n.Name(), n.Hostinfo().Hostname()), ".")
return name
}
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node tailcfg.NodeView, cidr netip.Prefix) bool {
if cidr.Bits() == 0 {
return false
}
if !cidr.IsSingleIP() {
return true
}
for _, selfCIDR := range node.Addresses().All() {
if cidr == selfCIDR {
return false
}
}
return true
}
// WGCfg returns the NetworkMaps's WireGuard configuration.
func WGCfg(pk key.NodePrivate, nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, exitNode tailcfg.StableNodeID) (*wgcfg.Config, error) {
cfg := &wgcfg.Config{
PrivateKey: pk,
Addresses: nm.GetAddresses().AsSlice(),
Peers: make([]wgcfg.Peer, 0, len(nm.Peers)),
}
// Setup log IDs for data plane audit logging.
if nm.SelfNode.Valid() {
canNetworkLog := nm.SelfNode.HasCap(tailcfg.CapabilityDataPlaneAuditLogs)
logExitFlowEnabled := nm.SelfNode.HasCap(tailcfg.NodeAttrLogExitFlows)
if canNetworkLog && nm.SelfNode.DataPlaneAuditLogID() != "" && nm.DomainAuditLogID != "" {
nodeID, errNode := logid.ParsePrivateID(nm.SelfNode.DataPlaneAuditLogID())
if errNode != nil {
logf("[v1] wgcfg: unable to parse node audit log ID: %v", errNode)
}
domainID, errDomain := logid.ParsePrivateID(nm.DomainAuditLogID)
if errDomain != nil {
logf("[v1] wgcfg: unable to parse domain audit log ID: %v", errDomain)
}
if errNode == nil && errDomain == nil {
cfg.NetworkLogging.NodeID = nodeID
cfg.NetworkLogging.DomainID = domainID
cfg.NetworkLogging.LogExitFlowEnabled = logExitFlowEnabled
}
}
}
var skippedExitNode, skippedSubnetRouter, skippedExpired []tailcfg.NodeView
for _, peer := range nm.Peers {
if peer.DiscoKey().IsZero() && peer.HomeDERP() == 0 && !peer.IsWireGuardOnly() {
// Peer predates both DERP and active discovery, we cannot
// communicate with it.
logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key().ShortString())
continue
}
// Skip expired peers; we'll end up failing to connect to them
// anyway, since control intentionally breaks node keys for
// expired peers so that we can't discover endpoints via DERP.
if peer.Expired() {
skippedExpired = append(skippedExpired, peer)
continue
}
cfg.Peers = append(cfg.Peers, wgcfg.Peer{
PublicKey: peer.Key(),
DiscoKey: peer.DiscoKey(),
})
cpeer := &cfg.Peers[len(cfg.Peers)-1]
didExitNodeLog := false
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer().Clone()
cpeer.V6MasqAddr = peer.SelfNodeV6MasqAddrForThisPeer().Clone()
cpeer.IsJailed = peer.IsJailed()
for _, allowedIP := range peer.AllowedIPs().All() {
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
if didExitNodeLog {
// Don't log about both the IPv4 /0 and IPv6 /0.
continue
}
didExitNodeLog = true
skippedExitNode = append(skippedExitNode, peer)
continue
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & netmap.AllowSubnetRoutes) == 0 {
skippedSubnetRouter = append(skippedSubnetRouter, peer)
continue
}
}
cpeer.AllowedIPs = append(cpeer.AllowedIPs, allowedIP)
}
}
logList := func(title string, nodes []tailcfg.NodeView) {
if len(nodes) == 0 {
return
}
logf("[v1] wgcfg: %s from %d nodes: %s", title, len(nodes), logger.ArgWriter(func(bw *bufio.Writer) {
const max = 5
for i, n := range nodes {
if i == max {
fmt.Fprintf(bw, "... +%d", len(nodes)-max)
return
}
if i > 0 {
bw.WriteString(", ")
}
fmt.Fprintf(bw, "%s (%s)", nodeDebugName(n), n.StableID())
}
}))
}
logList("skipped unselected exit nodes", skippedExitNode)
logList("did not accept subnet routes", skippedSubnetRouter)
logList("skipped expired peers", skippedExpired)
return cfg, nil
}