cmd/tailscaled: add `-state=mem:` to support creation of an ephemeral node.

RELNOTE=`tailscaled --state=mem:` registers as an ephemeral node and
does not store state to disk.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
pull/3978/head
Maisem Ali 2 years ago committed by Maisem Ali
parent 823d970d60
commit f9a50779e2

@ -28,6 +28,7 @@ import (
"time"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnserver"
@ -123,7 +124,7 @@ func main() {
flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`)
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM. If empty and --statedir is provided, the default is <statedir>/tailscaled.state")
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an emphemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state")
flag.StringVar(&args.statedir, "statedir", "", "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible.")
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
@ -238,8 +239,19 @@ func ipnServerOpts() (o ipnserver.Options) {
o.VarRoot = dir
}
}
if strings.HasPrefix(statePathOrDefault(), "mem:") {
// Register as an ephemeral node.
o.LoginFlags = controlclient.LoginEphemeral
}
switch goos {
case "js":
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
o.LoginFlags = controlclient.LoginEphemeral
fallthrough
default:
o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey

@ -136,6 +136,7 @@ type LocalBackend struct {
prevIfState *interfaces.State
peerAPIServer *peerAPIServer // or nil
peerAPIListeners []*peerAPIListener
loginFlags controlclient.LoginFlags
incomingFiles map[*incomingFile]bool
// directFileRoot, if non-empty, means to write received files
// directly to this directory, without staging them in an
@ -166,7 +167,7 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
// but is not actually running.
//
// If dialer is nil, a new one is made.
func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine) (*LocalBackend, error) {
func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
if e == nil {
panic("ipn.NewLocalBackend: engine must not be nil")
}
@ -199,6 +200,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
state: ipn.NoState,
portpoll: portpoll,
gotPortPollRes: make(chan struct{}),
loginFlags: loginFlags,
}
// Default filter blocks everything and logs nothing, until Start() is called.
@ -1569,13 +1571,14 @@ func (b *LocalBackend) InServerMode() bool {
}
// Login implements Backend.
// As of 2022-02-17, this is only exists for tests.
func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) {
b.mu.Lock()
b.assertClientLocked()
cc := b.cc
b.mu.Unlock()
cc.Login(token, controlclient.LoginInteractive)
cc.Login(token, b.loginFlags|controlclient.LoginInteractive)
}
// StartLoginInteractive implements Backend. It requests a new
@ -1594,15 +1597,7 @@ func (b *LocalBackend) StartLoginInteractive() {
if url != "" {
b.popBrowserAuthNow()
} else {
flags := controlclient.LoginInteractive
if runtime.GOOS == "js" {
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
flags |= controlclient.LoginEphemeral
}
cc.Login(nil, flags)
cc.Login(nil, b.loginFlags|controlclient.LoginInteractive)
}
}

@ -463,7 +463,7 @@ func TestLazyMachineKeyGeneration(t *testing.T) {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(eng.Close)
lb, err := NewLocalBackend(logf, "logid", store, nil, eng)
lb, err := NewLocalBackend(logf, "logid", store, nil, eng, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}

@ -54,7 +54,7 @@ func TestLocalLogLines(t *testing.T) {
}
t.Cleanup(e.Close)
lb, err := NewLocalBackend(logf, idA.String(), store, nil, e)
lb, err := NewLocalBackend(logf, idA.String(), store, nil, e, 0)
if err != nil {
t.Fatal(err)
}

@ -293,7 +293,7 @@ func TestStateMachine(t *testing.T) {
cc := newMockControl(t)
t.Cleanup(func() { cc.preventLog.Set(true) }) // hacky way to pacify issue 3020
b, err := NewLocalBackend(logf, "logid", store, nil, e)
b, err := NewLocalBackend(logf, "logid", store, nil, e, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@ -954,7 +954,7 @@ func TestWGEngineStatusRace(t *testing.T) {
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
c.Assert(err, qt.IsNil)
t.Cleanup(eng.Close)
b, err := NewLocalBackend(logf, "logid", new(ipn.MemoryStore), nil, eng)
b, err := NewLocalBackend(logf, "logid", new(ipn.MemoryStore), nil, eng, 0)
c.Assert(err, qt.IsNil)
cc := newMockControl(t)

@ -85,6 +85,9 @@ type Options struct {
// the actual definition of "disconnect" is when the
// connection count transitions from 1 to 0.
SurviveDisconnects bool
// LoginFlags specifies the LoginFlags to pass to the client.
LoginFlags controlclient.LoginFlags
}
// Server is an IPN backend and its set of 0 or more active localhost
@ -660,6 +663,8 @@ func tryWindowsAppDataMigration(logf logger.Logf, path string) string {
// Special cases:
//
// * empty string means to use an in-memory store
// * if the string begins with "mem:", the suffix
// is ignored and an in-memory store is used.
// * if the string begins with "kube:", the suffix
// is a Kubernetes secret name
// * if the string begins with "arn:", the value is
@ -668,9 +673,12 @@ func StateStore(path string, logf logger.Logf) (ipn.StateStore, error) {
if path == "" {
return &ipn.MemoryStore{}, nil
}
const memPrefix = "mem:"
const kubePrefix = "kube:"
const arnPrefix = "arn:"
switch {
case strings.HasPrefix(path, memPrefix):
return &ipn.MemoryStore{}, nil
case strings.HasPrefix(path, kubePrefix):
secretName := strings.TrimPrefix(path, kubePrefix)
store, err := ipn.NewKubeStore(secretName)
@ -797,7 +805,7 @@ func Run(ctx context.Context, logf logger.Logf, ln net.Listener, store ipn.State
//
// To start it, use the Server.Run method.
func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engine, dialer *tsdial.Dialer, serverModeUser *user.User, opts Options) (*Server, error) {
b, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng)
b, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, opts.LoginFlags)
if err != nil {
return nil, fmt.Errorf("NewLocalBackend: %v", err)
}

@ -54,6 +54,10 @@ type Server struct {
// log.Printf is used.
Logf logger.Logf
// Ephemeral, if true, specifies that the instance should register
// as an Ephemeral node (https://tailscale.com/kb/1111/ephemeral-nodes/).
Emphemeral bool
initOnce sync.Once
initErr error
lb *ipnlocal.LocalBackend
@ -173,7 +177,11 @@ func (s *Server) start() error {
}
logid := "tslib-TODO"
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, s.dialer, eng)
loginFlags := controlclient.LoginDefault
if s.Emphemeral {
loginFlags = controlclient.LoginEphemeral
}
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, s.dialer, eng, loginFlags)
if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err)
}

@ -13,6 +13,7 @@ import (
// process and can cache a prior success when a dependency changes.
_ "inet.af/netaddr"
_ "tailscale.com/chirp"
_ "tailscale.com/control/controlclient"
_ "tailscale.com/derp/derphttp"
_ "tailscale.com/envknob"
_ "tailscale.com/ipn"

@ -13,6 +13,7 @@ import (
// process and can cache a prior success when a dependency changes.
_ "inet.af/netaddr"
_ "tailscale.com/chirp"
_ "tailscale.com/control/controlclient"
_ "tailscale.com/derp/derphttp"
_ "tailscale.com/envknob"
_ "tailscale.com/ipn"

@ -13,6 +13,7 @@ import (
// process and can cache a prior success when a dependency changes.
_ "inet.af/netaddr"
_ "tailscale.com/chirp"
_ "tailscale.com/control/controlclient"
_ "tailscale.com/derp/derphttp"
_ "tailscale.com/envknob"
_ "tailscale.com/ipn"

@ -13,6 +13,7 @@ import (
// process and can cache a prior success when a dependency changes.
_ "inet.af/netaddr"
_ "tailscale.com/chirp"
_ "tailscale.com/control/controlclient"
_ "tailscale.com/derp/derphttp"
_ "tailscale.com/envknob"
_ "tailscale.com/ipn"

@ -16,6 +16,7 @@ import (
_ "golang.org/x/sys/windows/svc/mgr"
_ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
_ "inet.af/netaddr"
_ "tailscale.com/control/controlclient"
_ "tailscale.com/derp/derphttp"
_ "tailscale.com/envknob"
_ "tailscale.com/ipn"

Loading…
Cancel
Save