exit node dst wip

This commit is contained in:
Claire Wang 2024-03-27 15:45:02 -04:00
parent 92d3f64e95
commit 78c36f53fe
8 changed files with 94 additions and 63 deletions

View File

@ -42,6 +42,7 @@ type setArgsT struct {
acceptDNS bool acceptDNS bool
exitNodeIP string exitNodeIP string
exitNodeAllowLANAccess bool exitNodeAllowLANAccess bool
exitDestinationFlowLogs bool
shieldsUp bool shieldsUp bool
runSSH bool runSSH bool
runWebClient bool runWebClient bool
@ -66,6 +67,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.BoolVar(&setArgs.acceptDNS, "accept-dns", false, "accept DNS configuration from the admin panel") setf.BoolVar(&setArgs.acceptDNS, "accept-dns", false, "accept DNS configuration from the admin panel")
setf.StringVar(&setArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node") setf.StringVar(&setArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node")
setf.BoolVar(&setArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node") setf.BoolVar(&setArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
setf.BoolVar(&setArgs.exitDestinationFlowLogs, "exit-destination-flow-logs", false, "Enable exit node destination in network flow logs")
setf.BoolVar(&setArgs.shieldsUp, "shields-up", false, "don't allow incoming connections") setf.BoolVar(&setArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
setf.BoolVar(&setArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy") setf.BoolVar(&setArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy")
setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS") setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
@ -110,6 +112,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
RouteAll: setArgs.acceptRoutes, RouteAll: setArgs.acceptRoutes,
CorpDNS: setArgs.acceptDNS, CorpDNS: setArgs.acceptDNS,
ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess, ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess,
ExitDestinationFlowLogs: setArgs.exitDestinationFlowLogs,
ShieldsUp: setArgs.shieldsUp, ShieldsUp: setArgs.shieldsUp,
RunSSH: setArgs.runSSH, RunSSH: setArgs.runSSH,
RunWebClient: setArgs.runWebClient, RunWebClient: setArgs.runWebClient,

View File

@ -723,6 +723,7 @@ func init() {
addPrefFlagMapping("auto-update", "AutoUpdate.Apply") addPrefFlagMapping("auto-update", "AutoUpdate.Apply")
addPrefFlagMapping("advertise-connector", "AppConnector") addPrefFlagMapping("advertise-connector", "AppConnector")
addPrefFlagMapping("posture-checking", "PostureChecking") addPrefFlagMapping("posture-checking", "PostureChecking")
addPrefFlagMapping("exit-destination-flow-logs", "ExitDestinationFlowLogs")
} }
func addPrefFlagMapping(flagName string, prefNames ...string) { func addPrefFlagMapping(flagName string, prefNames ...string) {
@ -951,6 +952,8 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]any) {
set(exitNodeIPStr()) set(exitNodeIPStr())
case "exit-node-allow-lan-access": case "exit-node-allow-lan-access":
set(prefs.ExitNodeAllowLANAccess) set(prefs.ExitNodeAllowLANAccess)
case "exit-destination-flow-logs":
set(prefs.ExitDestinationFlowLogs)
case "advertise-tags": case "advertise-tags":
set(strings.Join(prefs.AdvertiseTags, ",")) set(strings.Join(prefs.AdvertiseTags, ","))
case "hostname": case "hostname":

View File

@ -1150,6 +1150,9 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
if setExitNodeID(prefs, st.NetMap) { if setExitNodeID(prefs, st.NetMap) {
prefsChanged = true prefsChanged = true
} }
if setExitDstFlowLogs(prefs) {
prefsChanged = true
}
if applySysPolicy(prefs) { if applySysPolicy(prefs) {
prefsChanged = true prefsChanged = true
} }
@ -1335,6 +1338,15 @@ func applySysPolicy(prefs *ipn.Prefs) (anyChange bool) {
return anyChange return anyChange
} }
func setExitDstFlowLogs(prefs *ipn.Prefs) (anyChange bool) {
fmt.Printf("set exit dst flow pref")
if enable, err := syspolicy.GetBoolean(syspolicy.ExitDestinationFlowLogs, prefs.ExitDestinationFlowLogs); err == nil && prefs.ExitDestinationFlowLogs != enable {
prefs.ExitDestinationFlowLogs = enable
anyChange = true
}
return anyChange
}
var _ controlclient.NetmapDeltaUpdater = (*LocalBackend)(nil) var _ controlclient.NetmapDeltaUpdater = (*LocalBackend)(nil)
// UpdateNetmapDelta implements controlclient.NetmapDeltaUpdater. // UpdateNetmapDelta implements controlclient.NetmapDeltaUpdater.
@ -3247,6 +3259,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
// everything in this function treats b.prefs as completely new // everything in this function treats b.prefs as completely new
// anyway. No-op if no exit node resolution is needed. // anyway. No-op if no exit node resolution is needed.
setExitNodeID(newp, netMap) setExitNodeID(newp, netMap)
setExitDstFlowLogs(newp)
// applySysPolicy does likewise so we can also ignore its return value. // applySysPolicy does likewise so we can also ignore its return value.
applySysPolicy(newp) applySysPolicy(newp)
// We do this to avoid holding the lock while doing everything else. // We do this to avoid holding the lock while doing everything else.
@ -3628,6 +3641,8 @@ func (b *LocalBackend) authReconfig() {
return return
} }
cfg.NetworkLogging.ExitDestinationFlowLogs = prefs.ExitDestinationFlowLogs()
oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS()) oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS())
rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute) rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute)

