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

@ -38,24 +38,25 @@ Only settings explicitly mentioned will be set. There are no default values.`,
} }
type setArgsT struct { type setArgsT struct {
acceptRoutes bool acceptRoutes bool
acceptDNS bool acceptDNS bool
exitNodeIP string exitNodeIP string
exitNodeAllowLANAccess bool exitNodeAllowLANAccess bool
shieldsUp bool exitDestinationFlowLogs bool
runSSH bool shieldsUp bool
runWebClient bool runSSH bool
hostname string runWebClient bool
advertiseRoutes string hostname string
advertiseDefaultRoute bool advertiseRoutes string
advertiseConnector bool advertiseDefaultRoute bool
opUser string advertiseConnector bool
acceptedRisks string opUser string
profileName string acceptedRisks string
forceDaemon bool profileName string
updateCheck bool forceDaemon bool
updateApply bool updateCheck bool
postureChecking bool updateApply bool
postureChecking bool
} }
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet { func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
@ -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")
@ -106,16 +108,17 @@ func runSet(ctx context.Context, args []string) (retErr error) {
maskedPrefs := &ipn.MaskedPrefs{ maskedPrefs := &ipn.MaskedPrefs{
Prefs: ipn.Prefs{ Prefs: ipn.Prefs{
ProfileName: setArgs.profileName, ProfileName: setArgs.profileName,
RouteAll: setArgs.acceptRoutes, RouteAll: setArgs.acceptRoutes,
CorpDNS: setArgs.acceptDNS, CorpDNS: setArgs.acceptDNS,
ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess, ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess,
ShieldsUp: setArgs.shieldsUp, ExitDestinationFlowLogs: setArgs.exitDestinationFlowLogs,
RunSSH: setArgs.runSSH, ShieldsUp: setArgs.shieldsUp,
RunWebClient: setArgs.runWebClient, RunSSH: setArgs.runSSH,
Hostname: setArgs.hostname, RunWebClient: setArgs.runWebClient,
OperatorUser: setArgs.opUser, Hostname: setArgs.hostname,
ForceDaemon: setArgs.forceDaemon, OperatorUser: setArgs.opUser,
ForceDaemon: setArgs.forceDaemon,
AutoUpdate: ipn.AutoUpdatePrefs{ AutoUpdate: ipn.AutoUpdatePrefs{
Check: setArgs.updateCheck, Check: setArgs.updateCheck,
Apply: opt.NewBool(setArgs.updateApply), Apply: opt.NewBool(setArgs.updateApply),

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
@ -274,33 +277,34 @@ type AppConnectorPrefs struct {
type MaskedPrefs struct { type MaskedPrefs struct {
Prefs Prefs
ControlURLSet bool `json:",omitempty"` ControlURLSet bool `json:",omitempty"`
RouteAllSet bool `json:",omitempty"` RouteAllSet bool `json:",omitempty"`
AllowSingleHostsSet bool `json:",omitempty"` AllowSingleHostsSet bool `json:",omitempty"`
ExitNodeIDSet bool `json:",omitempty"` ExitDestinationFlowLogsSet bool `json:",omitempty"`
ExitNodeIPSet bool `json:",omitempty"` ExitNodeIDSet bool `json:",omitempty"`
ExitNodeAllowLANAccessSet bool `json:",omitempty"` ExitNodeIPSet bool `json:",omitempty"`
CorpDNSSet bool `json:",omitempty"` ExitNodeAllowLANAccessSet bool `json:",omitempty"`
RunSSHSet bool `json:",omitempty"` CorpDNSSet bool `json:",omitempty"`
RunWebClientSet bool `json:",omitempty"` RunSSHSet bool `json:",omitempty"`
WantRunningSet bool `json:",omitempty"` RunWebClientSet bool `json:",omitempty"`
LoggedOutSet bool `json:",omitempty"` WantRunningSet bool `json:",omitempty"`
ShieldsUpSet bool `json:",omitempty"` LoggedOutSet bool `json:",omitempty"`
AdvertiseTagsSet bool `json:",omitempty"` ShieldsUpSet bool `json:",omitempty"`
HostnameSet bool `json:",omitempty"` AdvertiseTagsSet bool `json:",omitempty"`
NotepadURLsSet bool `json:",omitempty"` HostnameSet bool `json:",omitempty"`
ForceDaemonSet bool `json:",omitempty"` NotepadURLsSet bool `json:",omitempty"`
EggSet bool `json:",omitempty"` ForceDaemonSet bool `json:",omitempty"`
AdvertiseRoutesSet bool `json:",omitempty"` EggSet bool `json:",omitempty"`
NoSNATSet bool `json:",omitempty"` AdvertiseRoutesSet bool `json:",omitempty"`
NetfilterModeSet bool `json:",omitempty"` NoSNATSet bool `json:",omitempty"`
OperatorUserSet bool `json:",omitempty"` NetfilterModeSet bool `json:",omitempty"`
ProfileNameSet bool `json:",omitempty"` OperatorUserSet bool `json:",omitempty"`
AutoUpdateSet AutoUpdatePrefsMask `json:",omitempty"` ProfileNameSet bool `json:",omitempty"`
AppConnectorSet bool `json:",omitempty"` AutoUpdateSet AutoUpdatePrefsMask `json:",omitempty"`
PostureCheckingSet bool `json:",omitempty"` AppConnectorSet bool `json:",omitempty"`
NetfilterKindSet bool `json:",omitempty"` PostureCheckingSet bool `json:",omitempty"`
DriveSharesSet bool `json:",omitempty"` NetfilterKindSet bool `json:",omitempty"`
DriveSharesSet bool `json:",omitempty"`
} }
type AutoUpdatePrefsMask struct { type AutoUpdatePrefsMask struct {
@ -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

@ -28,8 +28,9 @@ type Config struct {
// NetworkLogging enables network logging. // NetworkLogging enables network logging.
// It is disabled if either ID is the zero value. // It is disabled if either ID is the zero value.
NetworkLogging struct { NetworkLogging struct {
NodeID logid.PrivateID NodeID logid.PrivateID
DomainID logid.PrivateID DomainID logid.PrivateID
ExitDestinationFlowLogs bool
} }
} }