summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2024-03-06 15:06:59 -0800
committerBrad Fitzpatrick <bradfitz@tailscale.com>2024-03-07 20:57:33 -0800
commit3570a10e908f3abacecc975289413e2572510337 (patch)
tree4f8d31f91f5b4c475fa57c4204ecb21d9b4d28f8
parent74e33b9c508566b188eeadf18087de6f14c96e95 (diff)
downloadtailscale-brafitz/remote-config.tar.xz
tailscale-brafitz/remote-config.zip
cmd/tailscale, ipn: add start of remote-config supportbrafitz/remote-config
Updates tailscale/corp#18043 Change-Id: I1d19e7ccb16ade93468cbc5698171e04f9539b76 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--cmd/tailscale/cli/set.go4
-rw-r--r--cmd/tailscale/cli/up.go1
-rw-r--r--ipn/ipn_clone.go1
-rw-r--r--ipn/ipn_view.go2
-rw-r--r--ipn/ipnlocal/c2n.go38
-rw-r--r--ipn/localapi/localapi.go10
-rw-r--r--ipn/prefs.go9
-rw-r--r--ipn/prefs_test.go6
-rw-r--r--tailcfg/tailcfg.go3
9 files changed, 73 insertions, 1 deletions
diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go
index 02d4f5a06..8048b405b 100644
--- a/cmd/tailscale/cli/set.go
+++ b/cmd/tailscale/cli/set.go
@@ -56,6 +56,7 @@ type setArgsT struct {
updateCheck bool
updateApply bool
postureChecking bool
+ remoteConfig bool
}
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
@@ -76,6 +77,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version")
setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information")
setf.BoolVar(&setArgs.runWebClient, "webclient", false, "run a web interface for managing this node, served over Tailscale at port 5252")
+ setf.BoolVar(&setArgs.remoteConfig, "remote-config", false, "HIDDEN: allow talinet admins to manage this node's settings")
if safesocket.GOOSUsesPeerCreds(goos) {
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
@@ -116,6 +118,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
Hostname: setArgs.hostname,
OperatorUser: setArgs.opUser,
ForceDaemon: setArgs.forceDaemon,
+ RemoteConfig: setArgs.remoteConfig,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: setArgs.updateCheck,
Apply: opt.NewBool(setArgs.updateApply),
@@ -148,6 +151,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
advertiseRoutesSet = true
}
})
+
if maskedPrefs.IsEmpty() {
return flag.ErrHelp
}
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index 43f36f819..3c6eba84b 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -723,6 +723,7 @@ func init() {
addPrefFlagMapping("auto-update", "AutoUpdate.Apply")
addPrefFlagMapping("advertise-connector", "AppConnector")
addPrefFlagMapping("posture-checking", "PostureChecking")
+ addPrefFlagMapping("remote-config", "RemoteConfig")
}
func addPrefFlagMapping(flagName string, prefNames ...string) {
diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go
index 40cc44296..01f83ecff 100644
--- a/ipn/ipn_clone.go
+++ b/ipn/ipn_clone.go
@@ -56,6 +56,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
+ RemoteConfig bool
Persist *persist.Persist
}{})
diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go
index 18436867d..3f9c93beb 100644
--- a/ipn/ipn_view.go
+++ b/ipn/ipn_view.go
@@ -91,6 +91,7 @@ func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpda
func (v PrefsView) AppConnector() AppConnectorPrefs { return v.ж.AppConnector }
func (v PrefsView) PostureChecking() bool { return v.ж.PostureChecking }
func (v PrefsView) NetfilterKind() string { return v.ж.NetfilterKind }
+func (v PrefsView) RemoteConfig() bool { return v.ж.RemoteConfig }
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
@@ -121,6 +122,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
+ RemoteConfig bool
Persist *persist.Persist
}{})
diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go
index 9e6af14de..a76014351 100644
--- a/ipn/ipnlocal/c2n.go
+++ b/ipn/ipnlocal/c2n.go
@@ -26,9 +26,13 @@ import (
"tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/ipn"
+ "tailscale.com/net/netmon"
"tailscale.com/net/sockstats"
"tailscale.com/posture"
"tailscale.com/tailcfg"
+ "tailscale.com/types/logger"
+ "tailscale.com/types/logid"
+ "tailscale.com/types/ptr"
"tailscale.com/util/clientmetric"
"tailscale.com/util/goroutines"
"tailscale.com/util/set"
@@ -72,6 +76,10 @@ var c2nHandlers = map[methodAndPath]c2nHandler{
// Linux netfilter.
req("POST /netfilter-kind"): handleC2NSetNetfilterKind,
+
+ // Remote config
+ req("/remote-config/prefs"): handleC2NRemoteConfigPrefs,
+ // TODO(bradfitz): more (but not all) LocalAPI proxies for remote-config
}
type c2nHandler func(*LocalBackend, http.ResponseWriter, *http.Request)
@@ -569,3 +577,33 @@ func handleC2NTLSCertStatus(b *LocalBackend, w http.ResponseWriter, r *http.Requ
writeJSON(w, ret)
}
+
+// NewC2NLocalAPIHandler is initialized by the localapi package's init func.
+// It returns a new http.Handler that serves LocalAPI for c2n.
+var NewC2NLocalAPIHandler func(b *LocalBackend, logf logger.Logf, netMon *netmon.Monitor, logID logid.PublicID) http.Handler
+
+func handleC2NRemoteConfigPrefs(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
+ prefs := b.Prefs()
+ if !prefs.Valid() || !prefs.RemoteConfig() {
+ http.Error(w, "remote config not enabled", http.StatusForbidden)
+ return
+ }
+ if NewC2NLocalAPIHandler == nil {
+ http.Error(w, "remote config not wired up", http.StatusInternalServerError)
+ return
+ }
+ var newPath string
+ switch r.URL.Path {
+ case "/remote-config/prefs":
+ newPath = "/localapi/v0/prefs"
+ default:
+ http.Error(w, "unsupported path", http.StatusBadRequest)
+ return
+ }
+ r2 := r.WithContext(r.Context())
+ r2.URL = ptr.To(*r.URL) // shallow clone
+ r2.URL.Path = newPath
+
+ h := NewC2NLocalAPIHandler(b, b.logf, b.sys.NetMon.Get(), b.backendLogID)
+ h.ServeHTTP(w, r2)
+}
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index f68f0a282..dd5b0c7bb 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -153,6 +153,16 @@ func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, netMon *netmon.Monit
return &Handler{b: b, logf: logf, netMon: netMon, backendLogID: logID, clock: tstime.StdClock{}}
}
+func init() {
+ ipnlocal.NewC2NLocalAPIHandler = func(b *ipnlocal.LocalBackend, logf logger.Logf, netMon *netmon.Monitor, logID logid.PublicID) http.Handler {
+ h := NewHandler(b, logf, netMon, logID)
+ h.PermitRead, h.PermitWrite = true, true
+ h.PermitCert = false
+ h.ConnIdentity = &ipnauth.ConnIdentity{}
+ return h
+ }
+}
+
type Handler struct {
// RequiredPassword, if non-empty, forces all HTTP
// requests to have HTTP basic auth with this password.
diff --git a/ipn/prefs.go b/ipn/prefs.go
index 7bfbd613f..cae9e1696 100644
--- a/ipn/prefs.go
+++ b/ipn/prefs.go
@@ -222,6 +222,10 @@ type Prefs struct {
// Linux-only.
NetfilterKind string
+ // RemoteConfig specifies whether to allow the control server to
+ // manage this node's settings.
+ RemoteConfig bool
+
// The Persist field is named 'Config' in the file for backward
// compatibility with earlier versions.
// TODO(apenwarr): We should move this out of here, it's not a pref.
@@ -293,6 +297,7 @@ type MaskedPrefs struct {
AppConnectorSet bool `json:",omitempty"`
PostureCheckingSet bool `json:",omitempty"`
NetfilterKindSet bool `json:",omitempty"`
+ RemoteConfigSet bool `json:",omitempty"`
}
type AutoUpdatePrefsMask struct {
@@ -467,6 +472,9 @@ func (p *Prefs) pretty(goos string) string {
if p.ShieldsUp {
sb.WriteString("shields=true ")
}
+ if p.RemoteConfig {
+ sb.WriteString("remoteconfig=true ")
+ }
if p.ExitNodeIP.IsValid() {
fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess)
} else if !p.ExitNodeID.IsZero() {
@@ -549,6 +557,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.OperatorUser == p2.OperatorUser &&
p.Hostname == p2.Hostname &&
p.ForceDaemon == p2.ForceDaemon &&
+ p.RemoteConfig == p2.RemoteConfig &&
compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
p.Persist.Equals(p2.Persist) &&
diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go
index 9251bb2bb..acb7b0feb 100644
--- a/ipn/prefs_test.go
+++ b/ipn/prefs_test.go
@@ -62,6 +62,7 @@ func TestPrefsEqual(t *testing.T) {
"AppConnector",
"PostureChecking",
"NetfilterKind",
+ "RemoteConfig",
"Persist",
}
if have := fieldsOf(reflect.TypeFor[Prefs]()); !reflect.DeepEqual(have, prefsHandles) {
@@ -436,6 +437,11 @@ func TestPrefsPretty(t *testing.T) {
"Prefs{ra=false mesh=false dns=false want=false shields=true update=off Persist=nil}",
},
{
+ Prefs{RemoteConfig: true},
+ "windows",
+ "Prefs{ra=false mesh=false dns=false want=false remoteconfig=true update=off Persist=nil}",
+ },
+ {
Prefs{AllowSingleHosts: true},
"windows",
"Prefs{ra=false dns=false want=false update=off Persist=nil}",
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go
index aeb41ce7e..42353e8ef 100644
--- a/tailcfg/tailcfg.go
+++ b/tailcfg/tailcfg.go
@@ -129,7 +129,8 @@ type CapabilityVersion int
// - 86: 2024-01-23: Client understands NodeAttrProbeUDPLifetime
// - 87: 2024-02-11: UserProfile.Groups removed (added in 66)
// - 88: 2024-03-05: Client understands NodeAttrSuggestExitNode
-const CurrentCapabilityVersion CapabilityVersion = 88
+// - 89: 2024-03-07: c2n remote config prefs
+const CurrentCapabilityVersion CapabilityVersion = 89
type StableID string