ipn/conf: support RemoteConfig in the config file

Add ConfigVAlpha.RemoteConfig so a user-data/cloud-init config can
delegate remote control to the tailnet admin (see Prefs.RemoteConfig).

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby
2026-07-14 10:29:19 +02:00
committed by Kristoffer Dalby
parent f9cd687180
commit 58fcaaf9a5
3 changed files with 33 additions and 1 deletions
+3 -1
View File
@@ -92,7 +92,9 @@ single AMI supports two ways of joining a tailnet:
```
A config present in user-data locks the CLI (`tailscale set`/`up` are
rejected) unless it sets `"Locked": false`.
rejected) unless it sets `"Locked": false`. Add `"RemoteConfig": true` to
hand full remote management of the node to the tailnet admin (see
`Prefs.RemoteConfig`) — appropriate for admin-owned fleet devices.
- **Interactive (serial console):** launch the AMI with *no* user-data. The
`optional:` prefix means the missing config is not an error, so `tailscaled`
+5
View File
@@ -47,6 +47,7 @@ type ConfigVAlpha struct {
RunSSHServer opt.Bool `json:",omitempty"` // Tailscale SSH
RunWebClient opt.Bool `json:",omitempty"`
ShieldsUp opt.Bool `json:",omitempty"`
RemoteConfig opt.Bool `json:",omitzero"` // delegate full remote control to the tailnet admin; see Prefs.RemoteConfig
AutoUpdate *AutoUpdatePrefs `json:",omitempty"`
ServeConfigTemp *ServeConfig `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this
@@ -167,6 +168,10 @@ func (c *ConfigVAlpha) ToPrefs() (MaskedPrefs, error) {
mp.ShieldsUp = c.ShieldsUp.EqualBool(true)
mp.ShieldsUpSet = true
}
if c.RemoteConfig != "" {
mp.RemoteConfig = c.RemoteConfig.EqualBool(true)
mp.RemoteConfigSet = true
}
if c.AutoUpdate != nil {
mp.AutoUpdate = *c.AutoUpdate
mp.AutoUpdateSet = AutoUpdatePrefsMask{ApplySet: true, CheckSet: true}
+25
View File
@@ -186,3 +186,28 @@ func TestConfigVAlphaToPrefs(t *testing.T) {
})
}
}
func TestConfigVAlphaToPrefsRemoteConfig(t *testing.T) {
for name, tt := range map[string]struct {
cfg ConfigVAlpha
wantSet bool
wantVal bool
}{
"absent": {ConfigVAlpha{Version: "alpha0"}, false, false},
"true": {ConfigVAlpha{Version: "alpha0", RemoteConfig: "true"}, true, true},
"false": {ConfigVAlpha{Version: "alpha0", RemoteConfig: "false"}, true, false},
} {
t.Run(name, func(t *testing.T) {
mp, err := tt.cfg.ToPrefs()
if err != nil {
t.Fatal(err)
}
if mp.RemoteConfigSet != tt.wantSet {
t.Errorf("RemoteConfigSet = %v; want %v", mp.RemoteConfigSet, tt.wantSet)
}
if mp.RemoteConfig != tt.wantVal {
t.Errorf("RemoteConfig = %v; want %v", mp.RemoteConfig, tt.wantVal)
}
})
}
}