Update dependencies

This commit is contained in:
bluepython508
2025-04-09 01:00:12 +01:00
parent f0641ffd6e
commit 5a9cfc022c
882 changed files with 68930 additions and 24201 deletions

View File

@@ -4,6 +4,7 @@
package localapi
import (
"cmp"
"context"
"crypto/tls"
"encoding/json"
@@ -81,7 +82,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
client *http.Client = http.DefaultClient
)
checkConn := func(derpNode *tailcfg.DERPNode) bool {
port := firstNonzero(derpNode.DERPPort, 443)
port := cmp.Or(derpNode.DERPPort, 443)
var (
hasIPv4 bool
@@ -89,7 +90,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
)
// Check IPv4 first
addr := net.JoinHostPort(firstNonzero(derpNode.IPv4, derpNode.HostName), strconv.Itoa(port))
addr := net.JoinHostPort(cmp.Or(derpNode.IPv4, derpNode.HostName), strconv.Itoa(port))
conn, err := dialer.DialContext(ctx, "tcp4", addr)
if err != nil {
st.Errors = append(st.Errors, fmt.Sprintf("Error connecting to node %q @ %q over IPv4: %v", derpNode.HostName, addr, err))
@@ -98,7 +99,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
// Upgrade to TLS and verify that works properly.
tlsConn := tls.Client(conn, &tls.Config{
ServerName: firstNonzero(derpNode.CertName, derpNode.HostName),
ServerName: cmp.Or(derpNode.CertName, derpNode.HostName),
})
if err := tlsConn.HandshakeContext(ctx); err != nil {
st.Errors = append(st.Errors, fmt.Sprintf("Error upgrading connection to node %q @ %q to TLS over IPv4: %v", derpNode.HostName, addr, err))
@@ -108,7 +109,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
}
// Check IPv6
addr = net.JoinHostPort(firstNonzero(derpNode.IPv6, derpNode.HostName), strconv.Itoa(port))
addr = net.JoinHostPort(cmp.Or(derpNode.IPv6, derpNode.HostName), strconv.Itoa(port))
conn, err = dialer.DialContext(ctx, "tcp6", addr)
if err != nil {
st.Errors = append(st.Errors, fmt.Sprintf("Error connecting to node %q @ %q over IPv6: %v", derpNode.HostName, addr, err))
@@ -117,7 +118,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
// Upgrade to TLS and verify that works properly.
tlsConn := tls.Client(conn, &tls.Config{
ServerName: firstNonzero(derpNode.CertName, derpNode.HostName),
ServerName: cmp.Or(derpNode.CertName, derpNode.HostName),
// TODO(andrew-d): we should print more
// detailed failure information on if/why TLS
// verification fails
@@ -166,7 +167,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
addr = addrs[0]
}
addrPort := netip.AddrPortFrom(addr, uint16(firstNonzero(derpNode.STUNPort, 3478)))
addrPort := netip.AddrPortFrom(addr, uint16(cmp.Or(derpNode.STUNPort, 3478)))
txID := stun.NewTxID()
req := stun.Request(txID)
@@ -230,8 +231,14 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
connSuccess := checkConn(derpNode)
// Verify that the /generate_204 endpoint works
captivePortalURL := "http://" + derpNode.HostName + "/generate_204"
resp, err := client.Get(captivePortalURL)
captivePortalURL := fmt.Sprintf("http://%s/generate_204?t=%d", derpNode.HostName, time.Now().Unix())
req, err := http.NewRequest("GET", captivePortalURL, nil)
if err != nil {
st.Warnings = append(st.Warnings, fmt.Sprintf("Internal error creating request for captive portal check: %v", err))
continue
}
req.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate, no-transform, max-age=0")
resp, err := client.Do(req)
if err != nil {
st.Warnings = append(st.Warnings, fmt.Sprintf("Error making request to the captive portal check %q; is port 80 blocked?", captivePortalURL))
} else {
@@ -292,13 +299,3 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
// issued in the first place, tell them specifically that the
// cert is bad not just that the connection failed.
}
func firstNonzero[T comparable](items ...T) T {
var zero T
for _, item := range items {
if item != zero {
return item
}
}
return zero
}

View File

@@ -62,32 +62,34 @@ import (
"tailscale.com/util/osdiag"
"tailscale.com/util/progresstracking"
"tailscale.com/util/rands"
"tailscale.com/util/testenv"
"tailscale.com/util/syspolicy/rsop"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/version"
"tailscale.com/wgengine/magicsock"
)
type localAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
type LocalAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
// handler is the set of LocalAPI handlers, keyed by the part of the
// Request.URL.Path after "/localapi/v0/". If the key ends with a trailing slash
// then it's a prefix match.
var handler = map[string]localAPIHandler{
var handler = map[string]LocalAPIHandler{
// The prefix match handlers end with a slash:
"cert/": (*Handler).serveCert,
"file-put/": (*Handler).serveFilePut,
"files/": (*Handler).serveFiles,
"policy/": (*Handler).servePolicy,
"profiles/": (*Handler).serveProfiles,
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
// without a trailing slash:
"alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690
"bugreport": (*Handler).serveBugReport,
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
"check-prefs": (*Handler).serveCheckPrefs,
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
"component-debug-logging": (*Handler).serveComponentDebugLogging,
"debug": (*Handler).serveDebug,
"debug-capture": (*Handler).serveDebugCapture,
"debug-derp-region": (*Handler).serveDebugDERPRegion,
"debug-dial-types": (*Handler).serveDebugDialTypes,
"debug-log": (*Handler).serveDebugLog,
@@ -98,6 +100,7 @@ var handler = map[string]localAPIHandler{
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"disconnect-control": (*Handler).disconnectControl,
"dns-osconfig": (*Handler).serveDNSOSConfig,
"dns-query": (*Handler).serveDNSQuery,
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
@@ -148,6 +151,14 @@ var handler = map[string]localAPIHandler{
"whois": (*Handler).serveWhoIs,
}
// Register registers a new LocalAPI handler for the given name.
func Register(name string, fn LocalAPIHandler) {
if _, ok := handler[name]; ok {
panic("duplicate LocalAPI handler registration: " + name)
}
handler[name] = fn
}
var (
// The clientmetrics package is stateful, but we want to expose a simple
// imperative API to local clients, so we need to keep track of
@@ -158,10 +169,9 @@ var (
metrics = map[string]*clientmetric.Metric{}
)
// NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon
// are required (if non-nil it's used to do faster interface lookups).
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler {
return &Handler{b: b, logf: logf, backendLogID: logID, clock: tstime.StdClock{}}
// NewHandler creates a new LocalAPI HTTP handler. All parameters are required.
func NewHandler(actor ipnauth.Actor, b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler {
return &Handler{Actor: actor, b: b, logf: logf, backendLogID: logID, clock: tstime.StdClock{}}
}
type Handler struct {
@@ -192,6 +202,10 @@ type Handler struct {
clock tstime.Clock
}
func (h *Handler) LocalBackend() *ipnlocal.LocalBackend {
return h.b
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.b == nil {
http.Error(w, "server has no local backend", http.StatusInternalServerError)
@@ -256,7 +270,7 @@ func (h *Handler) validHost(hostname string) bool {
// handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
// (the path doesn't include any query parameters)
func handlerForPath(urlPath string) (h localAPIHandler, ok bool) {
func handlerForPath(urlPath string) (h LocalAPIHandler, ok bool) {
if urlPath == "/" {
return (*Handler).serveLocalAPIRoot, true
}
@@ -443,6 +457,33 @@ func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
h.serveWhoIsWithBackend(w, r, h.b)
}
// serveSetDeviceAttrs is (as of 2024-12-30) an experimental LocalAPI handler to
// set device attributes via the control plane.
//
// See tailscale/corp#24690.
func (h *Handler) serveSetDeviceAttrs(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if !h.PermitWrite {
http.Error(w, "set-device-attrs access denied", http.StatusForbidden)
return
}
if r.Method != "PATCH" {
http.Error(w, "only PATCH allowed", http.StatusMethodNotAllowed)
return
}
var req map[string]any
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := h.b.SetDeviceAttrs(ctx, req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, "{}\n")
}
// localBackendWhoIsMethods is the subset of ipn.LocalBackend as needed
// by the localapi WhoIs method.
type localBackendWhoIsMethods interface {
@@ -560,6 +601,7 @@ func (h *Handler) serveLogTap(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) {
metricDebugMetricsCalls.Add(1)
// Require write access out of paranoia that the metrics
// might contain something sensitive.
if !h.PermitWrite {
@@ -570,15 +612,10 @@ func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) {
clientmetric.WritePrometheusExpositionFormat(w)
}
// TODO(kradalby): Remove this once we have landed on a final set of
// metrics to export to clients and consider the metrics stable.
var debugUsermetricsEndpoint = envknob.RegisterBool("TS_DEBUG_USER_METRICS")
// serveUserMetrics returns user-facing metrics in Prometheus text
// exposition format.
func (h *Handler) serveUserMetrics(w http.ResponseWriter, r *http.Request) {
if !testenv.InTest() && !debugUsermetricsEndpoint() {
http.Error(w, "usermetrics debug flag not enabled", http.StatusForbidden)
return
}
metricUserMetricsCalls.Add(1)
h.b.UserMetricsRegistry().Handler(w, r)
}
@@ -635,6 +672,13 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) {
}
case "pick-new-derp":
err = h.b.DebugPickNewDERP()
case "force-prefer-derp":
var n int
err = json.NewDecoder(r.Body).Decode(&n)
if err != nil {
break
}
h.b.DebugForcePreferDERP(n)
case "":
err = fmt.Errorf("missing parameter 'action'")
default:
@@ -956,6 +1000,22 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
servePprofFunc(w, r)
}
// disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that
// node no longer communicates with control. Doing this makes control consider this node inactive. This can be used
// before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
// peers to switch over to another replica whilst still maintaining th existing peer connections.
func (h *Handler) disconnectControl(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
return
}
if r.Method != httpm.POST {
http.Error(w, "use POST", http.StatusMethodNotAllowed)
return
}
h.b.DisconnectControl()
}
func (h *Handler) reloadConfig(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
@@ -1047,7 +1107,7 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) {
func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeConfig, h *Handler) error {
switch goos {
case "windows", "linux", "darwin":
case "windows", "linux", "darwin", "illumos", "solaris":
default:
return nil
}
@@ -1067,7 +1127,7 @@ func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeC
switch goos {
case "windows":
return errors.New("must be a Windows local admin to serve a path")
case "linux", "darwin":
case "linux", "darwin", "illumos", "solaris":
return errors.New("must be root, or be an operator and able to run 'sudo tailscale' to serve a path")
default:
// We filter goos at the start of the func, this default case
@@ -1231,7 +1291,7 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
ctx := r.Context()
enc := json.NewEncoder(w)
h.b.WatchNotifications(ctx, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) {
h.b.WatchNotificationsAs(ctx, h.Actor, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) {
err := enc.Encode(roNotify)
if err != nil {
h.logf("json.Encode: %v", err)
@@ -1251,7 +1311,7 @@ func (h *Handler) serveLoginInteractive(w http.ResponseWriter, r *http.Request)
http.Error(w, "want POST", http.StatusBadRequest)
return
}
h.b.StartLoginInteractive(r.Context())
h.b.StartLoginInteractiveAs(r.Context(), h.Actor)
w.WriteHeader(http.StatusNoContent)
return
}
@@ -1320,7 +1380,7 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
return
}
var err error
prefs, err = h.b.EditPrefs(mp)
prefs, err = h.b.EditPrefsAs(mp, h.Actor)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
@@ -1339,6 +1399,53 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
e.Encode(prefs)
}
func (h *Handler) servePolicy(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "policy access denied", http.StatusForbidden)
return
}
suffix, ok := strings.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/policy/")
if !ok {
http.Error(w, "misconfigured", http.StatusInternalServerError)
return
}
var scope setting.PolicyScope
if suffix == "" {
scope = setting.DefaultScope()
} else if err := scope.UnmarshalText([]byte(suffix)); err != nil {
http.Error(w, fmt.Sprintf("%q is not a valid scope", suffix), http.StatusBadRequest)
return
}
policy, err := rsop.PolicyFor(scope)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var effectivePolicy *setting.Snapshot
switch r.Method {
case "GET":
effectivePolicy = policy.Get()
case "POST":
effectivePolicy, err = policy.Reload()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
default:
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
e := json.NewEncoder(w)
e.SetIndent("", "\t")
e.Encode(effectivePolicy)
}
type resJSON struct {
Error string `json:",omitempty"`
}
@@ -2493,8 +2600,8 @@ func (h *Handler) serveProfiles(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case httpm.GET:
profiles := h.b.ListProfiles()
profileIndex := slices.IndexFunc(profiles, func(p ipn.LoginProfile) bool {
return p.ID == profileID
profileIndex := slices.IndexFunc(profiles, func(p ipn.LoginProfileView) bool {
return p.ID() == profileID
})
if profileIndex == -1 {
http.Error(w, "Profile not found", http.StatusNotFound)
@@ -2592,21 +2699,6 @@ func defBool(a string, def bool) bool {
return v
}
func (h *Handler) serveDebugCapture(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "debug access denied", http.StatusForbidden)
return
}
if r.Method != "POST" {
http.Error(w, "POST required", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
h.b.StreamDebugCapture(r.Context(), w)
}
func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "debug-log access denied", http.StatusForbidden)
@@ -2912,7 +3004,9 @@ var (
metricInvalidRequests = clientmetric.NewCounter("localapi_invalid_requests")
// User-visible LocalAPI endpoints.
metricFilePutCalls = clientmetric.NewCounter("localapi_file_put")
metricFilePutCalls = clientmetric.NewCounter("localapi_file_put")
metricDebugMetricsCalls = clientmetric.NewCounter("localapi_debugmetric_requests")
metricUserMetricsCalls = clientmetric.NewCounter("localapi_usermetric_requests")
)
// serveSuggestExitNode serves a POST endpoint for returning a suggested exit node.