cmd/tailscale,ipn/ipnlocal: enable web client over quad 100 by default (#11419)

Enable the web client over 100.100.100.100 by default. Accepting traffic
from [tailnet IP]:5252 still requires setting the `webclient` user pref.

Updates https://github.com/tailscale/tailscale/issues/10261

Signed-off-by: Mario Minardi <mario@tailscale.com>
This commit is contained in:
Mario Minardi 2024-03-18 15:47:21 -06:00 committed by GitHub
parent 4d747c1833
commit d2ccfa4edd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 16 deletions

View File

@ -75,7 +75,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.BoolVar(&setArgs.updateCheck, "update-check", true, "notify about available Tailscale updates") setf.BoolVar(&setArgs.updateCheck, "update-check", true, "notify about available Tailscale updates")
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version") setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version")
setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information") setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information")
setf.BoolVar(&setArgs.runWebClient, "webclient", false, "run a web interface for managing this node, served over Tailscale at port 5252") setf.BoolVar(&setArgs.runWebClient, "webclient", false, "expose the web interface for managing this node over Tailscale at port 5252")
if safesocket.GOOSUsesPeerCreds(goos) { if safesocket.GOOSUsesPeerCreds(goos) {
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo") setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")

View File

@ -176,10 +176,15 @@ type LocalBackend struct {
logFlushFunc func() // or nil if SetLogFlusher wasn't called logFlushFunc func() // or nil if SetLogFlusher wasn't called
em *expiryManager // non-nil em *expiryManager // non-nil
sshAtomicBool atomic.Bool sshAtomicBool atomic.Bool
webClientAtomicBool atomic.Bool // webClientAtomicBool controls whether the web client is running. This should
shutdownCalled bool // if Shutdown has been called // be true unless the disable-web-client node attribute has been set.
debugSink *capture.Sink webClientAtomicBool atomic.Bool
sockstatLogger *sockstatlog.Logger // exposeRemoteWebClientAtomicBool controls whether the web client is exposed over
// Tailscale on port 5252.
exposeRemoteWebClientAtomicBool atomic.Bool
shutdownCalled bool // if Shutdown has been called
debugSink *capture.Sink
sockstatLogger *sockstatlog.Logger
// getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for // getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for
// the provided srcAddr and dstPort if one exists. // the provided srcAddr and dstPort if one exists.
@ -1160,7 +1165,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
// Perform all reconfiguration based on the netmap here. // Perform all reconfiguration based on the netmap here.
if st.NetMap != nil { if st.NetMap != nil {
b.capTailnetLock = hasCapability(st.NetMap, tailcfg.CapabilityTailnetLock) b.capTailnetLock = hasCapability(st.NetMap, tailcfg.CapabilityTailnetLock)
b.setWebClientAtomicBoolLocked(st.NetMap, prefs.View()) b.setWebClientAtomicBoolLocked(st.NetMap)
b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded
if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil { if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil {
@ -2719,11 +2724,12 @@ func (b *LocalBackend) setTCPPortsIntercepted(ports []uint16) {
b.shouldInterceptTCPPortAtomic.Store(f) b.shouldInterceptTCPPortAtomic.Store(f)
} }
// setAtomicValuesFromPrefsLocked populates sshAtomicBool, containsViaIPFuncAtomic // setAtomicValuesFromPrefsLocked populates sshAtomicBool, containsViaIPFuncAtomic,
// and shouldInterceptTCPPortAtomic from the prefs p, which may be !Valid(). // shouldInterceptTCPPortAtomic, and exposeRemoteWebClientAtomicBool from the prefs p,
// which may be !Valid().
func (b *LocalBackend) setAtomicValuesFromPrefsLocked(p ipn.PrefsView) { func (b *LocalBackend) setAtomicValuesFromPrefsLocked(p ipn.PrefsView) {
b.sshAtomicBool.Store(p.Valid() && p.RunSSH() && envknob.CanSSHD()) b.sshAtomicBool.Store(p.Valid() && p.RunSSH() && envknob.CanSSHD())
b.setWebClientAtomicBoolLocked(b.netMap, p) b.setExposeRemoteWebClientAtomicBoolLocked(p)
if !p.Valid() { if !p.Valid() {
b.containsViaIPFuncAtomic.Store(tsaddr.FalseContainsIPFunc()) b.containsViaIPFuncAtomic.Store(tsaddr.FalseContainsIPFunc())
@ -3356,6 +3362,8 @@ func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c
if hittingServiceIP { if hittingServiceIP {
switch dst.Port() { switch dst.Port() {
case 80: case 80:
// TODO(mpminardi): do we want to show an error message if the web client
// has been disabled instead of the more "basic" web UI?
if b.ShouldRunWebClient() { if b.ShouldRunWebClient() {
return b.handleWebClientConn, opts return b.handleWebClientConn, opts
} }
@ -3380,7 +3388,7 @@ func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c
return b.handleSSHConn, opts return b.handleSSHConn, opts
} }
// TODO(will,sonia): allow customizing web client port ? // TODO(will,sonia): allow customizing web client port ?
if dst.Port() == webClientPort && b.ShouldRunWebClient() { if dst.Port() == webClientPort && b.ShouldExposeRemoteWebClient() {
return b.handleWebClientConn, opts return b.handleWebClientConn, opts
} }
if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port { if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port {
@ -4508,19 +4516,39 @@ func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && en
// call regardless of whether b.mu is held or not. // call regardless of whether b.mu is held or not.
func (b *LocalBackend) ShouldRunWebClient() bool { return b.webClientAtomicBool.Load() } func (b *LocalBackend) ShouldRunWebClient() bool { return b.webClientAtomicBool.Load() }
// ShouldExposeRemoteWebClient reports whether the web client should
// accept connections via [tailscale IP]:5252 in addition to the default
// behaviour of accepting local connections over 100.100.100.100.
//
// This function checks both the web client user pref via
// exposeRemoteWebClientAtomicBool and the disable-web-client node attr
// via ShouldRunWebClient to determine whether the web client should be
// exposed.
func (b *LocalBackend) ShouldExposeRemoteWebClient() bool {
return b.ShouldRunWebClient() && b.exposeRemoteWebClientAtomicBool.Load()
}
// setWebClientAtomicBoolLocked sets webClientAtomicBool based on whether // setWebClientAtomicBoolLocked sets webClientAtomicBool based on whether
// the RunWebClient pref is set, and whether tailcfg.NodeAttrDisableWebClient // tailcfg.NodeAttrDisableWebClient has been set in the netmap.NetworkMap.
// has been set in the netmap.NetworkMap.
// //
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) setWebClientAtomicBoolLocked(nm *netmap.NetworkMap, prefs ipn.PrefsView) { func (b *LocalBackend) setWebClientAtomicBoolLocked(nm *netmap.NetworkMap) {
shouldRun := prefs.Valid() && prefs.RunWebClient() && !hasCapability(nm, tailcfg.NodeAttrDisableWebClient) shouldRun := !hasCapability(nm, tailcfg.NodeAttrDisableWebClient)
wasRunning := b.webClientAtomicBool.Swap(shouldRun) wasRunning := b.webClientAtomicBool.Swap(shouldRun)
if wasRunning && !shouldRun { if wasRunning && !shouldRun {
go b.webClientShutdown() // stop web client go b.webClientShutdown() // stop web client
} }
} }
// setExposeRemoteWebClientAtomicBoolLocked sets exposeRemoteWebClientAtomicBool
// based on whether the RunWebClient pref is set.
//
// b.mu must be held.
func (b *LocalBackend) setExposeRemoteWebClientAtomicBoolLocked(prefs ipn.PrefsView) {
shouldExpose := prefs.Valid() && prefs.RunWebClient()
b.exposeRemoteWebClientAtomicBool.Store(shouldExpose)
}
// ShouldHandleViaIP reports whether ip is an IPv6 address in the // ShouldHandleViaIP reports whether ip is an IPv6 address in the
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to // Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
// by Tailscale. // by Tailscale.
@ -4808,7 +4836,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
if prefs.Valid() && prefs.RunSSH() && envknob.CanSSHD() { if prefs.Valid() && prefs.RunSSH() && envknob.CanSSHD() {
handlePorts = append(handlePorts, 22) handlePorts = append(handlePorts, 22)
} }
if b.ShouldRunWebClient() { if b.ShouldExposeRemoteWebClient() {
handlePorts = append(handlePorts, webClientPort) handlePorts = append(handlePorts, webClientPort)
// don't listen on netmap addresses if we're in userspace mode // don't listen on netmap addresses if we're in userspace mode

View File

@ -118,7 +118,8 @@ type Prefs struct {
// policies as configured by the Tailnet's admin(s). // policies as configured by the Tailnet's admin(s).
RunSSH bool RunSSH bool
// RunWebClient bool is whether this node should run a web client, // RunWebClient bool is whether this node should expose
// its web client over Tailscale at port 5252,
// permitting access to peers according to the // permitting access to peers according to the
// policies as configured by the Tailnet's admin(s). // policies as configured by the Tailnet's admin(s).
RunWebClient bool RunWebClient bool