diff --git a/cmd/tailscale/cli/switch.go b/cmd/tailscale/cli/switch.go index 857b88d8b..ffbf552a9 100644 --- a/cmd/tailscale/cli/switch.go +++ b/cmd/tailscale/cli/switch.go @@ -8,8 +8,6 @@ import ( "flag" "fmt" "os" - "strings" - "text/tabwriter" "time" "github.com/peterbourgon/ff/v3/ffcli" @@ -27,14 +25,10 @@ var switchCmd = &ffcli.Command{ Exec: switchProfile, UsageFunc: func(*ffcli.Command) string { return `USAGE - switch + switch switch --list -"tailscale switch" switches between logged in accounts. You can -use the ID that's returned from 'tailnet switch -list' -to pick which profile you want to switch to. Alternatively, you -can use the Tailnet or the account names to switch as well. - +"tailscale switch" switches between logged in accounts. This command is currently in alpha and may change in the future.` }, } @@ -48,22 +42,12 @@ func listProfiles(ctx context.Context) error { if err != nil { return err } - tw := tabwriter.NewWriter(os.Stdout, 2, 2, 2, ' ', 0) - defer tw.Flush() - printRow := func(vals ...string) { - fmt.Fprintln(tw, strings.Join(vals, "\t")) - } - printRow("ID", "Tailnet", "Account") for _, prof := range all { - name := prof.Name if prof.ID == curP.ID { - name += "*" + fmt.Printf("%s *\n", prof.Name) + } else { + fmt.Println(prof.Name) } - printRow( - string(prof.ID), - prof.NetworkProfile.DomainName, - name, - ) } return nil } @@ -82,30 +66,12 @@ func switchProfile(ctx context.Context, args []string) error { os.Exit(1) } var profID ipn.ProfileID - // Allow matching by ID, Tailnet, or Account - // in that order. for _, p := range all { - if p.ID == ipn.ProfileID(args[0]) { + if p.Name == args[0] { profID = p.ID break } } - if profID == "" { - for _, p := range all { - if p.NetworkProfile.DomainName == args[0] { - profID = p.ID - break - } - } - } - if profID == "" { - for _, p := range all { - if p.Name == args[0] { - profID = p.ID - break - } - } - } if profID == "" { errf("No profile named %q\n", args[0]) os.Exit(1) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 48253fa99..b0de68f2c 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -341,7 +341,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo return nil, err } p.ApplyEdits(&mp) - if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(p.View(), ""); err != nil { return nil, err } } @@ -1105,19 +1105,10 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control prefsChanged = true } - // Until recently, we did not store the account's tailnet name. So check if this is the case, - // and backfill it on incoming status update. - if b.pm.requiresBackfill() && st.NetMap != nil && st.NetMap.Domain != "" { - prefsChanged = true - } - // Perform all mutations of prefs based on the netmap here. if prefsChanged { // Prefs will be written out if stale; this is not safe unless locked or cloned. - if err := b.pm.SetPrefs(prefs.View(), ipn.NetworkProfile{ - MagicDNSName: st.NetMap.MagicDNSSuffix(), - DomainName: st.NetMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(prefs.View(), st.NetMap.MagicDNSSuffix()); err != nil { b.logf("Failed to save new controlclient state: %v", err) } } @@ -1173,10 +1164,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control b.mu.Lock() prefs.WantRunning = false p := prefs.View() - if err := b.pm.SetPrefs(p, ipn.NetworkProfile{ - MagicDNSName: st.NetMap.MagicDNSSuffix(), - DomainName: st.NetMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(p, st.NetMap.MagicDNSSuffix()); err != nil { b.logf("Failed to save new controlclient state: %v", err) } b.mu.Unlock() @@ -1585,10 +1573,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { newPrefs := opts.UpdatePrefs.Clone() newPrefs.Persist = oldPrefs.Persist().AsStruct() pv := newPrefs.View() - if err := b.pm.SetPrefs(pv, ipn.NetworkProfile{ - MagicDNSName: b.netMap.MagicDNSSuffix(), - DomainName: b.netMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(pv, b.netMap.MagicDNSSuffix()); err != nil { b.logf("failed to save UpdatePrefs state: %v", err) } b.setAtomicValuesFromPrefsLocked(pv) @@ -2494,10 +2479,7 @@ func (b *LocalBackend) migrateStateLocked(prefs *ipn.Prefs) (err error) { // Backend owns the state, but frontend is trying to migrate // state into the backend. b.logf("importing frontend prefs into backend store; frontend prefs: %s", prefs.Pretty()) - if err := b.pm.SetPrefs(prefs.View(), ipn.NetworkProfile{ - MagicDNSName: b.netMap.MagicDNSSuffix(), - DomainName: b.netMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(prefs.View(), b.netMap.MagicDNSSuffix()); err != nil { return fmt.Errorf("store.WriteState: %v", err) } } @@ -3078,10 +3060,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn } prefs := newp.View() - if err := b.pm.SetPrefs(prefs, ipn.NetworkProfile{ - MagicDNSName: b.netMap.MagicDNSSuffix(), - DomainName: b.netMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(prefs, b.netMap.MagicDNSSuffix()); err != nil { b.logf("failed to save new controlclient state: %v", err) } b.lastProfileID = b.pm.CurrentProfile().ID diff --git a/ipn/ipnlocal/network-lock.go b/ipn/ipnlocal/network-lock.go index cfe33147b..11cebcca3 100644 --- a/ipn/ipnlocal/network-lock.go +++ b/ipn/ipnlocal/network-lock.go @@ -578,10 +578,7 @@ func (b *LocalBackend) NetworkLockForceLocalDisable() error { newPrefs := b.pm.CurrentPrefs().AsStruct().Clone() // .Persist should always be initialized here. newPrefs.Persist.DisallowedTKAStateIDs = append(newPrefs.Persist.DisallowedTKAStateIDs, stateID) - if err := b.pm.SetPrefs(newPrefs.View(), ipn.NetworkProfile{ - MagicDNSName: b.netMap.MagicDNSSuffix(), - DomainName: b.netMap.DomainName(), - }); err != nil { + if err := b.pm.SetPrefs(newPrefs.View(), b.netMap.MagicDNSSuffix()); err != nil { return fmt.Errorf("saving prefs: %w", err) } diff --git a/ipn/ipnlocal/network-lock_test.go b/ipn/ipnlocal/network-lock_test.go index da317f343..db5a7f0f6 100644 --- a/ipn/ipnlocal/network-lock_test.go +++ b/ipn/ipnlocal/network-lock_test.go @@ -151,7 +151,7 @@ func TestTKAEnablementFlow(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) b := LocalBackend{ capTailnetLock: true, varRoot: temp, @@ -191,7 +191,7 @@ func TestTKADisablementFlow(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) temp := t.TempDir() tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID)) @@ -383,7 +383,7 @@ func TestTKASync(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) // Setup the tka authority on the control plane. key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} @@ -605,7 +605,7 @@ func TestTKADisable(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) temp := t.TempDir() tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID)) @@ -696,7 +696,7 @@ func TestTKASign(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) // Make a fake TKA authority, to seed local state. disablementSecret := bytes.Repeat([]byte{0xa5}, 32) @@ -785,7 +785,7 @@ func TestTKAForceDisable(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) temp := t.TempDir() tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID)) @@ -880,7 +880,7 @@ func TestTKAAffectedSigs(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) // Make a fake TKA authority, to seed local state. disablementSecret := bytes.Repeat([]byte{0xa5}, 32) @@ -1013,7 +1013,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: nlPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) // Make a fake TKA authority, to seed local state. disablementSecret := bytes.Repeat([]byte{0xa5}, 32) @@ -1104,7 +1104,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) { PrivateNodeKey: nodePriv, NetworkLockKey: cosignPriv, }, - }).View(), ipn.NetworkProfile{})) + }).View(), "")) b := LocalBackend{ varRoot: temp, logf: t.Logf, diff --git a/ipn/ipnlocal/peerapi_test.go b/ipn/ipnlocal/peerapi_test.go index 74e1f4e25..8f54affc9 100644 --- a/ipn/ipnlocal/peerapi_test.go +++ b/ipn/ipnlocal/peerapi_test.go @@ -657,7 +657,7 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) { netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"), }, - }).View(), ipn.NetworkProfile{}) + }).View(), "") if !h.ps.b.OfferingExitNode() { t.Fatal("unexpectedly not offering exit node") } diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index 55bb6a1a1..74e5c52bd 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -207,10 +207,11 @@ func init() { // It also saves the prefs to the StateStore. It stores a copy of the // provided prefs, which may be accessed via CurrentPrefs. // -// NetworkProfile stores additional information about the tailnet the user -// is logged into so that we can keep track of things like their domain name -// across user switches to disambiguate the same account but a different tailnet. -func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile) error { +// If tailnetMagicDNSName is provided non-empty, it will be used to +// enrich the profile with the tailnet's MagicDNS name. The MagicDNS +// name cannot be pulled from prefsIn directly because it is not saved +// on ipn.Prefs (since it's not a field that is configurable by nodes). +func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, tailnetMagicDNSName string) error { prefs := prefsIn.AsStruct() newPersist := prefs.Persist if newPersist == nil || newPersist.NodeID == "" || newPersist.UserProfile.LoginName == "" { @@ -254,7 +255,9 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile) cp.ControlURL = prefs.ControlURL cp.UserProfile = newPersist.UserProfile cp.NodeID = newPersist.NodeID - cp.NetworkProfile = np + if tailnetMagicDNSName != "" { + cp.TailnetMagicDNSName = tailnetMagicDNSName + } pm.knownProfiles[cp.ID] = cp pm.currentProfile = cp if err := pm.writeKnownProfiles(); err != nil { @@ -598,7 +601,7 @@ func (pm *profileManager) migrateFromLegacyPrefs() error { return fmt.Errorf("load legacy prefs: %w", err) } pm.dlogf("loaded legacy preferences; sentinel=%q", sentinel) - if err := pm.SetPrefs(prefs, ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(prefs, ""); err != nil { metricMigrationError.Add(1) return fmt.Errorf("migrating _daemon profile: %w", err) } @@ -608,12 +611,6 @@ func (pm *profileManager) migrateFromLegacyPrefs() error { return nil } -func (pm *profileManager) requiresBackfill() bool { - return pm != nil && - pm.currentProfile != nil && - pm.currentProfile.NetworkProfile.RequiresBackfill() -} - var ( metricNewProfile = clientmetric.NewCounter("profiles_new") metricSwitchProfile = clientmetric.NewCounter("profiles_switch") diff --git a/ipn/ipnlocal/profiles_test.go b/ipn/ipnlocal/profiles_test.go index 47f75baba..fa5c89b63 100644 --- a/ipn/ipnlocal/profiles_test.go +++ b/ipn/ipnlocal/profiles_test.go @@ -41,7 +41,7 @@ func TestProfileCurrentUserSwitch(t *testing.T) { LoginName: loginName, }, } - if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(p.View(), ""); err != nil { t.Fatal(err) } return p.View() @@ -96,7 +96,7 @@ func TestProfileList(t *testing.T) { LoginName: loginName, }, } - if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(p.View(), ""); err != nil { t.Fatal(err) } return p.View() @@ -157,7 +157,7 @@ func TestProfileDupe(t *testing.T) { reauth := func(pm *profileManager, p *persist.Persist) { prefs := ipn.NewPrefs() prefs.Persist = p - must.Do(pm.SetPrefs(prefs.View(), ipn.NetworkProfile{})) + must.Do(pm.SetPrefs(prefs.View(), "")) } login := func(pm *profileManager, p *persist.Persist) { pm.NewProfile() @@ -379,7 +379,7 @@ func TestProfileManagement(t *testing.T) { }, NodeID: nid, } - if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(p.View(), ""); err != nil { t.Fatal(err) } return p.View() @@ -506,7 +506,7 @@ func TestProfileManagementWindows(t *testing.T) { }, NodeID: tailcfg.StableNodeID(strconv.Itoa(int(id))), } - if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil { + if err := pm.SetPrefs(p.View(), ""); err != nil { t.Fatal(err) } return p.View() diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 6e7791acd..0ac687bb5 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -923,7 +923,7 @@ func TestEditPrefsHasNoKeys(t *testing.T) { LegacyFrontendPrivateMachineKey: key.NewMachine(), }, - }).View(), ipn.NetworkProfile{}) + }).View(), "") if p := b.pm.CurrentPrefs().Persist(); !p.Valid() || p.PrivateNodeKey().IsZero() { t.Fatalf("PrivateNodeKey not set") } diff --git a/ipn/prefs.go b/ipn/prefs.go index 71aef0733..6f4d5fb06 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -790,23 +790,6 @@ type ProfileID string // tests. type WindowsUserID string -// NetworkProfile is a subset of netmap.NetworkMap -// that should be saved with each user profile. -type NetworkProfile struct { - MagicDNSName string - DomainName string -} - -// RequiresBackfill returns whether this object does not have all the data -// expected. This is because this struct is a later addition to LoginProfile and -// this method can be checked to see if it's been backfilled to the current -// expectation or not. Note that for now, it just checks if the struct is empty. -// In the future, if we have new optional fields, this method can be changed to -// do more explicit checks to return whether it's apt for a backfill or not. -func (n NetworkProfile) RequiresBackfill() bool { - return n == NetworkProfile{} -} - // LoginProfile represents a single login profile as managed // by the ProfileManager. type LoginProfile struct { @@ -821,12 +804,13 @@ type LoginProfile struct { // It is filled in from the UserProfile.LoginName field. Name string - // NetworkProfile is a subset of netmap.NetworkMap that we - // store to remember information about the tailnet that this - // profile was logged in with. + // TailnetMagicDNSName is filled with the MagicDNS suffix for this + // profile's node (even if MagicDNS isn't necessarily in use). + // It will neither start nor end with a period. // - // This field was added on 2023-11-17. - NetworkProfile NetworkProfile + // TailnetMagicDNSName is only filled from 2023-09-09 forward, + // and will only get backfilled when a profile is the current profile. + TailnetMagicDNSName string // Key is the StateKey under which the profile is stored. // It is assigned once at profile creation time and never changes. diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index 233dcc656..7511544a5 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -177,16 +177,6 @@ func (nm *NetworkMap) MagicDNSSuffix() string { return MagicDNSSuffixOfNodeName(nm.Name) } -// DomainName returns the name of the NetworkMap's -// current tailnet. If the map is nil, it returns -// an empty string. -func (nm *NetworkMap) DomainName() string { - if nm == nil { - return "" - } - return nm.Domain -} - // SelfCapabilities returns SelfNode.Capabilities if nm and nm.SelfNode are // non-nil. This is a method so we can use it in envknob/logknob without a // circular dependency.