Repository: tailscale/tailscale (Go)

Issue:
Posture-checking only accessible with tailscale set (and not tailscale up)
### What is the issue?

Disclaimer: This may be because Posture Checking is a new feature, maybe still in testing?

Nearly all the options that can be set in `tailscale set` commands, like accepting/advertising routes can also be set with `tailscale up` commands to start and enroll a user with the right environment in one shot.

`tailscale set --posture-checking=true` works, but `tailscale up --posture-checking=true` does now.

Also, the help output of `tailscale set` does not list `--posture-checking` as one of the flags you can configure with it.

### Steps to reproduce

```
$ tailscale up --posture-checking=true
flag provided but not defined: -posture-checking
```

```
$ tailscale set
Change specified preferences

USAGE
  tailscale set [flags]

"tailscale set" allows changing specific preferences.

Unlike "tailscale up", this command does not require the complete set of desired settings.

Only settings explicitly mentioned will be set. There are no default values.

FLAGS
  --accept-dns, --accept-dns=false
        accept DNS configuration from the admin panel
<SNIP>
```

### Are there any recent changes that introduced the issue?

_No response_

### OS

Linux

### OS version

Debian 12/Bookworm

### Tailscale version

1.66.3

### Other software

We use posture-checking with Crowdstrike.

### Bug report

_No response_


Discussion:
This has another surprising side effect: without support for `--posture-checking` in `tailscale up`, running `tailscale up` silently resets the `PostureChecking` pref that might have been set via `tailscale set`.

Example:
```bash
$ tailscale debug prefs | grep Posture
"PostureChecking": false,
$ sudo tailscale set --posture-checking=true
$ tailscale debug prefs | grep Posture
"PostureChecking": true,
$ sudo tailscale down
$ tailscale debug prefs | grep Posture
"PostureChecking": true,
$ sudo tailscale up --accept-routes
$ tailscale debug prefs | grep Posture
"PostureChecking": false,
```

So currently Linux users need to re-enable posture checking after every `tailscale up` run.



Patch:
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index b907257cf5a3e..4af264d73a991 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -116,6 +116,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
 	upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
 	upf.BoolVar(&upArgs.advertiseConnector, "advertise-connector", false, "advertise this node as an app connector")
 	upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
+	upf.BoolVar(&upArgs.postureChecking, "posture-checking", false, hidden+"allow management plane to gather device posture information")
 
 	if safesocket.GOOSUsesPeerCreds(goos) {
 		upf.StringVar(&upArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
@@ -194,6 +195,7 @@ type upArgsT struct {
 	timeout                time.Duration
 	acceptedRisks          string
 	profileName            string
+	postureChecking        bool
 }
 
 func (a upArgsT) getAuthKey() (string, error) {
@@ -304,6 +306,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
 	prefs.OperatorUser = upArgs.opUser
 	prefs.ProfileName = upArgs.profileName
 	prefs.AppConnector.Advertise = upArgs.advertiseConnector
+	prefs.PostureChecking = upArgs.postureChecking
 
 	if goos == "linux" {
 		prefs.NoSNAT = !upArgs.snat
@@ -1053,6 +1056,8 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]any) {
 			set(prefs.NetfilterMode.String())
 		case "unattended":
 			set(prefs.ForceDaemon)
+		case "posture-checking":
+			set(prefs.PostureChecking)
 		}
 	})
 	return ret


Test patch:
diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go
index 0444e914c7260..dccb69876d3b4 100644
--- a/cmd/tailscale/cli/cli_test.go
+++ b/cmd/tailscale/cli/cli_test.go
@@ -601,6 +601,19 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
 			goos: "linux",
 			want: "",
 		},
+		{
+			name:  "losing_posture_checking",
+			flags: []string{"--accept-dns"},
+			curPrefs: &ipn.Prefs{
+				ControlURL:          ipn.DefaultControlURL,
+				WantRunning:         false,
+				CorpDNS:             true,
+				PostureChecking:     true,
+				NetfilterMode:       preftype.NetfilterOn,
+				NoStatefulFiltering: opt.NewBool(true),
+			},
+			want: accidentalUpPrefix + " --accept-dns --posture-checking",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -1045,6 +1058,7 @@ func TestUpdatePrefs(t *testing.T) {
 				NoSNATSet:                 true,
 				NoStatefulFilteringSet:    true,
 				OperatorUserSet:           true,
+				PostureCheckingSet:        true,
 				RouteAllSet:               true,
 				RunSSHSet:                 true,
 				ShieldsUpSet:              true,