diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go index 4049eb12e..80d93c695 100644 --- a/cmd/tailscale/cli/set.go +++ b/cmd/tailscale/cli/set.go @@ -38,24 +38,25 @@ Only settings explicitly mentioned will be set. There are no default values.`, } type setArgsT struct { - acceptRoutes bool - acceptDNS bool - exitNodeIP string - exitNodeAllowLANAccess bool - shieldsUp bool - runSSH bool - runWebClient bool - hostname string - advertiseRoutes string - advertiseDefaultRoute bool - advertiseConnector bool - opUser string - acceptedRisks string - profileName string - forceDaemon bool - updateCheck bool - updateApply bool - postureChecking bool + acceptRoutes bool + acceptDNS bool + exitNodeIP string + exitNodeAllowLANAccess bool + exitDestinationFlowLogs bool + shieldsUp bool + runSSH bool + runWebClient bool + hostname string + advertiseRoutes string + advertiseDefaultRoute bool + advertiseConnector bool + opUser string + acceptedRisks string + profileName string + forceDaemon bool + updateCheck bool + updateApply bool + postureChecking bool } 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.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.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.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") @@ -106,16 +108,17 @@ func runSet(ctx context.Context, args []string) (retErr error) { maskedPrefs := &ipn.MaskedPrefs{ Prefs: ipn.Prefs{ - ProfileName: setArgs.profileName, - RouteAll: setArgs.acceptRoutes, - CorpDNS: setArgs.acceptDNS, - ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess, - ShieldsUp: setArgs.shieldsUp, - RunSSH: setArgs.runSSH, - RunWebClient: setArgs.runWebClient, - Hostname: setArgs.hostname, - OperatorUser: setArgs.opUser, - ForceDaemon: setArgs.forceDaemon, + ProfileName: setArgs.profileName, + RouteAll: setArgs.acceptRoutes, + CorpDNS: setArgs.acceptDNS, + ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess, + ExitDestinationFlowLogs: setArgs.exitDestinationFlowLogs, + ShieldsUp: setArgs.shieldsUp, + RunSSH: setArgs.runSSH, + RunWebClient: setArgs.runWebClient, + Hostname: setArgs.hostname, + OperatorUser: setArgs.opUser, + ForceDaemon: setArgs.forceDaemon, AutoUpdate: ipn.AutoUpdatePrefs{ Check: setArgs.updateCheck, Apply: opt.NewBool(setArgs.updateApply), diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index e448bd065..7b375b532 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -37,34 +37,35 @@ func (src *Prefs) Clone() *Prefs { // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _PrefsCloneNeedsRegeneration = Prefs(struct { - ControlURL string - RouteAll bool - AllowSingleHosts bool - ExitNodeID tailcfg.StableNodeID - ExitNodeIP netip.Addr - ExitNodeAllowLANAccess bool - CorpDNS bool - RunSSH bool - RunWebClient bool - WantRunning bool - LoggedOut bool - ShieldsUp bool - AdvertiseTags []string - Hostname string - NotepadURLs bool - ForceDaemon bool - Egg bool - AdvertiseRoutes []netip.Prefix - NoSNAT bool - NetfilterMode preftype.NetfilterMode - OperatorUser string - ProfileName string - AutoUpdate AutoUpdatePrefs - AppConnector AppConnectorPrefs - PostureChecking bool - NetfilterKind string - TailFSShares []*tailfs.Share - Persist *persist.Persist + ControlURL string + RouteAll bool + AllowSingleHosts bool + ExitNodeID tailcfg.StableNodeID + ExitNodeIP netip.Addr + ExitNodeAllowLANAccess bool + ExitDestinationFlowLogs bool + CorpDNS bool + RunSSH bool + RunWebClient bool + WantRunning bool + LoggedOut bool + ShieldsUp bool + AdvertiseTags []string + Hostname string + NotepadURLs bool + ForceDaemon bool + Egg bool + AdvertiseRoutes []netip.Prefix + NoSNAT bool + NetfilterMode preftype.NetfilterMode + OperatorUser string + ProfileName string + AutoUpdate AutoUpdatePrefs + AppConnector AppConnectorPrefs + PostureChecking bool + NetfilterKind string + TailFSShares []*tailfs.Share + Persist *persist.Persist }{}) // Clone makes a deep copy of ServeConfig. diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index b156e37ef..d1dd59438 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -70,6 +70,7 @@ func (v PrefsView) AllowSingleHosts() bool { return v.ж.AllowSingle func (v PrefsView) ExitNodeID() tailcfg.StableNodeID { return v.ж.ExitNodeID } func (v PrefsView) ExitNodeIP() netip.Addr { return v.ж.ExitNodeIP } func (v PrefsView) ExitNodeAllowLANAccess() bool { return v.ж.ExitNodeAllowLANAccess } +func (v PrefsView) ExitDestinationFlowLogs() bool { return v.ж.ExitDestinationFlowLogs } func (v PrefsView) CorpDNS() bool { return v.ж.CorpDNS } func (v PrefsView) RunSSH() bool { return v.ж.RunSSH } func (v PrefsView) RunWebClient() bool { return v.ж.RunWebClient } @@ -99,34 +100,35 @@ func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _PrefsViewNeedsRegeneration = Prefs(struct { - ControlURL string - RouteAll bool - AllowSingleHosts bool - ExitNodeID tailcfg.StableNodeID - ExitNodeIP netip.Addr - ExitNodeAllowLANAccess bool - CorpDNS bool - RunSSH bool - RunWebClient bool - WantRunning bool - LoggedOut bool - ShieldsUp bool - AdvertiseTags []string - Hostname string - NotepadURLs bool - ForceDaemon bool - Egg bool - AdvertiseRoutes []netip.Prefix - NoSNAT bool - NetfilterMode preftype.NetfilterMode - OperatorUser string - ProfileName string - AutoUpdate AutoUpdatePrefs - AppConnector AppConnectorPrefs - PostureChecking bool - NetfilterKind string - TailFSShares []*tailfs.Share - Persist *persist.Persist + ControlURL string + RouteAll bool + AllowSingleHosts bool + ExitNodeID tailcfg.StableNodeID + ExitNodeIP netip.Addr + ExitNodeAllowLANAccess bool + ExitDestinationFlowLogs bool + CorpDNS bool + RunSSH bool + RunWebClient bool + WantRunning bool + LoggedOut bool + ShieldsUp bool + AdvertiseTags []string + Hostname string + NotepadURLs bool + ForceDaemon bool + Egg bool + AdvertiseRoutes []netip.Prefix + NoSNAT bool + NetfilterMode preftype.NetfilterMode + OperatorUser string + ProfileName string + AutoUpdate AutoUpdatePrefs + AppConnector AppConnectorPrefs + PostureChecking bool + NetfilterKind string + TailFSShares []*tailfs.Share + Persist *persist.Persist }{}) // View returns a readonly view of ServeConfig. diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 5fcb73a8d..33aacfd18 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1142,7 +1142,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control if setExitNodeID(prefs, st.NetMap) { prefsChanged = true } - if setExitNodeDstLogging(prefs) { + if setExitDstFlowLogs(prefs) { prefsChanged = true } if applySysPolicy(prefs) { @@ -1330,9 +1330,9 @@ func applySysPolicy(prefs *ipn.Prefs) (anyChange bool) { return anyChange } -func setExitNodeDstLogging(prefs *ipn.Prefs) (anyChange bool) { - if enable, err := syspolicy.GetBoolean(syspolicy.ExitDestinationFlowLogs, prefs.ExitDestinationFlowLog); err == nil && prefs.ExitDestinationFlowLog != enable { - prefs.ExitDestinationFlowLog = enable +func setExitDstFlowLogs(prefs *ipn.Prefs) (anyChange bool) { + if enable, err := syspolicy.GetBoolean(syspolicy.ExitDestinationFlowLogs, prefs.ExitDestinationFlowLogs); err == nil && prefs.ExitDestinationFlowLogs != enable { + prefs.ExitDestinationFlowLogs = enable anyChange = true } return anyChange @@ -3250,7 +3250,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn // everything in this function treats b.prefs as completely new // anyway. No-op if no exit node resolution is needed. setExitNodeID(newp, netMap) - setExitNodeDstLogging(newp) + setExitDstFlowLogs(newp) // applySysPolicy does likewise so we can also ignore its return value. applySysPolicy(newp) // We do this to avoid holding the lock while doing everything else. @@ -3632,7 +3632,7 @@ func (b *LocalBackend) authReconfig() { return } - cfg.NetworkLogging.EnableExitNodeDstLog = // prefs here + cfg.NetworkLogging.ExitDestinationFlowLogs = prefs.ExitDestinationFlowLogs() oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS()) rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute) diff --git a/ipn/prefs.go b/ipn/prefs.go index 0819663b8..574ac9477 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -109,7 +109,8 @@ type Prefs struct { // routed directly or via the exit node. ExitNodeAllowLANAccess bool - ExitDestinationFlowLog bool + // ExitDestinationFlowLogs indicates whether exit node destination is recorded in network flow logs. + ExitDestinationFlowLogs bool // CorpDNS specifies whether to install the Tailscale network's // DNS configuration, if it exists. @@ -477,6 +478,9 @@ func (p *Prefs) pretty(goos string) string { if p.ShieldsUp { sb.WriteString("shields=true ") } + if p.ExitDestinationFlowLogs { + sb.WriteString("exitdestinationflowlogs=true ") + } if p.ExitNodeIP.IsValid() { fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess) } else if !p.ExitNodeID.IsZero() { @@ -547,6 +551,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool { p.ExitNodeID == p2.ExitNodeID && p.ExitNodeIP == p2.ExitNodeIP && p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess && + p.ExitDestinationFlowLogs == p2.ExitDestinationFlowLogs && p.CorpDNS == p2.CorpDNS && p.RunSSH == p2.RunSSH && p.RunWebClient == p2.RunWebClient && diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index 20f7134e8..937fe3143 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -28,9 +28,9 @@ type Config struct { // NetworkLogging enables network logging. // It is disabled if either ID is the zero value. NetworkLogging struct { - NodeID logid.PrivateID - DomainID logid.PrivateID - EnableExitNodeDstLog bool + NodeID logid.PrivateID + DomainID logid.PrivateID + ExitDestinationFlowLogs bool } } diff --git a/wgengine/wgcfg/wgcfg_clone.go b/wgengine/wgcfg/wgcfg_clone.go index 4a2288f1e..4116abaeb 100644 --- a/wgengine/wgcfg/wgcfg_clone.go +++ b/wgengine/wgcfg/wgcfg_clone.go @@ -43,8 +43,9 @@ var _ConfigCloneNeedsRegeneration = Config(struct { DNS []netip.Addr Peers []Peer NetworkLogging struct { - NodeID logid.PrivateID - DomainID logid.PrivateID + NodeID logid.PrivateID + DomainID logid.PrivateID + ExitDestinationFlowLogs bool } }{}) diff --git a/wgengine/wgcfg/wgcfg_view.go b/wgengine/wgcfg/wgcfg_view.go new file mode 100644 index 000000000..992a8ebaf --- /dev/null +++ b/wgengine/wgcfg/wgcfg_view.go @@ -0,0 +1,173 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by tailscale/cmd/viewer; DO NOT EDIT. + +package wgcfg + +import ( + "encoding/json" + "errors" + "net/netip" + + "tailscale.com/tailcfg" + "tailscale.com/types/key" + "tailscale.com/types/logid" + "tailscale.com/types/views" +) + +//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=Config,Peer + +// View returns a readonly view of Config. +func (p *Config) View() ConfigView { + return ConfigView{ж: p} +} + +// ConfigView provides a read-only view over Config. +// +// Its methods should only be called if `Valid()` returns true. +type ConfigView struct { + // ж is the underlying mutable value, named with a hard-to-type + // character that looks pointy like a pointer. + // It is named distinctively to make you think of how dangerous it is to escape + // to callers. You must not let callers be able to mutate it. + ж *Config +} + +// Valid reports whether underlying value is non-nil. +func (v ConfigView) Valid() bool { return v.ж != nil } + +// AsStruct returns a clone of the underlying value which aliases no memory with +// the original. +func (v ConfigView) AsStruct() *Config { + if v.ж == nil { + return nil + } + return v.ж.Clone() +} + +func (v ConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *ConfigView) UnmarshalJSON(b []byte) error { + if v.ж != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + var x Config + if err := json.Unmarshal(b, &x); err != nil { + return err + } + v.ж = &x + return nil +} + +func (v ConfigView) Name() string { return v.ж.Name } +func (v ConfigView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID } +func (v ConfigView) PrivateKey() key.NodePrivate { return v.ж.PrivateKey } +func (v ConfigView) Addresses() views.Slice[netip.Prefix] { return views.SliceOf(v.ж.Addresses) } +func (v ConfigView) MTU() uint16 { return v.ж.MTU } +func (v ConfigView) DNS() views.Slice[netip.Addr] { return views.SliceOf(v.ж.DNS) } +func (v ConfigView) Peers() Peer { panic("unsupported") } +func (v ConfigView) NetworkLogging() struct { + NodeID logid.PrivateID + DomainID logid.PrivateID + ExitDestinationFlowLogs bool +} { + return v.ж.NetworkLogging +} + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _ConfigViewNeedsRegeneration = Config(struct { + Name string + NodeID tailcfg.StableNodeID + PrivateKey key.NodePrivate + Addresses []netip.Prefix + MTU uint16 + DNS []netip.Addr + Peers []Peer + NetworkLogging struct { + NodeID logid.PrivateID + DomainID logid.PrivateID + ExitDestinationFlowLogs bool + } +}{}) + +// View returns a readonly view of Peer. +func (p *Peer) View() PeerView { + return PeerView{ж: p} +} + +// PeerView provides a read-only view over Peer. +// +// Its methods should only be called if `Valid()` returns true. +type PeerView struct { + // ж is the underlying mutable value, named with a hard-to-type + // character that looks pointy like a pointer. + // It is named distinctively to make you think of how dangerous it is to escape + // to callers. You must not let callers be able to mutate it. + ж *Peer +} + +// Valid reports whether underlying value is non-nil. +func (v PeerView) Valid() bool { return v.ж != nil } + +// AsStruct returns a clone of the underlying value which aliases no memory with +// the original. +func (v PeerView) AsStruct() *Peer { + if v.ж == nil { + return nil + } + return v.ж.Clone() +} + +func (v PeerView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *PeerView) UnmarshalJSON(b []byte) error { + if v.ж != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + var x Peer + if err := json.Unmarshal(b, &x); err != nil { + return err + } + v.ж = &x + return nil +} + +func (v PeerView) PublicKey() key.NodePublic { return v.ж.PublicKey } +func (v PeerView) DiscoKey() key.DiscoPublic { return v.ж.DiscoKey } +func (v PeerView) AllowedIPs() views.Slice[netip.Prefix] { return views.SliceOf(v.ж.AllowedIPs) } +func (v PeerView) V4MasqAddr() *netip.Addr { + if v.ж.V4MasqAddr == nil { + return nil + } + x := *v.ж.V4MasqAddr + return &x +} + +func (v PeerView) V6MasqAddr() *netip.Addr { + if v.ж.V6MasqAddr == nil { + return nil + } + x := *v.ж.V6MasqAddr + return &x +} + +func (v PeerView) PersistentKeepalive() uint16 { return v.ж.PersistentKeepalive } +func (v PeerView) WGEndpoint() key.NodePublic { return v.ж.WGEndpoint } + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _PeerViewNeedsRegeneration = Peer(struct { + PublicKey key.NodePublic + DiscoKey key.DiscoPublic + AllowedIPs []netip.Prefix + V4MasqAddr *netip.Addr + V6MasqAddr *netip.Addr + PersistentKeepalive uint16 + WGEndpoint key.NodePublic +}{}) diff --git a/wgengine/wgengine_clone.go b/wgengine/wgengine_clone.go new file mode 100644 index 000000000..cf823a42b --- /dev/null +++ b/wgengine/wgengine_clone.go @@ -0,0 +1,51 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. + +package wgengine + +import ( + "github.com/tailscale/wireguard-go/tun" + "tailscale.com/control/controlknobs" + "tailscale.com/net/dns" + "tailscale.com/net/netmon" + "tailscale.com/net/tsdial" + "tailscale.com/tailfs" + "tailscale.com/wgengine/router" +) + +// Clone makes a deep copy of Config. +// The result aliases no memory with the original. +func (src *Config) Clone() *Config { + if src == nil { + return nil + } + dst := new(Config) + *dst = *src + dst.Tun = *src.Tun.Clone() + dst.Router = *src.Router.Clone() + dst.DNS = *src.DNS.Clone() + dst.NetMon = src.NetMon.Clone() + dst.Dialer = src.Dialer.Clone() + dst.ControlKnobs = src.ControlKnobs.Clone() + dst.BIRDClient = *src.BIRDClient.Clone() + dst.TailFSForLocal = *src.TailFSForLocal.Clone() + return dst +} + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _ConfigCloneNeedsRegeneration = Config(struct { + Tun tun.Device + IsTAP bool + Router router.Router + DNS dns.OSConfigurator + NetMon *netmon.Monitor + Dialer *tsdial.Dialer + ControlKnobs *controlknobs.Knobs + ListenPort uint16 + RespondToPing bool + BIRDClient BIRDClient + SetSubsystem func(any) + TailFSForLocal tailfs.FileSystemForLocal +}{}) diff --git a/wgengine/wgengine_view.go b/wgengine/wgengine_view.go new file mode 100644 index 000000000..799d14669 --- /dev/null +++ b/wgengine/wgengine_view.go @@ -0,0 +1,95 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by tailscale/cmd/viewer; DO NOT EDIT. + +package wgengine + +import ( + "encoding/json" + "errors" + + "github.com/tailscale/wireguard-go/tun" + "tailscale.com/control/controlknobs" + "tailscale.com/net/dns" + "tailscale.com/net/netmon" + "tailscale.com/net/tsdial" + "tailscale.com/tailfs" + "tailscale.com/wgengine/router" +) + +//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=Config + +// View returns a readonly view of Config. +func (p *Config) View() ConfigView { + return ConfigView{ж: p} +} + +// ConfigView provides a read-only view over Config. +// +// Its methods should only be called if `Valid()` returns true. +type ConfigView struct { + // ж is the underlying mutable value, named with a hard-to-type + // character that looks pointy like a pointer. + // It is named distinctively to make you think of how dangerous it is to escape + // to callers. You must not let callers be able to mutate it. + ж *Config +} + +// Valid reports whether underlying value is non-nil. +func (v ConfigView) Valid() bool { return v.ж != nil } + +// AsStruct returns a clone of the underlying value which aliases no memory with +// the original. +func (v ConfigView) AsStruct() *Config { + if v.ж == nil { + return nil + } + return v.ж.Clone() +} + +func (v ConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *ConfigView) UnmarshalJSON(b []byte) error { + if v.ж != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + var x Config + if err := json.Unmarshal(b, &x); err != nil { + return err + } + v.ж = &x + return nil +} + +func (v ConfigView) Tun() { panic("unsupported") } +func (v ConfigView) IsTAP() bool { return v.ж.IsTAP } +func (v ConfigView) Router() bool { panic("unsupported") } +func (v ConfigView) DNS() bool { panic("unsupported") } +func (v ConfigView) NetMon() netmon.MonitorView { return v.ж.NetMon.View() } +func (v ConfigView) Dialer() tsdial.DialerView { return v.ж.Dialer.View() } +func (v ConfigView) ControlKnobs() controlknobs.KnobsView { return v.ж.ControlKnobs.View() } +func (v ConfigView) ListenPort() uint16 { return v.ж.ListenPort } +func (v ConfigView) RespondToPing() bool { return v.ж.RespondToPing } +func (v ConfigView) BIRDClient() bool { panic("unsupported") } +func (v ConfigView) SetSubsystem() func(any) { return v.ж.SetSubsystem } +func (v ConfigView) TailFSForLocal() func(any) { panic("unsupported") } + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _ConfigViewNeedsRegeneration = Config(struct { + Tun tun.Device + IsTAP bool + Router router.Router + DNS dns.OSConfigurator + NetMon *netmon.Monitor + Dialer *tsdial.Dialer + ControlKnobs *controlknobs.Knobs + ListenPort uint16 + RespondToPing bool + BIRDClient BIRDClient + SetSubsystem func(any) + TailFSForLocal tailfs.FileSystemForLocal +}{})