ipn: add user pref for running web client

This is not currently exposed as a user-settable preference through
`tailscale up` or `tailscale set`.  Instead, the preference is set when
turning the web client on and off via localapi. In a subsequent commit,
the pref will be used to automatically start the web client on startup
when appropriate.

Updates tailscale/corp#14335

Signed-off-by: Will Norris <will@tailscale.com>
pull/10032/head
Will Norris 1 year ago committed by Will Norris
parent dd842d4d37
commit 28ad910840

@ -810,6 +810,9 @@ func TestPrefFlagMapping(t *testing.T) {
case "Egg": case "Egg":
// Not applicable. // Not applicable.
continue continue
case "RunWebClient":
// TODO(tailscale/corp#14335): Currently behind a feature flag.
continue
} }
t.Errorf("unexpected new ipn.Pref field %q is not handled by up.go (see addPrefFlagMapping and checkForAccidentalSettingReverts)", prefName) t.Errorf("unexpected new ipn.Pref field %q is not handled by up.go (see addPrefFlagMapping and checkForAccidentalSettingReverts)", prefName)
} }

@ -36,6 +36,7 @@ type ConfigVAlpha struct {
PostureChecking opt.Bool `json:",omitempty"` PostureChecking opt.Bool `json:",omitempty"`
RunSSHServer opt.Bool `json:",omitempty"` // Tailscale SSH RunSSHServer opt.Bool `json:",omitempty"` // Tailscale SSH
RunWebClient opt.Bool `json:",omitempty"`
ShieldsUp opt.Bool `json:",omitempty"` ShieldsUp opt.Bool `json:",omitempty"`
AutoUpdate *AutoUpdatePrefs `json:",omitempty"` AutoUpdate *AutoUpdatePrefs `json:",omitempty"`
ServeConfigTemp *ServeConfig `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this ServeConfigTemp *ServeConfig `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this
@ -113,6 +114,10 @@ func (c *ConfigVAlpha) ToPrefs() (MaskedPrefs, error) {
mp.RunSSH = c.RunSSHServer.EqualBool(true) mp.RunSSH = c.RunSSHServer.EqualBool(true)
mp.RunSSHSet = true mp.RunSSHSet = true
} }
if c.RunWebClient != "" {
mp.RunWebClient = c.RunWebClient.EqualBool(true)
mp.RunWebClientSet = true
}
if c.ShieldsUp != "" { if c.ShieldsUp != "" {
mp.ShieldsUp = c.ShieldsUp.EqualBool(true) mp.ShieldsUp = c.ShieldsUp.EqualBool(true)
mp.ShieldsUpSet = true mp.ShieldsUpSet = true

@ -38,6 +38,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
ExitNodeAllowLANAccess bool ExitNodeAllowLANAccess bool
CorpDNS bool CorpDNS bool
RunSSH bool RunSSH bool
RunWebClient bool
WantRunning bool WantRunning bool
LoggedOut bool LoggedOut bool
ShieldsUp bool ShieldsUp bool

@ -71,6 +71,7 @@ func (v PrefsView) ExitNodeIP() netip.Addr { return v.ж.ExitNodeIP
func (v PrefsView) ExitNodeAllowLANAccess() bool { return v.ж.ExitNodeAllowLANAccess } func (v PrefsView) ExitNodeAllowLANAccess() bool { return v.ж.ExitNodeAllowLANAccess }
func (v PrefsView) CorpDNS() bool { return v.ж.CorpDNS } func (v PrefsView) CorpDNS() bool { return v.ж.CorpDNS }
func (v PrefsView) RunSSH() bool { return v.ж.RunSSH } func (v PrefsView) RunSSH() bool { return v.ж.RunSSH }
func (v PrefsView) RunWebClient() bool { return v.ж.RunWebClient }
func (v PrefsView) WantRunning() bool { return v.ж.WantRunning } func (v PrefsView) WantRunning() bool { return v.ж.WantRunning }
func (v PrefsView) LoggedOut() bool { return v.ж.LoggedOut } func (v PrefsView) LoggedOut() bool { return v.ж.LoggedOut }
func (v PrefsView) ShieldsUp() bool { return v.ж.ShieldsUp } func (v PrefsView) ShieldsUp() bool { return v.ж.ShieldsUp }
@ -100,6 +101,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
ExitNodeAllowLANAccess bool ExitNodeAllowLANAccess bool
CorpDNS bool CorpDNS bool
RunSSH bool RunSSH bool
RunWebClient bool
WantRunning bool WantRunning bool
LoggedOut bool LoggedOut bool
ShieldsUp bool ShieldsUp bool

@ -168,6 +168,7 @@ 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
shutdownCalled bool // if Shutdown has been called shutdownCalled bool // if Shutdown has been called
debugSink *capture.Sink debugSink *capture.Sink
sockstatLogger *sockstatlog.Logger sockstatLogger *sockstatlog.Logger
@ -2500,6 +2501,7 @@ func (b *LocalBackend) setTCPPortsIntercepted(ports []uint16) {
// and shouldInterceptTCPPortAtomic from the prefs p, which may be !Valid(). // and shouldInterceptTCPPortAtomic 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.webclientAtomicBool.Store(p.Valid() && p.RunWebClient())
if !p.Valid() { if !p.Valid() {
b.containsViaIPFuncAtomic.Store(tsaddr.FalseContainsIPFunc()) b.containsViaIPFuncAtomic.Store(tsaddr.FalseContainsIPFunc())
@ -2918,6 +2920,11 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error") b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
return ipn.PrefsView{}, errors.New("Tailscale SSH server administratively disabled.") return ipn.PrefsView{}, errors.New("Tailscale SSH server administratively disabled.")
} }
if p1.RunWebClient && !envknob.Bool("TS_DEBUG_WEB_UI") {
b.mu.Unlock()
b.logf("EditPrefs requests web client, but disabled by envknob; returning error")
return ipn.PrefsView{}, errors.New("web ui flag not set")
}
if p1.View().Equals(p0) { if p1.View().Equals(p0) {
b.mu.Unlock() b.mu.Unlock()
return stripKeysFromPrefs(p0), nil return stripKeysFromPrefs(p0), nil
@ -3010,6 +3017,9 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
b.sshServer = nil b.sshServer = nil
} }
} }
if oldp.ShouldWebClientBeRunning() && !newp.ShouldWebClientBeRunning() {
b.WebShutdown()
}
if netMap != nil { if netMap != nil {
newProfile := netMap.UserProfiles[netMap.User()] newProfile := netMap.UserProfiles[netMap.User()]
if newLoginName := newProfile.LoginName; newLoginName != "" { if newLoginName := newProfile.LoginName; newLoginName != "" {
@ -4146,6 +4156,10 @@ func (b *LocalBackend) ResetForClientDisconnect() {
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() } func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() }
func (b *LocalBackend) ShouldRunWebClient() bool {
return b.webclientAtomicBool.Load() && envknob.Bool("TS_DEBUG_WEB_UI")
}
// 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.

@ -2249,10 +2249,20 @@ func (h *Handler) serveWeb(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// try to set pref, but ignore errors
_, _ = h.b.EditPrefs(&ipn.MaskedPrefs{
Prefs: ipn.Prefs{RunWebClient: true},
RunWebClientSet: true,
})
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return return
case "/localapi/v0/web/stop": case "/localapi/v0/web/stop":
h.b.WebShutdown() h.b.WebShutdown()
// try to set pref, but ignore errors
_, _ = h.b.EditPrefs(&ipn.MaskedPrefs{
Prefs: ipn.Prefs{RunWebClient: false},
RunWebClientSet: true,
})
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return return
default: default:

@ -112,6 +112,11 @@ 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,
// permitting access to peers according to the
// policies as configured by the Tailnet's admin(s).
RunWebClient bool
// WantRunning indicates whether networking should be active on // WantRunning indicates whether networking should be active on
// this node. // this node.
WantRunning bool WantRunning bool
@ -236,6 +241,7 @@ type MaskedPrefs struct {
ExitNodeAllowLANAccessSet bool `json:",omitempty"` ExitNodeAllowLANAccessSet bool `json:",omitempty"`
CorpDNSSet bool `json:",omitempty"` CorpDNSSet bool `json:",omitempty"`
RunSSHSet bool `json:",omitempty"` RunSSHSet bool `json:",omitempty"`
RunWebClientSet bool `json:",omitempty"`
WantRunningSet bool `json:",omitempty"` WantRunningSet bool `json:",omitempty"`
LoggedOutSet bool `json:",omitempty"` LoggedOutSet bool `json:",omitempty"`
ShieldsUpSet bool `json:",omitempty"` ShieldsUpSet bool `json:",omitempty"`
@ -350,6 +356,9 @@ func (p *Prefs) pretty(goos string) string {
if p.RunSSH { if p.RunSSH {
sb.WriteString("ssh=true ") sb.WriteString("ssh=true ")
} }
if p.RunWebClient {
sb.WriteString("webclient=true ")
}
if p.LoggedOut { if p.LoggedOut {
sb.WriteString("loggedout=true ") sb.WriteString("loggedout=true ")
} }
@ -431,6 +440,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess && p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
p.CorpDNS == p2.CorpDNS && p.CorpDNS == p2.CorpDNS &&
p.RunSSH == p2.RunSSH && p.RunSSH == p2.RunSSH &&
p.RunWebClient == p2.RunWebClient &&
p.WantRunning == p2.WantRunning && p.WantRunning == p2.WantRunning &&
p.LoggedOut == p2.LoggedOut && p.LoggedOut == p2.LoggedOut &&
p.NotepadURLs == p2.NotepadURLs && p.NotepadURLs == p2.NotepadURLs &&
@ -691,6 +701,18 @@ func (p *Prefs) ShouldSSHBeRunning() bool {
return p.WantRunning && p.RunSSH return p.WantRunning && p.RunSSH
} }
// ShouldWebClientBeRunning reports whether the web client server should be running based on
// the prefs.
func (p PrefsView) ShouldWebClientBeRunning() bool {
return p.Valid() && p.ж.ShouldWebClientBeRunning()
}
// ShouldWebClientBeRunning reports whether the web client server should be running based on
// the prefs.
func (p *Prefs) ShouldWebClientBeRunning() bool {
return p.WantRunning && p.RunWebClient
}
// PrefsFromBytes deserializes Prefs from a JSON blob. // PrefsFromBytes deserializes Prefs from a JSON blob.
func PrefsFromBytes(b []byte) (*Prefs, error) { func PrefsFromBytes(b []byte) (*Prefs, error) {
p := NewPrefs() p := NewPrefs()

@ -43,6 +43,7 @@ func TestPrefsEqual(t *testing.T) {
"ExitNodeAllowLANAccess", "ExitNodeAllowLANAccess",
"CorpDNS", "CorpDNS",
"RunSSH", "RunSSH",
"RunWebClient",
"WantRunning", "WantRunning",
"LoggedOut", "LoggedOut",
"ShieldsUp", "ShieldsUp",

Loading…
Cancel
Save