This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+75 -6
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package tsweb contains code used in various Tailscale webservers.
@@ -13,6 +13,8 @@ import (
"expvar"
"fmt"
"io"
"log"
"maps"
"net"
"net/http"
"net/netip"
@@ -53,6 +55,50 @@ func IsProd443(addr string) bool {
return port == "443" || port == "https"
}
// debugTrustedCIDRs is the envknob for TS_DEBUG_TRUSTED_CIDRS, a
// comma-separated list of CIDR ranges (e.g. "10.0.0.0/8,172.16.0.0/12")
// whose source IPs are allowed to access debug endpoints without Tailscale
// authentication. This will supersede both IsTailscaleIP() and
// TS_ALLOW_DEBUG_IP.
var debugTrustedCIDRs = envknob.RegisterString("TS_DEBUG_TRUSTED_CIDRS")
// trustedCIDRs returns the parsed CIDR prefixes from TS_DEBUG_TRUSTED_CIDRS.
var trustedCIDRs = sync.OnceValue(func() []netip.Prefix {
return parseTrustedCIDRs(debugTrustedCIDRs())
})
// parseTrustedCIDRs parses a comma-separated list of CIDR prefixes.
// It fatals on invalid entries, consistent with other envknob parsing.
func parseTrustedCIDRs(raw string) []netip.Prefix {
if raw == "" {
return nil
}
var prefixes []netip.Prefix
for s := range strings.SplitSeq(raw, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
pfx, err := netip.ParsePrefix(s)
if err != nil {
log.Fatalf("invalid CIDR in TS_DEBUG_TRUSTED_CIDRS: %q: %v", s, err)
}
prefixes = append(prefixes, pfx)
}
return prefixes
}
// cidrsContain checks if the source IP is associated with one of the
// provided cidrs.
func cidrsContain(cidrs []netip.Prefix, ip netip.Addr) bool {
for _, pfx := range cidrs {
if pfx.Contains(ip) {
return true
}
}
return false
}
// AllowDebugAccess reports whether r should be permitted to access
// various debug endpoints.
func AllowDebugAccess(r *http.Request) bool {
@@ -74,6 +120,9 @@ func AllowDebugAccess(r *http.Request) bool {
if tsaddr.IsTailscaleIP(ip) || ip.IsLoopback() || ipStr == envknob.String("TS_ALLOW_DEBUG_IP") {
return true
}
if cidrsContain(trustedCIDRs(), ip) {
return true
}
return false
}
@@ -687,6 +736,13 @@ func (lg loggingResponseWriter) Flush() {
f.Flush()
}
// FlushError is the error-returning form of Flush that
// [http.ResponseController.Flush] prefers, so handlers can see write
// errors (such as an expired write deadline) that Flush would swallow.
func (lg *loggingResponseWriter) FlushError() error {
return http.NewResponseController(lg.ResponseWriter).Flush()
}
func (lg *loggingResponseWriter) Unwrap() http.ResponseWriter {
return lg.ResponseWriter
}
@@ -734,8 +790,14 @@ func (h errorHandler) handleError(w http.ResponseWriter, r *http.Request, lw *lo
// Extract a presentable, loggable error.
var hOK bool
var hErr HTTPError
if errors.As(err, &hErr) {
hErr, hAsOK := errors.AsType[HTTPError](err)
if !hAsOK {
if hs, ok := errors.AsType[HTTPStatuser](err); ok {
hErr = hs.HTTPStatus()
hAsOK = true
}
}
if hAsOK {
hOK = true
if hErr.Code == 0 {
lw.logf("[unexpected] HTTPError %v did not contain an HTTP status code, sending internal server error", hErr)
@@ -854,9 +916,7 @@ func WriteHTTPError(w http.ResponseWriter, r *http.Request, e HTTPError) {
h.Set("X-Content-Type-Options", "nosniff")
// Custom headers from the error.
for k, vs := range e.Header {
h[k] = vs
}
maps.Copy(h, e.Header)
// Write the msg back to the user.
w.WriteHeader(e.Code)
@@ -872,6 +932,15 @@ func WriteHTTPError(w http.ResponseWriter, r *http.Request, e HTTPError) {
}
}
// HTTPStatuser is an optional interface implemented by errors that
// carry an intended HTTP response. Handlers translating errors to
// HTTP should honour the returned HTTPError rather than defaulting to
// 500.
type HTTPStatuser interface {
error
HTTPStatus() HTTPError
}
// HTTPError is an error with embedded HTTP response information.
//
// It is the error type to be (optionally) used by Handler.ServeHTTPReturn.