View File

@ -109,6 +109,9 @@ type Prefs struct {
// routed directly or via the exit node. // routed directly or via the exit node.
ExitNodeAllowLANAccess bool ExitNodeAllowLANAccess bool
// ExitDestinationFlowLogs indicates whether exit node destination is recorded in network flow logs.
ExitDestinationFlowLogs bool
// CorpDNS specifies whether to install the Tailscale network's // CorpDNS specifies whether to install the Tailscale network's
// DNS configuration, if it exists. // DNS configuration, if it exists.
CorpDNS bool CorpDNS bool
@ -277,6 +280,7 @@ type MaskedPrefs struct {
ControlURLSet bool `json:",omitempty"` ControlURLSet bool `json:",omitempty"`
RouteAllSet bool `json:",omitempty"` RouteAllSet bool `json:",omitempty"`
AllowSingleHostsSet bool `json:",omitempty"` AllowSingleHostsSet bool `json:",omitempty"`
ExitDestinationFlowLogsSet bool `json:",omitempty"`
ExitNodeIDSet bool `json:",omitempty"` ExitNodeIDSet bool `json:",omitempty"`
ExitNodeIPSet bool `json:",omitempty"` ExitNodeIPSet bool `json:",omitempty"`
ExitNodeAllowLANAccessSet bool `json:",omitempty"` ExitNodeAllowLANAccessSet bool `json:",omitempty"`
@ -475,6 +479,9 @@ func (p *Prefs) pretty(goos string) string {
if p.ShieldsUp { if p.ShieldsUp {
sb.WriteString("shields=true ") sb.WriteString("shields=true ")
} }
if p.ExitDestinationFlowLogs {
sb.WriteString("exitdestinationflowlogs=true ")
}
if p.ExitNodeIP.IsValid() { if p.ExitNodeIP.IsValid() {
fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess) fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess)
} else if !p.ExitNodeID.IsZero() { } else if !p.ExitNodeID.IsZero() {
@ -545,6 +552,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.ExitNodeID == p2.ExitNodeID && p.ExitNodeID == p2.ExitNodeID &&
p.ExitNodeIP == p2.ExitNodeIP && p.ExitNodeIP == p2.ExitNodeIP &&
p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess && p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
p.ExitDestinationFlowLogs == p2.ExitDestinationFlowLogs &&
p.CorpDNS == p2.CorpDNS && p.CorpDNS == p2.CorpDNS &&
p.RunSSH == p2.RunSSH && p.RunSSH == p2.RunSSH &&
p.RunWebClient == p2.RunWebClient && p.RunWebClient == p2.RunWebClient &&

View File

@ -67,7 +67,7 @@ const (
// The default is 0 unless otherwise stated. // The default is 0 unless otherwise stated.
LogSCMInteractions Key = "LogSCMInteractions" LogSCMInteractions Key = "LogSCMInteractions"
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock" FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
ExitDestinationFlowLogs Key = "ExitDestinationFlowLogs"
// PostureChecking indicates if posture checking is enabled and the client shall gather // PostureChecking indicates if posture checking is enabled and the client shall gather
// posture data. // posture data.
// Key is a string value that specifies an option: "always", "never", "user-decides". // Key is a string value that specifies an option: "always", "never", "user-decides".

View File

@ -92,7 +92,7 @@ var testClient *http.Client
// The IP protocol and source port are always zero. // The IP protocol and source port are always zero.
// The sock is used to populated the PhysicalTraffic field in Message. // The sock is used to populated the PhysicalTraffic field in Message.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor) error { func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor, enableExitDstFlowLogs bool) error {
nl.mu.Lock() nl.mu.Lock()
defer nl.mu.Unlock() defer nl.mu.Unlock()
if nl.logger != nil { if nl.logger != nil {
@ -130,7 +130,7 @@ func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID lo
addrs := nl.addrs addrs := nl.addrs
prefixes := nl.prefixes prefixes := nl.prefixes
nl.mu.Unlock() nl.mu.Unlock()
recordStatistics(nl.logger, nodeID, start, end, virtual, physical, addrs, prefixes) recordStatistics(nl.logger, nodeID, start, end, virtual, physical, addrs, prefixes, enableExitDstFlowLogs)
}) })
// Register the connection tracker into the TUN device. // Register the connection tracker into the TUN device.
@ -150,7 +150,7 @@ func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID lo
return nil return nil
} }
func recordStatistics(logger *logtail.Logger, nodeID tailcfg.StableNodeID, start, end time.Time, connstats, sockStats map[netlogtype.Connection]netlogtype.Counts, addrs map[netip.Addr]bool, prefixes map[netip.Prefix]bool) { func recordStatistics(logger *logtail.Logger, nodeID tailcfg.StableNodeID, start, end time.Time, connstats, sockStats map[netlogtype.Connection]netlogtype.Counts, addrs map[netip.Addr]bool, prefixes map[netip.Prefix]bool, enableExitDstFlowLogs bool) {
m := netlogtype.Message{NodeID: nodeID, Start: start.UTC(), End: end.UTC()} m := netlogtype.Message{NodeID: nodeID, Start: start.UTC(), End: end.UTC()}
classifyAddr := func(a netip.Addr) (isTailscale, withinRoute bool) { classifyAddr := func(a netip.Addr) (isTailscale, withinRoute bool) {
@ -179,7 +179,7 @@ func recordStatistics(logger *logtail.Logger, nodeID tailcfg.StableNodeID, start
m.SubnetTraffic = append(m.SubnetTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts}) m.SubnetTraffic = append(m.SubnetTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
default: default:
const anonymize = true const anonymize = true
if anonymize { if anonymize && !enableExitDstFlowLogs {
// Only preserve the address if it is a Tailscale IP address. // Only preserve the address if it is a Tailscale IP address.
srcOrig, dstOrig := conn.Src, conn.Dst srcOrig, dstOrig := conn.Src, conn.Dst
conn = netlogtype.Connection{} // scrub everything by default conn = netlogtype.Connection{} // scrub everything by default

View File

@ -932,8 +932,9 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
if netLogRunning && !e.networkLogger.Running() { if netLogRunning && !e.networkLogger.Running() {
nid := cfg.NetworkLogging.NodeID nid := cfg.NetworkLogging.NodeID
tid := cfg.NetworkLogging.DomainID tid := cfg.NetworkLogging.DomainID
enableExitDstFlowLogs := cfg.NetworkLogging.ExitDestinationFlowLogs
e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public()) e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public())
if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn, e.netMon); err != nil { if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn, e.netMon, enableExitDstFlowLogs); err != nil {
e.logf("wgengine: Reconfig: error starting up network logger: %v", err) e.logf("wgengine: Reconfig: error starting up network logger: %v", err)
} }
e.networkLogger.ReconfigRoutes(routerCfg) e.networkLogger.ReconfigRoutes(routerCfg)

View File

@ -30,6 +30,7 @@ type Config struct {
NetworkLogging struct { NetworkLogging struct {
NodeID logid.PrivateID NodeID logid.PrivateID
DomainID logid.PrivateID DomainID logid.PrivateID
ExitDestinationFlowLogs bool
} }
} }