cmd/tailscale, ipn: add start of remote-config support

Updates tailscale/corp#18043

Change-Id: I1d19e7ccb16ade93468cbc5698171e04f9539b76
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
brafitz/remote-config
Brad Fitzpatrick 3 months ago
parent 74e33b9c50
commit 3570a10e90

@ -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
}

@ -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) {

@ -56,6 +56,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
RemoteConfig bool
Persist *persist.Persist
}{})

@ -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
}{})

@ -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)
}

@ -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.

@ -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) &&

@ -62,6 +62,7 @@ func TestPrefsEqual(t *testing.T) {
"AppConnector",
"PostureChecking",
"NetfilterKind",
"RemoteConfig",
"Persist",
}
if have := fieldsOf(reflect.TypeFor[Prefs]()); !reflect.DeepEqual(have, prefsHandles) {
@ -435,6 +436,11 @@ func TestPrefsPretty(t *testing.T) {
"windows",
"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",

@ -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

Loading…
Cancel
Save