Bläddra i källkod

cmd/tailscale/cli: add ability to set short names for profiles

This adds a `--nickname` flag to `tailscale login|set`.

Updates #713

Signed-off-by: Maisem Ali <[email protected]>
Maisem Ali 3 år sedan
förälder
incheckning
b94b91c168

+ 13 - 0
cmd/tailscale/cli/cli_test.go

@@ -480,6 +480,19 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
 			distro: "", // not Synology
 			want:   accidentalUpPrefix + " --hostname=foo --accept-routes",
 		},
+		{
+			name:  "profile_name_ignored_in_up",
+			flags: []string{"--hostname=foo"},
+			curPrefs: &ipn.Prefs{
+				ControlURL:       "https://login.tailscale.com",
+				CorpDNS:          true,
+				AllowSingleHosts: true,
+				NetfilterMode:    preftype.NetfilterOn,
+				ProfileName:      "foo",
+			},
+			goos: "linux",
+			want: "",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {

+ 1 - 1
cmd/tailscale/cli/login.go

@@ -27,6 +27,6 @@ This command is currently in alpha and may change in the future.`,
 		if err := localClient.SwitchToEmptyProfile(ctx); err != nil {
 			return err
 		}
-		return runUp(ctx, args, loginArgs)
+		return runUp(ctx, "login", args, loginArgs)
 	},
 }

+ 8 - 0
cmd/tailscale/cli/set.go

@@ -43,11 +43,13 @@ type setArgsT struct {
 	advertiseDefaultRoute  bool
 	opUser                 string
 	acceptedRisks          string
+	profileName            string
 }
 
 func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
 	setf := newFlagSet("set")
 
+	setf.StringVar(&setArgs.profileName, "nickname", "", "nickname for the login profile")
 	setf.BoolVar(&setArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
 	setf.BoolVar(&setArgs.acceptDNS, "accept-dns", false, "accept DNS configuration from the admin panel")
 	setf.StringVar(&setArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node")
@@ -81,6 +83,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
 
 	maskedPrefs := &ipn.MaskedPrefs{
 		Prefs: ipn.Prefs{
+			ProfileName:            setArgs.profileName,
 			RouteAll:               setArgs.acceptRoutes,
 			CorpDNS:                setArgs.acceptDNS,
 			ExitNodeAllowLANAccess: setArgs.exitNodeAllowLANAccess,
@@ -132,6 +135,11 @@ func runSet(ctx context.Context, args []string) (retErr error) {
 			return err
 		}
 	}
+	checkPrefs := curPrefs.Clone()
+	checkPrefs.ApplyEdits(maskedPrefs)
+	if err := localClient.CheckPrefs(ctx, checkPrefs); err != nil {
+		return err
+	}
 
 	_, err = localClient.EditPrefs(ctx, maskedPrefs)
 	return err

+ 15 - 2
cmd/tailscale/cli/up.go

@@ -60,7 +60,7 @@ settings.)
 `),
 	FlagSet: upFlagSet,
 	Exec: func(ctx context.Context, args []string) error {
-		return runUp(ctx, args, upArgsGlobal)
+		return runUp(ctx, "up", args, upArgsGlobal)
 	},
 }
 
@@ -122,6 +122,10 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
 	}
 	upf.DurationVar(&upArgs.timeout, "timeout", 0, "maximum amount of time to wait for tailscaled to enter a Running state; default (0s) blocks forever")
 
+	if cmd == "login" {
+		upf.StringVar(&upArgs.profileName, "nickname", "", "short name for the login profile")
+	}
+
 	if cmd == "up" {
 		// Some flags are only for "up", not "login".
 		upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
@@ -129,6 +133,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
 		upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
 		registerAcceptRiskFlag(upf, &upArgs.acceptedRisks)
 	}
+
 	return upf
 }
 
