Skip to content

Commit

Permalink
ipn/ipnlocal: add support for multiple profiles
Browse files Browse the repository at this point in the history
Currently all user state is stored in ipn.Prefs, which is then peristed
into the specified ipn.StateStore under a single key "_daemon" except in
the case of Windows. Windows kind of already supports multiple profiles
as multiple users can log in the node and go into "Unattended" mode.

It does this by storing another object (under "server-mode-start-key")
in the store which points to the current loaded profile. We reuse the
same mechanism to support multiple profiles on other platforms.
However, we do that behind a new key "_current-profile" which now
points to "profile-<name>" like ("profile-work"). This helps us not
accidentally start the profile at Tailscaled startup on Windows.

By adding a new ProfileManager we also simplify some of the state
machine from LocalBackend as it no longer needs ot own prefs.
We also only expose PrefsView from the ProfileManager so as to simplify
the ownership even further.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
  • Loading branch information
maisem committed Oct 23, 2022
1 parent 01faed5 commit 3b4e84a
Show file tree
Hide file tree
Showing 27 changed files with 839 additions and 307 deletions.
2 changes: 0 additions & 2 deletions cmd/tailscale/cli/up.go
Expand Up @@ -632,7 +632,6 @@ func runUp(ctx context.Context, args []string) (retErr error) {
return err
}
opts := ipn.Options{
StateKey: ipn.GlobalDaemonStateKey,
AuthKey: authKey,
UpdatePrefs: prefs,
}
Expand All @@ -647,7 +646,6 @@ func runUp(ctx context.Context, args []string) (retErr error) {
if effectiveGOOS() == "windows" {
// The Windows service will set this as needed based
// on our connection's identity.
opts.StateKey = ""
opts.Prefs = prefs
}

Expand Down
4 changes: 1 addition & 3 deletions cmd/tailscale/cli/web.go
Expand Up @@ -496,9 +496,7 @@ func tailscaleUp(ctx context.Context, prefs *ipn.Prefs, forceReauth bool) (authU

bc.SetPrefs(prefs)

bc.Start(ipn.Options{
StateKey: ipn.GlobalDaemonStateKey,
})
bc.Start(ipn.Options{})
if forceReauth {
bc.StartLoginInteractive()
}
Expand Down
9 changes: 6 additions & 3 deletions cmd/tailscaled/tailscaled.go
Expand Up @@ -34,7 +34,7 @@ import (
"tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
"tailscale.com/logpolicy"
Expand Down Expand Up @@ -307,7 +307,6 @@ func ipnServerOpts() (o ipnserver.Options) {
fallthrough
default:
o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey
case "windows":
// Not those.
}
Expand Down Expand Up @@ -453,7 +452,11 @@ func run() error {
if err != nil {
return fmt.Errorf("store.New: %w", err)
}
srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, dialer, nil, opts)
pm, err := ipnlocal.NewProfileManager(store, logf, "")
if err != nil {
return fmt.Errorf("ipnlocal.NewProfileManager: %w", err)
}
srv, err := ipnserver.New(logf, pol.PublicID.String(), pm, e, dialer, opts)
if err != nil {
return fmt.Errorf("ipnserver.New: %w", err)
}
Expand Down
7 changes: 5 additions & 2 deletions cmd/tsconnect/wasm/wasm_js.go
Expand Up @@ -124,7 +124,11 @@ func newIPN(jsConfig js.Value) map[string]any {
return ns.DialContextTCP(ctx, dst)
}

srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, nil, ipnserver.Options{
pm, err := ipnlocal.NewProfileManager(store, logf, "wasm")
if err != nil {
log.Fatalf("ipnlocal.NewProfileManager: %v", err)
}
srv, err := ipnserver.New(logf, lpc.PublicID.String(), pm, eng, dialer, ipnserver.Options{
SurviveDisconnects: true,
LoginFlags: controlclient.LoginEphemeral,
})
Expand Down Expand Up @@ -284,7 +288,6 @@ func (i *jsIPN) run(jsCallbacks js.Value) {

go func() {
err := i.lb.Start(ipn.Options{
StateKey: "wasm",
UpdatePrefs: &ipn.Prefs{
ControlURL: i.controlURL,
RouteAll: false,
Expand Down
4 changes: 2 additions & 2 deletions control/controlclient/auto.go
Expand Up @@ -585,7 +585,7 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
}
if nm != nil && loggedIn && synced {
pp := c.direct.GetPersist()
p = &pp
p = pp.AsStruct()
} else {
// don't send netmap status, as it's misleading when we're
// not logged in.
Expand Down Expand Up @@ -703,7 +703,7 @@ func (c *Auto) Shutdown() {
// used exclusively in tests.
func (c *Auto) TestOnlyNodePublicKey() key.NodePublic {
priv := c.direct.GetPersist()
return priv.PrivateNodeKey.Public()
return priv.PrivateNodeKey().Public()
}

func (c *Auto) TestOnlySetAuthKey(authkey string) {
Expand Down
4 changes: 2 additions & 2 deletions control/controlclient/direct.go
Expand Up @@ -317,10 +317,10 @@ func (c *Direct) SetNetInfo(ni *tailcfg.NetInfo) bool {
return true
}

func (c *Direct) GetPersist() persist.Persist {
func (c *Direct) GetPersist() persist.PersistView {
c.mu.Lock()
defer c.mu.Unlock()
return c.persist
return c.persist.View()
}

func (c *Direct) TryLogout(ctx context.Context) error {
Expand Down
22 changes: 6 additions & 16 deletions ipn/backend.go
Expand Up @@ -67,7 +67,7 @@ type Notify struct {

LoginFinished *empty.Message // non-nil when/if the login process succeeded
State *State // if non-nil, the new or current IPN state
Prefs PrefsView // if non-nil, the new or current preferences
Prefs PrefsView // if valid, the new or current preferences
NetMap *netmap.NetworkMap // if non-nil, the new or current netmap
Engine *EngineStatus // if non-nil, the new or current wireguard stats
BrowseToURL *string // if non-nil, UI should open a browser right now
Expand Down Expand Up @@ -178,21 +178,11 @@ type StateKey string
type Options struct {
// FrontendLogID is the public logtail id used by the frontend.
FrontendLogID string
// StateKey and Prefs together define the state the backend should
// use:
// - StateKey=="" && Prefs!=nil: use Prefs for internal state,
// don't persist changes in the backend, except for the machine key
// for migration purposes.
// - StateKey!="" && Prefs==nil: load the given backend-side
// state and use/update that.
// - StateKey!="" && Prefs!=nil: like the previous case, but do
// an initial overwrite of backend state with Prefs.
//
// NOTE(apenwarr): The above means that this Prefs field does not do
// what you probably think it does. It will overwrite your encryption
// keys. Do not use unless you know what you're doing.
StateKey StateKey
Prefs *Prefs
// Prefs is the initial preferences to use. If nil, the current
// profile's preferences are loaded from the store.
// If non-nil, the Prefs are used as-is, and the state store is
// updated to match.
Prefs *Prefs
// UpdatePrefs, if provided, overrides Options.Prefs *and* the Prefs
// already stored in the backend state, *except* for the Persist
// Persist member. If you just want to provide prefs, this is
Expand Down

0 comments on commit 3b4e84a

Please sign in to comment.