Skip to content

Commit

Permalink
wgengine/netstack: add an SSH server experiment
Browse files Browse the repository at this point in the history
Disabled by default.

To use, run tailscaled with:

    TS_SSH_ALLOW_LOGIN=you@bar.com

And enable with:

    $ TAILSCALE_USE_WIP_CODE=true tailscale up --ssh=true

Then ssh [any-user]@[your-tailscale-ip] for a root bash shell.
(both the "root" and "bash" part are temporary)

Updates #3802

Change-Id: I268f8c3c95c8eed5f3231d712a5dc89615a406f0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
  • Loading branch information
bradfitz committed Jan 25, 2022
1 parent 41fd4ea commit f3c0023
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 14 deletions.
11 changes: 11 additions & 0 deletions cmd/tailscale/cli/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
qrcode "github.com/skip2/go-qrcode"
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/safesocket"
Expand Down Expand Up @@ -81,6 +82,8 @@ func acceptRouteDefault(goos string) bool {

var upFlagSet = newUpFlagSet(effectiveGOOS(), &upArgs)

func inTest() bool { return flag.Lookup("test.v") != nil }

func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf := newFlagSet("up")

Expand All @@ -96,6 +99,9 @@ func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node")
upf.BoolVar(&upArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
if envknob.UseWIPCode() || inTest() {
upf.BoolVar(&upArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy")
}
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.authKeyOrFile, "authkey", "", `node authorization key; if it begins with "file:", then it's a path to a file containing the authkey`)
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
Expand Down Expand Up @@ -131,6 +137,7 @@ type upArgsT struct {
exitNodeIP string
exitNodeAllowLANAccess bool
shieldsUp bool
runSSH bool
forceReauth bool
forceDaemon bool
advertiseRoutes string
Expand Down Expand Up @@ -352,6 +359,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
prefs.CorpDNS = upArgs.acceptDNS
prefs.AllowSingleHosts = upArgs.singleRoutes
prefs.ShieldsUp = upArgs.shieldsUp
prefs.RunSSH = upArgs.runSSH
prefs.AdvertiseRoutes = routes
prefs.AdvertiseTags = tags
prefs.Hostname = upArgs.hostname
Expand Down Expand Up @@ -712,6 +720,7 @@ func init() {
addPrefFlagMapping("exit-node-allow-lan-access", "ExitNodeAllowLANAccess")
addPrefFlagMapping("unattended", "ForceDaemon")
addPrefFlagMapping("operator", "OperatorUser")
addPrefFlagMapping("ssh", "RunSSH")
}

func addPrefFlagMapping(flagName string, prefNames ...string) {
Expand Down Expand Up @@ -902,6 +911,8 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]interfac
switch f.Name {
default:
panic(fmt.Sprintf("unhandled flag %q", f.Name))
case "ssh":
set(prefs.RunSSH)
case "login-server":
set(prefs.ControlURL)
case "accept-routes":
Expand Down
14 changes: 10 additions & 4 deletions cmd/tailscaled/depaware.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
L github.com/anmitsu/go-shlex from github.com/gliderlabs/ssh
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/aws
Expand Down Expand Up @@ -60,6 +61,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
L 💣 github.com/creack/pty from tailscale.com/wgengine/netstack
L github.com/gliderlabs/ssh from tailscale.com/wgengine/netstack
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
Expand Down Expand Up @@ -256,7 +259,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/wgengine+
tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
💣 tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
Expand All @@ -265,16 +268,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
L golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
L golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
L golang.org/x/crypto/ssh from github.com/gliderlabs/ssh+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+
Expand Down Expand Up @@ -312,14 +318,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
crypto/aes from crypto/ecdsa+
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509
crypto/dsa from crypto/x509+
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+
crypto/hmac from crypto/tls+
crypto/md5 from crypto/tls+
crypto/rand from crypto/ed25519+
crypto/rc4 from crypto/tls
crypto/rc4 from crypto/tls+
crypto/rsa from crypto/tls+
crypto/sha1 from crypto/tls+
crypto/sha256 from crypto/tls+
Expand Down
8 changes: 4 additions & 4 deletions cmd/tailscaled/tailscaled.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,6 @@ func run() error {
}
ns.ProcessLocalIPs = useNetstack
ns.ProcessSubnets = useNetstack || wrapNetstack
if err := ns.Start(); err != nil {
return fmt.Errorf("failed to start netstack: %w", err)
}

if useNetstack {
dialer.UseNetstackForIP = func(ip netaddr.IP) bool {
Expand All @@ -342,7 +339,6 @@ func run() error {
return ns.DialContextTCP(ctx, dst)
}
}

if socksListener != nil || httpProxyListener != nil {
if httpProxyListener != nil {
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
Expand Down Expand Up @@ -392,6 +388,10 @@ func run() error {
if err != nil {
return fmt.Errorf("ipnserver.New: %w", err)
}
ns.SetLocalBackend(srv.LocalBackend())
if err := ns.Start(); err != nil {
log.Fatalf("failed to start netstack: %v", err)
}

if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
Expand Down
4 changes: 4 additions & 0 deletions envknob/envknob.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,7 @@ func LookupInt(envVar string) (v int, ok bool) {
log.Fatalf("invalid environment variable %s value %q: %v", envVar, val, err)
panic("unreachable")
}

// 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") }
10 changes: 10 additions & 0 deletions ipn/ipnlocal/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"tailscale.com/net/tsdial"
"tailscale.com/paths"
"tailscale.com/portlist"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
"tailscale.com/types/empty"
Expand Down Expand Up @@ -100,6 +101,7 @@ type LocalBackend struct {
serverURL string // tailcontrol URL
newDecompressor func() (controlclient.Decompressor, error)
varRoot string // or empty if SetVarRoot never called
sshAtomicBool syncs.AtomicBool

filterHash deephash.Sum

Expand Down Expand Up @@ -1536,6 +1538,9 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
}

b.logf("backend prefs for %q: %s", key, b.prefs.Pretty())

b.sshAtomicBool.Set(b.prefs != nil && b.prefs.RunSSH)

return nil
}

Expand Down Expand Up @@ -1709,6 +1714,8 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
netMap := b.netMap
stateKey := b.stateKey

b.sshAtomicBool.Set(newp.RunSSH)

oldp := b.prefs
newp.Persist = oldp.Persist // caller isn't allowed to override this
b.prefs = newp
Expand Down Expand Up @@ -2618,8 +2625,11 @@ func (b *LocalBackend) ResetForClientDisconnect() {
b.authURL = ""
b.authURLSticky = ""
b.activeLogin = ""
b.sshAtomicBool.Set(false)
}

func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() }

// Logout tells the controlclient that we want to log out, and
// transitions the local engine to the logged-out state without
// waiting for controlclient to be in that state.
Expand Down
10 changes: 10 additions & 0 deletions ipn/prefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ type Prefs struct {
// DNS configuration, if it exists.
CorpDNS bool

// RunSSH bool is whether this node should run an SSH
// server, permitting access to peers according to the
// policies as configured by the Tailnet's admin(s).
RunSSH bool

// WantRunning indicates whether networking should be active on
// this node.
WantRunning bool
Expand Down Expand Up @@ -193,6 +198,7 @@ type MaskedPrefs struct {
ExitNodeIPSet bool `json:",omitempty"`
ExitNodeAllowLANAccessSet bool `json:",omitempty"`
CorpDNSSet bool `json:",omitempty"`
RunSSHSet bool `json:",omitempty"`
WantRunningSet bool `json:",omitempty"`
LoggedOutSet bool `json:",omitempty"`
ShieldsUpSet bool `json:",omitempty"`
Expand Down Expand Up @@ -277,6 +283,9 @@ func (p *Prefs) pretty(goos string) string {
sb.WriteString("mesh=false ")
}
fmt.Fprintf(&sb, "dns=%v want=%v ", p.CorpDNS, p.WantRunning)
if p.RunSSH {
sb.WriteString("ssh=true ")
}
if p.LoggedOut {
sb.WriteString("loggedout=true ")
}
Expand Down Expand Up @@ -348,6 +357,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.ExitNodeIP == p2.ExitNodeIP &&
p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
p.CorpDNS == p2.CorpDNS &&
p.RunSSH == p2.RunSSH &&
p.WantRunning == p2.WantRunning &&
p.LoggedOut == p2.LoggedOut &&
p.NotepadURLs == p2.NotepadURLs &&
Expand Down
1 change: 1 addition & 0 deletions ipn/prefs_clone.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ipn/prefs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func TestPrefsEqual(t *testing.T) {
"ExitNodeIP",
"ExitNodeAllowLANAccess",
"CorpDNS",
"RunSSH",
"WantRunning",
"LoggedOut",
"ShieldsUp",
Expand Down
4 changes: 2 additions & 2 deletions tsnet/tsnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"

"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi"
Expand Down Expand Up @@ -89,7 +89,7 @@ func (s *Server) Start() error {
}

func (s *Server) start() error {
if v, _ := strconv.ParseBool(os.Getenv("TAILSCALE_USE_WIP_CODE")); !v {
if !envknob.UseWIPCode() {
return errors.New("code disabled without environment variable TAILSCALE_USE_WIP_CODE set true")
}

Expand Down

0 comments on commit f3c0023

Please sign in to comment.