@@ -163,6 +168,7 @@ type upArgsT struct {
 	json                   bool
 	timeout                time.Duration
 	acceptedRisks          string
+	profileName            string
 }
 
 func (a upArgsT) getAuthKey() (string, error) {
@@ -343,6 +349,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
 	prefs.Hostname = upArgs.hostname
 	prefs.ForceDaemon = upArgs.forceDaemon
 	prefs.OperatorUser = upArgs.opUser
+	prefs.ProfileName = upArgs.profileName
 
 	if goos == "linux" {
 		prefs.NoSNAT = !upArgs.snat
@@ -437,7 +444,7 @@ func presentSSHToggleRisk(wantSSH, haveSSH bool, acceptedRisks string) error {
 	return presentRiskToUser(riskLoseSSH, `You are connected using Tailscale SSH; this action will result in your session disconnecting.`, acceptedRisks)
 }
 
-func runUp(ctx context.Context, args []string, upArgs upArgsT) (retErr error) {
+func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retErr error) {
 	var egg bool
 	if len(args) > 0 {
 		egg = fmt.Sprint(args) == "[up down down left right left right b a]"
@@ -496,6 +503,11 @@ func runUp(ctx context.Context, args []string, upArgs upArgsT) (retErr error) {
 	if err != nil {
 		return err
 	}
+	if cmd == "up" {
+		// "tailscale up" should not be able to change the
+		// profile name.
+		prefs.ProfileName = curPrefs.ProfileName
+	}
 
 	env := upCheckEnv{
 		goos:          effectiveGOOS(),
@@ -764,6 +776,7 @@ func init() {
 	addPrefFlagMapping("unattended", "ForceDaemon")
 	addPrefFlagMapping("operator", "OperatorUser")
 	addPrefFlagMapping("ssh", "RunSSH")
+	addPrefFlagMapping("nickname", "ProfileName")
 }
 
 func addPrefFlagMapping(flagName string, prefNames ...string) {

+ 20 - 0
ipn/ipnlocal/local.go

@@ -2109,6 +2109,9 @@ func (b *LocalBackend) checkPrefsLocked(p *ipn.Prefs) error {
 		// Keep this one just for testing.
 		errs = append(errs, errors.New("bad hostname [test]"))
 	}
+	if err := b.checkProfileNameLocked(p); err != nil {
+		errs = append(errs, err)
+	}
 	if err := b.checkSSHPrefsLocked(p); err != nil {
 		errs = append(errs, err)
 	}
@@ -2226,6 +2229,23 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
 	return stripKeysFromPrefs(newPrefs), nil
 }
 
+func (b *LocalBackend) checkProfileNameLocked(p *ipn.Prefs) error {
+	if p.ProfileName == "" {
+		// It is always okay to clear the profile name.
+		return nil
+	}
+	id := b.pm.ProfileIDForName(p.ProfileName)
+	if id == "" {
+		// No profile with that name exists. That's fine.
+		return nil
+	}
+	if id != b.pm.CurrentProfile().ID {
+		// Name is already in use by another profile.
+		return fmt.Errorf("profile name %q already in use", p.ProfileName)
+	}
+	return nil
+}
+
 // SetPrefs saves new user preferences and propagates them throughout
 // the system. Implements Backend.
 func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {

+ 10 - 0
ipn/ipnlocal/profiles.go

@@ -96,6 +96,16 @@ func (pm *profileManager) findProfilesByUserID(userID tailcfg.UserID) []*ipn.Log
 	return out
 }
 
+// ProfileIDForName returns the profile ID for the profile with the
+// given name. It returns "" if no such profile exists.
+func (pm *profileManager) ProfileIDForName(name string) ipn.ProfileID {
+	p := pm.findProfileByName(name)
+	if p == nil {
+		return ""
+	}
+	return p.ID
+}
+
 func (pm *profileManager) findProfileByName(name string) *ipn.LoginProfile {
 	for _, p := range pm.knownProfiles {
 		if p.Name == name {

+ 1 - 1
ipn/prefs.go

@@ -190,7 +190,7 @@ type Prefs struct {
 	// operate tailscaled without being root or using sudo.
 	OperatorUser string `json:",omitempty"`
 
-	// ProfileName is the desired name of the profile. If empty, then the users
+	// ProfileName is the desired name of the profile. If empty, then the user's
 	// LoginName is used. It is only used for display purposes in the client UI
 	// and CLI.
 	ProfileName string `json:",omitempty"`