From d2f3ec8a632306eed62a7c3ebc37940626a527c7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 23 Mar 2022 13:52:29 -0700 Subject: [PATCH] envknob, ipn/ipnlocal: add SSH admin kill switch Updates #3802 Change-Id: I6127907446d1a6be1b097d9ba3b534f2b8eb707f Signed-off-by: Brad Fitzpatrick --- client/tailscale/tailscale.go | 1 + envknob/envknob.go | 7 +++++++ ipn/ipnlocal/local.go | 14 ++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index 52c75ea7a..273f42282 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -183,6 +183,7 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read return nil, err } if res.StatusCode != wantStatus { + err = fmt.Errorf("%v: %s", res.Status, bytes.TrimSpace(slurp)) return nil, bestError(err, slurp) } return slurp, nil diff --git a/envknob/envknob.go b/envknob/envknob.go index fe38aca5f..bad2f097f 100644 --- a/envknob/envknob.go +++ b/envknob/envknob.go @@ -142,3 +142,10 @@ func LookupInt(envVar string) (v int, ok bool) { // UseWIPCode is whether TAILSCALE_USE_WIP_CODE is set to permit use // of Work-In-Progress code. func UseWIPCode() bool { return Bool("TAILSCALE_USE_WIP_CODE") } + +// CanSSHD is whether the Tailscale SSH server is allowed to run. +// +// If disabled, the SSH server won't start (won't intercept port 22) +// if already enabled and any attempt to re-enable it will result in +// an error. +func CanSSHD() bool { return !Bool("TS_DISABLE_SSH_SERVER") } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index ac8352f6c..143a8d2e4 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -65,6 +65,7 @@ import ( ) var controlDebugFlags = getControlDebugFlags() +var canSSH = envknob.CanSSHD() func getControlDebugFlags() []string { if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" { @@ -1564,7 +1565,7 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err b.logf("using backend prefs for %q: %s", key, b.prefs.Pretty()) - b.sshAtomicBool.Set(b.prefs != nil && b.prefs.RunSSH) + b.sshAtomicBool.Set(b.prefs != nil && b.prefs.RunSSH && canSSH) return nil } @@ -1703,6 +1704,11 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { p0 := b.prefs.Clone() p1 := b.prefs.Clone() p1.ApplyEdits(mp) + if p1.RunSSH && !canSSH { + b.mu.Unlock() + b.logf("EditPrefs requests SSH, but disabled by envknob; returning error") + return nil, errors.New("Tailscale SSH server administratively disabled.") + } if p1.Equals(p0) { b.mu.Unlock() return p1, nil @@ -1732,7 +1738,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) { netMap := b.netMap stateKey := b.stateKey - b.sshAtomicBool.Set(newp.RunSSH) + b.sshAtomicBool.Set(newp.RunSSH && canSSH) oldp := b.prefs newp.Persist = oldp.Persist // caller isn't allowed to override this @@ -2462,7 +2468,7 @@ func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Pre hi.ShieldsUp = prefs.ShieldsUp var sshHostKeys []string - if prefs.RunSSH { + if prefs.RunSSH && canSSH { // TODO(bradfitz): this is called with b.mu held. Not ideal. // If the filesystem gets wedged or something we could block for // a long time. But probably fine. @@ -2679,7 +2685,7 @@ func (b *LocalBackend) ResetForClientDisconnect() { b.sshAtomicBool.Set(false) } -func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() } +func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() && canSSH } // Logout tells the controlclient that we want to log out, and // transitions the local engine to the logged-out state without