diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 266afbe9f..1ac15a225 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -40,6 +40,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) { name string flagSet map[string]bool curPrefs *ipn.Prefs + curUser string // os.Getenv("USER") on the client side mp *ipn.MaskedPrefs want string }{ @@ -148,11 +149,43 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) { }, want: "", }, + { + name: "implicit_operator_change", + flagSet: f("hostname"), + curPrefs: &ipn.Prefs{ + ControlURL: ipn.DefaultControlURL, + OperatorUser: "alice", + }, + curUser: "eve", + mp: &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + ControlURL: ipn.DefaultControlURL, + }, + ControlURLSet: true, + }, + want: `'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --operator is not specified but its default value of "" differs from current value "alice"`, + }, + { + name: "implicit_operator_matches_shell_user", + flagSet: f("hostname"), + curPrefs: &ipn.Prefs{ + ControlURL: ipn.DefaultControlURL, + OperatorUser: "alice", + }, + curUser: "alice", + mp: &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + ControlURL: ipn.DefaultControlURL, + }, + ControlURLSet: true, + }, + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got string - if err := checkForAccidentalSettingReverts(tt.flagSet, tt.curPrefs, tt.mp); err != nil { + if err := checkForAccidentalSettingReverts(tt.flagSet, tt.curPrefs, tt.mp, tt.curUser); err != nil { got = err.Error() } if got != tt.want { diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 5393d2df9..61339a406 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -285,7 +285,7 @@ func runUp(ctx context.Context, args []string) error { }) if !upArgs.reset { - if err := checkForAccidentalSettingReverts(flagSet, curPrefs, mp); err != nil { + if err := checkForAccidentalSettingReverts(flagSet, curPrefs, mp, os.Getenv("USER")); err != nil { fatalf("%s", err) } } @@ -500,7 +500,7 @@ func updateMaskedPrefsFromUpFlag(mp *ipn.MaskedPrefs, flagName string) { // // mp is the mask of settings actually set, where mp.Prefs is the new // preferences to set, including any values set from implicit flags. -func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Prefs, mp *ipn.MaskedPrefs) error { +func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Prefs, mp *ipn.MaskedPrefs, curUser string) error { if len(flagSet) == 0 { // A bare "tailscale up" is a special case to just // mean bringing the network up without any changes. @@ -543,6 +543,11 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre if reflect.DeepEqual(exi, imi) { continue } + if flagName == "operator" && imi == "" && exi == curUser { + // Don't require setting operator if the current user matches + // the configured operator. + continue + } switch flagName { case "": errs = append(errs, fmt.Errorf("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; this command would change the value of flagless pref %q", prefName))