Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ipn/ipnlocal: add support for multiple profiles #6022

Merged
merged 2 commits into from Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 2 additions & 19 deletions cmd/tailscale/cli/up.go
Expand Up @@ -634,27 +634,10 @@ func runUp(ctx context.Context, args []string) (retErr error) {
if err != nil {
return err
}
opts := ipn.Options{
StateKey: ipn.GlobalDaemonStateKey,
bc.Start(ipn.Options{
AuthKey: authKey,
UpdatePrefs: prefs,
}
// On Windows, we still run in mostly the "legacy" way that
// predated the server's StateStore. That is, we send an empty
// StateKey and send the prefs directly. Although the Windows
// supports server mode, though, the transition to StateStore
// is only half complete. Only server mode uses it, and the
// Windows service (~tailscaled) is the one that computes the
// StateKey based on the connection identity. So for now, just
// do as the Windows GUI's always done:
if effectiveGOOS() == "windows" {
// The Windows service will set this as needed based
// on our connection's identity.
opts.StateKey = ""
opts.Prefs = prefs
}

bc.Start(opts)
})
if upArgs.forceReauth {
startLoginInteractive()
}
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
4 changes: 1 addition & 3 deletions cmd/tailscaled/tailscaled.go
Expand Up @@ -33,7 +33,6 @@ import (
"tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
"tailscale.com/logpolicy"
Expand Down Expand Up @@ -306,7 +305,6 @@ func ipnServerOpts() (o ipnserver.Options) {
fallthrough
default:
o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey
case "windows":
// Not those.
}
Expand Down Expand Up @@ -452,7 +450,7 @@ 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)
srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, dialer, opts)
if err != nil {
return fmt.Errorf("ipnserver.New: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/tsconnect/wasm/wasm_js.go
Expand Up @@ -124,9 +124,10 @@ 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{
srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, ipnserver.Options{
SurviveDisconnects: true,
LoginFlags: controlclient.LoginEphemeral,
AutostartStateKey: "wasm",
})
if err != nil {
log.Fatalf("ipnserver.New: %v", err)
Expand Down Expand Up @@ -284,7 +285,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 @@ -580,7 +580,7 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM

c.logf("[v1] sendStatus: %s: %v", who, state)

var p *persist.Persist
var p *persist.PersistView
var loginFin, logoutFin *empty.Message
if state == StateAuthenticated {
loginFin = new(empty.Message)
Expand Down Expand Up @@ -708,7 +708,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
22 changes: 14 additions & 8 deletions control/controlclient/direct.go
Expand Up @@ -87,7 +87,7 @@ type Direct struct {
sfGroup singleflight.Group[struct{}, *NoiseClient] // protects noiseClient creation.
noiseClient *NoiseClient

persist persist.Persist
persist persist.PersistView
authKey string
tryingNewKey key.NodePrivate
expiry *time.Time
Expand Down Expand Up @@ -238,7 +238,7 @@ func NewDirect(opts Options) (*Direct, error) {
logf: opts.Logf,
newDecompressor: opts.NewDecompressor,
keepAlive: opts.KeepAlive,
persist: opts.Persist,
persist: opts.Persist.View(),
authKey: opts.AuthKey,
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
Expand Down Expand Up @@ -333,7 +333,7 @@ func (c *Direct) SetTKAHead(tkaHead string) 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
Expand All @@ -346,7 +346,7 @@ func (c *Direct) TryLogout(ctx context.Context) error {
c.logf("[v1] TryLogout control response: mustRegen=%v, newURL=%v, err=%v", mustRegen, newURL, err)

c.mu.Lock()
c.persist = persist.Persist{}
c.persist = new(persist.Persist).View()
c.mu.Unlock()

return err
Expand Down Expand Up @@ -421,7 +421,7 @@ func (c *Direct) hostInfoLocked() *tailcfg.Hostinfo {

func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, newURL string, nks tkatype.MarshaledSignature, err error) {
c.mu.Lock()
persist := c.persist
persist := c.persist.AsStruct()
tryingNewKey := c.tryingNewKey
serverKey := c.serverKey
serverNoiseKey := c.serverNoiseKey
Expand Down Expand Up @@ -633,6 +633,12 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
if resp.Login.LoginName != "" {
persist.LoginName = resp.Login.LoginName
}
persist.UserProfile = tailcfg.UserProfile{
ID: resp.User.ID,
DisplayName: resp.Login.DisplayName,
ProfilePicURL: resp.Login.ProfilePicURL,
LoginName: resp.Login.LoginName,
}

// TODO(crawshaw): RegisterResponse should be able to mechanically
// communicate some extra instructions from the server:
Expand All @@ -654,7 +660,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
// save it for the retry-with-URL
c.tryingNewKey = tryingNewKey
}
c.persist = persist
c.persist = persist.View()
c.mu.Unlock()

if err != nil {
Expand Down Expand Up @@ -817,7 +823,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
return errors.New("getMachinePrivKey returned zero key")
}

if persist.PrivateNodeKey.IsZero() {
if persist.PrivateNodeKey().IsZero() {
return errors.New("privateNodeKey is zero")
}
if backendLogID == "" {
Expand Down Expand Up @@ -961,7 +967,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
}
}()

sess := newMapSession(persist.PrivateNodeKey)
sess := newMapSession(persist.PrivateNodeKey())
sess.logf = c.logf
sess.vlogf = vlogf
sess.machinePubKey = machinePubKey
Expand Down
2 changes: 1 addition & 1 deletion control/controlclient/status.go
Expand Up @@ -75,7 +75,7 @@ type Status struct {
// use them. Please don't use these fields.
// TODO(apenwarr): Unexport or remove these.
State State
Persist *persist.Persist // locally persisted configuration
Persist *persist.PersistView // locally persisted configuration
}

// Equal reports whether s and s2 are equal.
Expand Down
50 changes: 13 additions & 37 deletions ipn/backend.go
Expand Up @@ -20,13 +20,13 @@ import (
type State int

const (
NoState = State(iota)
InUseOtherUser
NeedsLogin
NeedsMachineAuth
Stopped
Starting
Running
NoState State = 0
InUseOtherUser State = 1
NeedsLogin State = 2
NeedsMachineAuth State = 3
Stopped State = 4
Starting State = 5
Running State = 6
)

// GoogleIDToken Type is the tailcfg.Oauth2Token.TokenType for the Google
Expand Down Expand Up @@ -153,21 +153,8 @@ type PartialFile struct {
}

// StateKey is an opaque identifier for a set of LocalBackend state
// (preferences, private keys, etc.).
//
// The reason we need this is that the Tailscale agent may be running
// on a multi-user machine, in a context where a single daemon is
// shared by several consecutive users. Ideally we would just use the
// username of the connected frontend as the StateKey.
//
// Various platforms currently set StateKey in different ways:
//
// - the macOS/iOS GUI apps set it to "ipn-go-bridge"
// - the Android app sets it to "ipn-android"
// - on Windows, it's the empty string (in client mode) or, via
// LocalBackend.userID, a string like "user-$USER_ID" (used in
// server mode).
// - on Linux/etc, it's always "_daemon" (ipn.GlobalDaemonStateKey)
// (preferences, private keys, etc.). It is also used as a key for
// the various LoginProfiles that the instance may be signed into.
//
// Additionally, the StateKey can be debug setting name:
//
Expand All @@ -178,21 +165,10 @@ type StateKey string
type Options struct {
maisem marked this conversation as resolved.
Show resolved Hide resolved
// 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
// LegacyMigrationPrefs are used to migrate preferences from the
// frontend to the backend.
// If non-nil, they are imported as a new profile.
LegacyMigrationPrefs *Prefs `json:"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
4 changes: 2 additions & 2 deletions ipn/fake_test.go
Expand Up @@ -16,13 +16,13 @@ type FakeBackend struct {
}

func (b *FakeBackend) Start(opts Options) error {
b.serverURL = opts.Prefs.ControlURLOrDefault()
b.serverURL = opts.LegacyMigrationPrefs.ControlURLOrDefault()
if b.notify == nil {
panic("FakeBackend.Start: SetNotifyCallback not called")
}
nl := NeedsLogin
if b.notify != nil {
p := opts.Prefs.View()
p := opts.LegacyMigrationPrefs.View()
b.notify(Notify{Prefs: &p})
b.notify(Notify{State: &nl})
}
Expand Down