mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-15 01:53:08 -04:00
Add a new Prefs.RemoteConfig bool. When true, a c2n endpoint at
/remoteapi/localapi/* proxies into this node's LocalAPI at
/localapi/* with full read/write permission, giving the tailnet
admin the same API surface a local root/admin user has via the
tailscale CLI. All LocalAPI versions (v0, v1, ...) proxy through.
RemoteConfig is an alternative to Tailscale's default per-feature
double opt-in, in which both the tailnet admin and the local machine
owner must consent to each individual setting change. It is a single
client-side "I trust the tailnet admin" switch that, once on, hands
over full remote management of this node's settings and LocalAPI
without any further local prompt or confirmation.
This is only appropriate when the tailnet admin already owns the
machine (e.g. a corporate fleet device) or the local user has
explicitly delegated full control. It should never be enabled on a
personal/BYOD device with an untrusted tailnet admin. The trust
model is documented on the pref, on the hidden --remote-config CLI
flag, and on the feature/remoteconfig package.
The node advertises its RemoteConfig state to the control plane via
a new Hostinfo.RemoteConfig bool. This is only true when the feature
is both compiled in (buildfeatures.HasRemoteConfig) and its init
actually ran (feature.IsRegistered("remoteconfig")); tsnet builds
have the former but not the latter and correctly report false.
The handler lives in feature/remoteconfig and can be omitted with the
ts_omit_remoteconfig build tag. tsnet's TestDeps guards against
accidentally pulling it in.
Updates tailscale/corp#18043
Change-Id: I72ce10a90a0e4e738c72c940af3af64c986160b2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package feature tracks which features are linked into the binary.
|
|
package feature
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
|
|
"tailscale.com/util/testenv"
|
|
)
|
|
|
|
var ErrUnavailable = errors.New("feature not included in this build")
|
|
|
|
var in = map[string]bool{}
|
|
|
|
// Registered reports the set of registered features.
|
|
//
|
|
// The returned map should not be modified by the caller,
|
|
// not accessed concurrently with calls to Register.
|
|
func Registered() map[string]bool { return in }
|
|
|
|
// IsRegistered reports whether the named feature package's init
|
|
// function has run and registered itself via [Register] in this
|
|
// binary. It is distinct from the compile-time [buildfeatures]
|
|
// constants: a feature package can be present in the binary but not
|
|
// imported (e.g. tsnet deliberately does not import many features),
|
|
// in which case its init does not run.
|
|
func IsRegistered(name string) bool { return in[name] }
|
|
|
|
// Register notes that the named feature is linked into the binary.
|
|
func Register(name string) {
|
|
if _, ok := in[name]; ok {
|
|
panic("duplicate feature registration for " + name)
|
|
}
|
|
in[name] = true
|
|
}
|
|
|
|
// Hook is a func that can only be set once.
|
|
//
|
|
// It is not safe for concurrent use.
|
|
type Hook[Func any] struct {
|
|
f Func
|
|
ok bool
|
|
}
|
|
|
|
// IsSet reports whether the hook has been set.
|
|
func (h *Hook[Func]) IsSet() bool {
|
|
return h.ok
|
|
}
|
|
|
|
// Set sets the hook function, panicking if it's already been set
|
|
// or f is the zero value.
|
|
//
|
|
// It's meant to be called in init.
|
|
func (h *Hook[Func]) Set(f Func) {
|
|
if h.ok {
|
|
panic("Set on already-set feature hook")
|
|
}
|
|
if reflect.ValueOf(f).IsZero() {
|
|
panic("Set with zero value")
|
|
}
|
|
h.f = f
|
|
h.ok = true
|
|
}
|
|
|
|
// SetForTest sets the hook function for tests, blowing
|
|
// away any previous value. It will panic if called from
|
|
// non-test code.
|
|
//
|
|
// It returns a restore function that resets the hook
|
|
// to its previous value.
|
|
func (h *Hook[Func]) SetForTest(f Func) (restore func()) {
|
|
testenv.AssertInTest()
|
|
old := *h
|
|
h.f, h.ok = f, true
|
|
return func() { *h = old }
|
|
}
|
|
|
|
// Get returns the hook function, or panics if it hasn't been set.
|
|
// Use IsSet to check if it's been set, or use GetOrNil if you're
|
|
// okay with a nil return value.
|
|
func (h *Hook[Func]) Get() Func {
|
|
if !h.ok {
|
|
panic("Get on unset feature hook, without IsSet")
|
|
}
|
|
return h.f
|
|
}
|
|
|
|
// GetOk returns the hook function and true if it has been set,
|
|
// otherwise its zero value and false.
|
|
func (h *Hook[Func]) GetOk() (f Func, ok bool) {
|
|
return h.f, h.ok
|
|
}
|
|
|
|
// GetOrNil returns the hook function or nil if it hasn't been set.
|
|
func (h *Hook[Func]) GetOrNil() Func {
|
|
return h.f
|
|
}
|
|
|
|
// Hooks is a slice of funcs.
|
|
//
|
|
// As opposed to a single Hook, this is meant to be used when
|
|
// multiple parties are able to install the same hook.
|
|
type Hooks[Func any] []Func
|
|
|
|
// Add adds a hook to the list of hooks.
|
|
//
|
|
// Add should only be called during early program
|
|
// startup before Tailscale has started.
|
|
// It is not safe for concurrent use.
|
|
func (h *Hooks[Func]) Add(f Func) {
|
|
if reflect.ValueOf(f).IsZero() {
|
|
panic("Add with zero value")
|
|
}
|
|
*h = append(*h, f)
|
|
}
|