Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+13
-6
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !android && !js && !ts_omit_acme
|
||||
@@ -6,12 +6,15 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/tsweb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -39,11 +42,15 @@ func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
pair, err := h.b.GetCertPEMWithValidity(r.Context(), domain, minValidity)
|
||||
if err != nil {
|
||||
// TODO(bradfitz): 500 is a little lazy here. The errors returned from
|
||||
// GetCertPEM (and everywhere) should carry info info to get whether
|
||||
// they're 400 vs 403 vs 500 at minimum. And then we should have helpers
|
||||
// (in tsweb probably) to return an error that looks at the error value
|
||||
// to determine the HTTP status code.
|
||||
if hs, ok := errors.AsType[tsweb.HTTPStatuser](err); ok {
|
||||
resp := hs.HTTPStatus()
|
||||
maps.Copy(w.Header(), resp.Header)
|
||||
http.Error(w, resp.Msg, resp.Code)
|
||||
return
|
||||
}
|
||||
// TODO(bradfitz): 500 is a little lazy. Other errors from GetCertPEM
|
||||
// should also implement tsweb.HTTPStatuser (400 vs 403 vs 500 vs …)
|
||||
// rather than falling through here.
|
||||
http.Error(w, fmt.Sprint(err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
+106
-12
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_debug
|
||||
@@ -6,8 +6,10 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -23,6 +25,7 @@ import (
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/httpm"
|
||||
@@ -35,6 +38,7 @@ func init() {
|
||||
Register("dev-set-state-store", (*Handler).serveDevSetStateStore)
|
||||
Register("debug-bus-events", (*Handler).serveDebugBusEvents)
|
||||
Register("debug-bus-graph", (*Handler).serveEventBusGraph)
|
||||
Register("debug-bus-queues", (*Handler).serveDebugBusQueues)
|
||||
Register("debug-derp-region", (*Handler).serveDebugDERPRegion)
|
||||
Register("debug-dial-types", (*Handler).serveDebugDialTypes)
|
||||
Register("debug-log", (*Handler).serveDebugLog)
|
||||
@@ -140,14 +144,11 @@ func (h *Handler) serveDebugDialTypes(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, dialer := range dialers {
|
||||
dialer := dialer // loop capture
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
conn, err := dialer.dial(ctx, network, addr)
|
||||
results <- result{dialer.name, conn, err}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@@ -200,8 +201,6 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) {
|
||||
break
|
||||
}
|
||||
h.b.DebugNotify(n)
|
||||
case "notify-last-netmap":
|
||||
h.b.DebugNotifyLastNetMap()
|
||||
case "break-tcp-conns":
|
||||
err = h.b.DebugBreakTCPConns()
|
||||
case "break-derp-conns":
|
||||
@@ -218,7 +217,7 @@ 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
|
||||
var n tailcfg.DERPRegionID
|
||||
err = json.NewDecoder(r.Body).Decode(&n)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -233,8 +232,39 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
case "peer-disco-keys":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(h.b.DebugPeerDiscoKeys())
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
case "rotate-disco-key":
|
||||
err = h.b.DebugRotateDiscoKey()
|
||||
case "statedir":
|
||||
root := h.b.TailscaleVarRoot()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(root)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
case "clear-netmap-cache":
|
||||
h.b.ClearNetmapCache(r.Context())
|
||||
case "current-netmap":
|
||||
// Return the current netmap (with peers populated) as JSON. This
|
||||
// is a debug-only path: the netmap.NetworkMap shape is an
|
||||
// internal type and may change without notice. Production
|
||||
// callers should fetch the narrower bits they need via their
|
||||
// own LocalAPI methods instead.
|
||||
nm := h.b.NetMapWithPeers()
|
||||
if nm == nil {
|
||||
err = errors.New("no netmap")
|
||||
break
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(nm)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
case "":
|
||||
err = fmt.Errorf("missing parameter 'action'")
|
||||
default:
|
||||
@@ -253,6 +283,13 @@ func (h *Handler) serveDevSetStateStore(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// state keys are otherwise gated by their own handlers, e.g. serve-config
|
||||
// requires a local admin for Unix-socket targets; writing a _serve/<profile>
|
||||
// key here would bypass that, so require a local admin too
|
||||
if !h.Actor.IsLocalAdmin(h.b.OperatorUserID()) {
|
||||
http.Error(w, "dev-set-state-store access denied; must be a local admin", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
@@ -270,7 +307,7 @@ func (h *Handler) serveDebugPacketFilterRules(w http.ResponseWriter, r *http.Req
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusNotFound)
|
||||
return
|
||||
@@ -287,7 +324,7 @@ func (h *Handler) serveDebugPacketFilterMatches(w http.ResponseWriter, r *http.R
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusNotFound)
|
||||
return
|
||||
@@ -424,12 +461,68 @@ func (h *Handler) serveEventBusGraph(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(topics)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugBusQueues(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != httpm.GET {
|
||||
http.Error(w, "GET required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
bus, ok := h.LocalBackend().Sys().Bus.GetOK()
|
||||
if !ok {
|
||||
http.Error(w, "event bus not running", http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
|
||||
debugger := bus.Debugger()
|
||||
|
||||
type clientQueue struct {
|
||||
Name string `json:"name"`
|
||||
SubscribeDepth int `json:"subscribeDepth"`
|
||||
SubscribeTypes []string `json:"subscribeTypes,omitempty"`
|
||||
PublishTypes []string `json:"publishTypes,omitempty"`
|
||||
}
|
||||
|
||||
publishQueue := debugger.PublishQueue()
|
||||
clients := debugger.Clients()
|
||||
result := struct {
|
||||
PublishQueueDepth int `json:"publishQueueDepth"`
|
||||
Clients []clientQueue `json:"clients"`
|
||||
}{
|
||||
PublishQueueDepth: len(publishQueue),
|
||||
}
|
||||
|
||||
for _, c := range clients {
|
||||
sq := debugger.SubscribeQueue(c)
|
||||
cq := clientQueue{
|
||||
Name: c.Name(),
|
||||
SubscribeDepth: len(sq),
|
||||
}
|
||||
for _, t := range debugger.SubscribeTypes(c) {
|
||||
cq.SubscribeTypes = append(cq.SubscribeTypes, t.String())
|
||||
}
|
||||
for _, t := range debugger.PublishTypes(c) {
|
||||
cq.PublishTypes = append(cq.PublishTypes, t.String())
|
||||
}
|
||||
result.Clients = append(result.Clients, cq)
|
||||
}
|
||||
|
||||
slices.SortFunc(result.Clients, func(a, b clientQueue) int {
|
||||
if a.SubscribeDepth != b.SubscribeDepth {
|
||||
return b.SubscribeDepth - a.SubscribeDepth
|
||||
}
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasLogTail {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if !h.PermitRead {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug-log access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -472,6 +565,7 @@ func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) serveDebugOptionalFeatures(w http.ResponseWriter, r *http.Request) {
|
||||
of := &apitype.OptionalFeatures{
|
||||
Features: feature.Registered(),
|
||||
Disabled: feature.EnvDisabled(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(of)
|
||||
|
||||
+22
-11
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_debug
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tailscale.com/derp/derphttp"
|
||||
@@ -22,11 +23,28 @@ import (
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/nettype"
|
||||
)
|
||||
|
||||
// tlsConfigForNode builds a *tls.Config for connecting to a DERP node,
|
||||
// mirroring the logic in derphttp.Client.tlsClient so that sha256-raw cert
|
||||
// pinning and domain-fronting CertName values are handled correctly.
|
||||
func tlsConfigForNode(node *tailcfg.DERPNode) *tls.Config {
|
||||
conf := tlsdial.Config(nil, nil)
|
||||
conf.ServerName = node.HostName
|
||||
if node.CertName != "" {
|
||||
if suf, ok := strings.CutPrefix(node.CertName, "sha256-raw:"); ok {
|
||||
tlsdial.SetConfigExpectedCertHash(conf, suf)
|
||||
} else {
|
||||
tlsdial.SetConfigExpectedCert(conf, node.CertName)
|
||||
}
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
@@ -50,7 +68,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
regStr := r.FormValue("region")
|
||||
var reg *tailcfg.DERPRegion
|
||||
if id, err := strconv.Atoi(regStr); err == nil {
|
||||
if id, err := tailcfg.ParseDERPRegionID(regStr); err == nil {
|
||||
reg = dm.Regions[id]
|
||||
} else {
|
||||
for _, r := range dm.Regions {
|
||||
@@ -100,9 +118,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
defer conn.Close()
|
||||
|
||||
// Upgrade to TLS and verify that works properly.
|
||||
tlsConn := tls.Client(conn, &tls.Config{
|
||||
ServerName: cmp.Or(derpNode.CertName, derpNode.HostName),
|
||||
})
|
||||
tlsConn := tls.Client(conn, tlsConfigForNode(derpNode))
|
||||
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))
|
||||
} else {
|
||||
@@ -119,12 +135,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
defer conn.Close()
|
||||
|
||||
// Upgrade to TLS and verify that works properly.
|
||||
tlsConn := tls.Client(conn, &tls.Config{
|
||||
ServerName: cmp.Or(derpNode.CertName, derpNode.HostName),
|
||||
// TODO(andrew-d): we should print more
|
||||
// detailed failure information on if/why TLS
|
||||
// verification fails
|
||||
})
|
||||
tlsConn := tls.Client(conn, tlsConfigForNode(derpNode))
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
st.Errors = append(st.Errors, fmt.Sprintf("Error upgrading connection to node %q @ %q to TLS over IPv6: %v", derpNode.HostName, addr, err))
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios || android || js
|
||||
|
||||
+258
-64
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package localapi contains the HTTP server handlers for tailscaled's API server.
|
||||
@@ -7,6 +7,7 @@ package localapi
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -28,13 +29,13 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health/healthmsg"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnauth"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -43,8 +44,8 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/def"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/httpm"
|
||||
"tailscale.com/util/mak"
|
||||
@@ -73,20 +74,25 @@ var handler = map[string]LocalAPIHandler{
|
||||
|
||||
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
|
||||
// without a trailing slash:
|
||||
"cert-domains": (*Handler).serveCertDomains,
|
||||
"check-prefs": (*Handler).serveCheckPrefs,
|
||||
"check-so-mark-in-use": (*Handler).serveCheckSOMarkInUse,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dns-config": (*Handler).serveDNSConfig,
|
||||
"goroutines": (*Handler).serveGoroutines,
|
||||
"login-interactive": (*Handler).serveLoginInteractive,
|
||||
"logout": (*Handler).serveLogout,
|
||||
"peer-by-id": (*Handler).servePeerByID,
|
||||
"ping": (*Handler).servePing,
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"reload-config": (*Handler).reloadConfig,
|
||||
"reset-auth": (*Handler).serveResetAuth,
|
||||
"services": (*Handler).serveServices,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"shutdown": (*Handler).serveShutdown,
|
||||
"start": (*Handler).serveStart,
|
||||
"status": (*Handler).serveStatus,
|
||||
"user-profile": (*Handler).serveUserProfile,
|
||||
"whois": (*Handler).serveWhoIs,
|
||||
}
|
||||
|
||||
@@ -99,9 +105,6 @@ func init() {
|
||||
Register("check-udp-gro-forwarding", (*Handler).serveCheckUDPGROForwarding)
|
||||
Register("set-udp-gro-forwarding", (*Handler).serveSetUDPGROForwarding)
|
||||
}
|
||||
if buildfeatures.HasUseExitNode && runtime.GOOS == "linux" {
|
||||
Register("check-reverse-path-filtering", (*Handler).serveCheckReversePathFiltering)
|
||||
}
|
||||
if buildfeatures.HasClientMetrics {
|
||||
Register("upload-client-metrics", (*Handler).serveUploadClientMetrics)
|
||||
}
|
||||
@@ -119,7 +122,7 @@ func init() {
|
||||
Register("bugreport", (*Handler).serveBugReport)
|
||||
Register("pprof", (*Handler).servePprof)
|
||||
}
|
||||
if buildfeatures.HasDebug || buildfeatures.HasServe {
|
||||
if buildfeatures.HasIPNBus {
|
||||
Register("watch-ipn-bus", (*Handler).serveWatchIPNBus)
|
||||
}
|
||||
if buildfeatures.HasDNS {
|
||||
@@ -266,6 +269,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
defer h.b.CheckDeadlocks()()
|
||||
if fn, route, ok := handlerForPath(r.URL.Path); ok {
|
||||
h.logRequest(r.Method, route)
|
||||
fn(h, w, r)
|
||||
@@ -350,7 +354,7 @@ func (h *Handler) serveIDToken(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "id-token access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
@@ -400,7 +404,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
defer h.b.TryFlushLogs() // kick off upload after bugreport's done logging
|
||||
|
||||
logMarker := func() string {
|
||||
return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, h.clock.Now().UTC().Format("20060102150405Z"), rands.HexString(16))
|
||||
return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, h.clock.Now().UTC().Format(tstime.NumericDateTimeZ), rands.HexString(16))
|
||||
}
|
||||
if envknob.NoLogsNoSupport() {
|
||||
logMarker = func() string { return "BUG-NO-LOGS-NO-SUPPORT-this-node-has-had-its-logging-disabled" }
|
||||
@@ -420,7 +424,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Information about the current node from the netmap
|
||||
if nm := h.b.NetMap(); nm != nil {
|
||||
if nm := h.b.NetMapNoPeers(); nm != nil {
|
||||
if self := nm.SelfNode; self.Valid() {
|
||||
h.logf("user bugreport node info: nodeid=%q stableid=%q expiry=%q", self.ID(), self.StableID(), self.KeyExpiry().Format(time.RFC3339))
|
||||
}
|
||||
@@ -438,7 +442,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
h.logf.JSON(1, "UserBugReportOS", osdiag.SupportInfo(osdiag.LogSupportInfoReasonBugReport))
|
||||
|
||||
// Tailnet Lock details
|
||||
st := h.b.NetworkLockStatus()
|
||||
st := h.b.TailnetLockStatus()
|
||||
if st.Enabled {
|
||||
h.logf.JSON(1, "UserBugReportTailnetLockStatus", st)
|
||||
if st.NodeKeySignature != nil {
|
||||
@@ -446,7 +450,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if defBool(r.URL.Query().Get("diagnose"), false) {
|
||||
if def.Bool(r.URL.Query().Get("diagnose"), false) {
|
||||
if f, ok := ipnlocal.HookDoctor.GetOk(); ok {
|
||||
f(r.Context(), h.b, logger.WithPrefix(h.logf, "diag: "))
|
||||
}
|
||||
@@ -456,7 +460,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Nothing else to do if we're not in record mode; we wrote the marker
|
||||
// above, so we can just finish our response now.
|
||||
if !defBool(r.URL.Query().Get("record"), false) {
|
||||
if !def.Bool(r.URL.Query().Get("record"), false) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -545,6 +549,8 @@ type localBackendWhoIsMethods interface {
|
||||
WhoIs(string, netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
|
||||
WhoIsNodeKey(key.NodePublic) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
|
||||
PeerCaps(netip.Addr) tailcfg.PeerCapMap
|
||||
PeerCapsForIP(src, dst netip.Addr) tailcfg.PeerCapMap
|
||||
PeerCapsForService(src netip.Addr, svcName tailcfg.ServiceName) tailcfg.PeerCapMap
|
||||
}
|
||||
|
||||
func (h *Handler) serveWhoIsWithBackend(w http.ResponseWriter, r *http.Request, b localBackendWhoIsMethods) {
|
||||
@@ -592,7 +598,25 @@ func (h *Handler) serveWhoIsWithBackend(w http.ResponseWriter, r *http.Request,
|
||||
UserProfile: &u, // always non-nil per WhoIsResponse contract
|
||||
}
|
||||
if n.Addresses().Len() > 0 {
|
||||
res.CapMap = b.PeerCaps(n.Addresses().At(0).Addr())
|
||||
src := n.Addresses().At(0).Addr()
|
||||
switch {
|
||||
case r.FormValue("svc_name") != "":
|
||||
svcName := tailcfg.AsServiceName(r.FormValue("svc_name"))
|
||||
if svcName == "" {
|
||||
http.Error(w, "invalid svc_name", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res.CapMap = b.PeerCapsForService(src, svcName)
|
||||
case r.FormValue("dst_ip") != "":
|
||||
svcAddr, err := netip.ParseAddr(r.FormValue("dst_ip"))
|
||||
if err != nil {
|
||||
http.Error(w, "invalid dst_ip", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res.CapMap = b.PeerCapsForIP(src, svcAddr)
|
||||
default:
|
||||
res.CapMap = b.PeerCaps(src)
|
||||
}
|
||||
}
|
||||
j, err := json.MarshalIndent(res, "", "\t")
|
||||
if err != nil {
|
||||
@@ -779,32 +803,6 @@ func (h *Handler) serveCheckSOMarkInUse(w http.ResponseWriter, r *http.Request)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) serveCheckReversePathFiltering(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "reverse path filtering check access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
var warning string
|
||||
|
||||
state := h.b.Sys().NetMon.Get().InterfaceState()
|
||||
warn, err := netutil.CheckReversePathFiltering(state)
|
||||
if err == nil && len(warn) > 0 {
|
||||
var msg strings.Builder
|
||||
msg.WriteString(healthmsg.WarnExitNodeUsage + ":\n")
|
||||
for _, w := range warn {
|
||||
msg.WriteString("- " + w + "\n")
|
||||
}
|
||||
msg.WriteString(healthmsg.DisableRPFilter)
|
||||
warning = msg.String()
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(struct {
|
||||
Warning string
|
||||
}{
|
||||
Warning: warning,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) serveCheckUDPGROForwarding(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "UDP GRO forwarding check access denied", http.StatusForbidden)
|
||||
@@ -850,7 +848,7 @@ func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var st *ipnstate.Status
|
||||
if defBool(r.FormValue("peers"), true) {
|
||||
if def.Bool(r.FormValue("peers"), true) {
|
||||
st = h.b.Status()
|
||||
} else {
|
||||
st = h.b.StatusWithoutPeers()
|
||||
@@ -874,8 +872,8 @@ func InUseOtherUserIPNStream(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
js, err := json.Marshal(&ipn.Notify{
|
||||
Version: version.Long(),
|
||||
State: ptr.To(ipn.InUseOtherUser),
|
||||
ErrMessage: ptr.To(err.Error()),
|
||||
State: new(ipn.InUseOtherUser),
|
||||
ErrMessage: new(err.Error()),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -899,12 +897,18 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var mask ipn.NotifyWatchOpt
|
||||
if s := r.FormValue("mask"); s != "" {
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
if err := mask.UnmarshalText([]byte(s)); err != nil {
|
||||
http.Error(w, "bad mask", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
mask = ipn.NotifyWatchOpt(v)
|
||||
}
|
||||
if mask&ipn.NotifyInProcessNoDisconnect != 0 {
|
||||
http.Error(w, "NotifyInProcessNoDisconnect is only valid for in-process IPN bus subscribers", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := ipn.ValidateNotifyWatchOpt(mask); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -913,7 +917,9 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
if !neterror.IsClosedPipeError(err) {
|
||||
h.logf("json.Encode: %v", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
f.Flush()
|
||||
@@ -1100,6 +1106,121 @@ func (h *Handler) serveDERPMap(w http.ResponseWriter, r *http.Request) {
|
||||
e.Encode(h.b.DERPMap())
|
||||
}
|
||||
|
||||
// serveCertDomains returns the list of DNS.CertDomains from the current
|
||||
// netmap, or an empty list if no netmap has been received yet.
|
||||
// The returned list is sorted in ascending order.
|
||||
func (h *Handler) serveCertDomains(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "cert-domains access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
var domains []string
|
||||
if nm := h.b.NetMapNoPeers(); nm != nil {
|
||||
domains = slices.Clone(nm.DNS.CertDomains)
|
||||
slices.Sort(domains)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(domains)
|
||||
}
|
||||
|
||||
// serveDNSConfig returns the [tailcfg.DNSConfig] from the current netmap.
|
||||
// It returns 503 if no netmap has been received yet.
|
||||
func (h *Handler) serveDNSConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "dns-config access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(nm.DNS)
|
||||
}
|
||||
|
||||
// peerByIDBackend is the subset of [ipnlocal.LocalBackend] used by
|
||||
// [Handler.servePeerByID]. It exists so the handler can be tested with a
|
||||
// trivial mock without spinning up a full LocalBackend.
|
||||
type peerByIDBackend interface {
|
||||
PeerByID(tailcfg.NodeID) (tailcfg.NodeView, bool)
|
||||
}
|
||||
|
||||
// servePeerByID returns the current full [tailcfg.Node] for the peer with the
|
||||
// NodeID given in the "id" query parameter. It returns 404 if no such peer is
|
||||
// in the current netmap.
|
||||
//
|
||||
// It is intended for clients that observed a peer-mutation signal (e.g.
|
||||
// [ipn.Notify.PeerChangedPatch] or [ipn.Notify.PeersChanged]) and want the
|
||||
// latest state of the affected node without having to apply the patch
|
||||
// themselves.
|
||||
func (h *Handler) servePeerByID(w http.ResponseWriter, r *http.Request) {
|
||||
h.servePeerByIDWithBackend(w, r, h.b)
|
||||
}
|
||||
|
||||
func (h *Handler) servePeerByIDWithBackend(w http.ResponseWriter, r *http.Request, b peerByIDBackend) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "peer-by-id access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
idStr := r.FormValue("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "invalid 'id' parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
nv, ok := b.PeerByID(tailcfg.NodeID(id))
|
||||
if !ok {
|
||||
http.Error(w, "no peer with that NodeID", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(nv.AsStruct())
|
||||
}
|
||||
|
||||
// userProfileBackend is the subset of [ipnlocal.LocalBackend] used by
|
||||
// [Handler.serveUserProfile]. It exists so the handler can be tested
|
||||
// with a trivial mock without spinning up a full LocalBackend.
|
||||
type userProfileBackend interface {
|
||||
UserProfile(tailcfg.UserID) (tailcfg.UserProfileView, bool)
|
||||
}
|
||||
|
||||
// serveUserProfile returns the current [tailcfg.UserProfile] for the User
|
||||
// with the UserID given in the "id" query parameter, in O(1) time. It
|
||||
// returns 404 if no such user is in the current netmap.
|
||||
//
|
||||
// It is the LocalAPI fallback for IPN-bus consumers that see a UserID
|
||||
// referenced by a peer Node and want to resolve it to a UserProfile.
|
||||
func (h *Handler) serveUserProfile(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveUserProfileWithBackend(w, r, h.b)
|
||||
}
|
||||
|
||||
func (h *Handler) serveUserProfileWithBackend(w http.ResponseWriter, r *http.Request, b userProfileBackend) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "user-profile access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
idStr := r.FormValue("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "invalid 'id' parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
uv, ok := b.UserProfile(tailcfg.UserID(id))
|
||||
if !ok {
|
||||
http.Error(w, "no user with that UserID", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(uv.AsStruct())
|
||||
}
|
||||
|
||||
// serveSetExpirySooner sets the expiry date on the current machine, specified
|
||||
// by an `expiry` unix timestamp as POST or query param.
|
||||
func (h *Handler) serveSetExpirySooner(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1196,15 +1317,33 @@ func (h *Handler) serveDial(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "missing Dial-Host or Dial-Port header", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
network := cmp.Or(r.Header.Get("Dial-Network"), "tcp")
|
||||
|
||||
addr := net.JoinHostPort(hostStr, portStr)
|
||||
|
||||
// Check whether the resolved address is a Tailscale route.
|
||||
// If not, tell the client to dial it directly so the connection
|
||||
// comes from the calling user's UID rather than our root-owned daemon.
|
||||
ipp, viaTailscale, err := h.b.Dialer().UserDialPlan(r.Context(), network, addr)
|
||||
if err != nil {
|
||||
http.Error(w, "resolve failure: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
if !viaTailscale {
|
||||
w.Header().Set("Dial-Self", "true")
|
||||
w.Header().Set("Dial-Addr", ipp.String())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
network := cmp.Or(r.Header.Get("Dial-Network"), "tcp")
|
||||
|
||||
addr := net.JoinHostPort(hostStr, portStr)
|
||||
// Dial via Tailscale with the original hostname so UserDial can
|
||||
// resolve all addresses and race across families (happy eyeballs).
|
||||
outConn, err := h.b.Dialer().UserDial(r.Context(), network, addr)
|
||||
if err != nil {
|
||||
http.Error(w, "dial failure: "+err.Error(), http.StatusBadGateway)
|
||||
@@ -1485,7 +1624,7 @@ func (h *Handler) serveQueryFeature(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "missing feature", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
@@ -1521,17 +1660,6 @@ func (h *Handler) serveQueryFeature(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func defBool(a string, def bool) bool {
|
||||
if a == "" {
|
||||
return def
|
||||
}
|
||||
v, err := strconv.ParseBool(a)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// serveUpdateCheck returns the ClientVersion from Status, which contains
|
||||
// information on whether an update is available, and if so, what version,
|
||||
// *if* we support auto-updates on this platform. If we don't, this endpoint
|
||||
@@ -1681,16 +1809,68 @@ func dnsMessageTypeForString(s string) (t dnsmessage.Type, err error) {
|
||||
return 0, errors.New("unknown DNS message type: " + s)
|
||||
}
|
||||
|
||||
// HookRouteCheckRefresh is the hook to request a refresh of the routecheck Report
|
||||
// for this LocalBackend.
|
||||
// It is used by serveSuggestExitNode to probe for reachable exit nodes
|
||||
// before it makes a suggestion.
|
||||
var HookRouteCheckRefresh feature.Hook[func(*ipnlocal.LocalBackend, context.Context, time.Duration) error]
|
||||
|
||||
// serveSuggestExitNode serves a POST endpoint for returning a suggested exit node.
|
||||
// If the probe query parameter is true,
|
||||
// then a new routecheck report will be probed
|
||||
// so that the suggested exit node isn’t based on stale reachability data.
|
||||
// Combined with probe=true:
|
||||
// if the timeout query parameter is 0, any probes will immediately timeout;
|
||||
// if the timeout is positive, probes will take that duration before timing out;
|
||||
// if the timeout is negative, probes will use the default routecheck timeout.
|
||||
func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.GET {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
|
||||
switch r.Method {
|
||||
case httpm.GET:
|
||||
// The GET method is inappropriate because it isn’t cacheable.
|
||||
// However, we retain it for backwards compatibility.
|
||||
//
|
||||
// The original implementation used to only allow GET requests,
|
||||
// but since this endpoint powers the `tailscale exit-node suggest` command
|
||||
// whose results can change based on the shape of the network,
|
||||
// it was never a proper GET method in the first place.
|
||||
//
|
||||
// Now that the suggestions are backed by routecheck
|
||||
// and we provide the user the ability to trigger a probe,
|
||||
// this is obviously a POST method because it has side-effects.
|
||||
// However, we don’t want to break existing clients,
|
||||
// so we silently support the old GET method
|
||||
// without the extra query parameters.
|
||||
//
|
||||
// This is also why the default case returns a "want POST" error
|
||||
// and not an "only POST allowed" like the other endpoints.
|
||||
// We still accept GET requests but we don’t want them.
|
||||
case httpm.POST:
|
||||
if !def.Bool(r.FormValue("probe"), false) {
|
||||
break
|
||||
}
|
||||
timeout := def.Duration(r.FormValue("timeout"), -1)
|
||||
|
||||
// Force routecheck to probe for a new report
|
||||
// and use it to suggest an exit node.
|
||||
routecheckRefresh := HookRouteCheckRefresh.GetOrNil()
|
||||
if routecheckRefresh == nil {
|
||||
break
|
||||
}
|
||||
if err := routecheckRefresh(h.b, r.Context(), timeout); err != nil {
|
||||
WriteErrorJSON(w, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
// Discourage the GET method:
|
||||
http.Error(w, "want POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.b.SuggestExitNode()
|
||||
if err != nil {
|
||||
WriteErrorJSON(w, err)
|
||||
@@ -1735,6 +1915,20 @@ func (h *Handler) serveShutdown(w http.ResponseWriter, r *http.Request) {
|
||||
eventbus.Publish[Shutdown](ec).Publish(Shutdown{})
|
||||
}
|
||||
|
||||
func (h *Handler) serveServices(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != httpm.GET {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMapNoPeers()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(nm.Services())
|
||||
}
|
||||
|
||||
func (h *Handler) serveGetAppcRouteInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasAppConnectors {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_drive
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !android && !js && !ts_omit_debug
|
||||
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_serve
|
||||
@@ -53,7 +53,8 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// require a local admin when setting a path handler
|
||||
// require a local admin when setting a path handler or serving a Unix
|
||||
// domain socket
|
||||
// TODO: roll-up this Windows-specific check into either PermitWrite
|
||||
// or a global admin escalation check.
|
||||
if err := authorizeServeConfigForGOOSAndUserContext(runtime.GOOS, configIn, h); err != nil {
|
||||
@@ -89,7 +90,7 @@ func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeC
|
||||
if goos == "darwin" && version.IsSandboxedMacOS() {
|
||||
return nil
|
||||
}
|
||||
if !configIn.HasPathHandler() {
|
||||
if !configIn.HasPathHandler() && !configIn.IsServingUnixAny() {
|
||||
return nil
|
||||
}
|
||||
if h.Actor.IsLocalAdmin(h.b.OperatorUserID()) {
|
||||
@@ -97,9 +98,9 @@ func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeC
|
||||
}
|
||||
switch goos {
|
||||
case "windows":
|
||||
return errors.New("must be a Windows local admin to serve a path")
|
||||
return errors.New("must be a Windows local admin to serve a path or Unix socket")
|
||||
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")
|
||||
return errors.New("must be root, or be an operator and able to run 'sudo tailscale' to serve a path or Unix socket")
|
||||
default:
|
||||
// We filter goos at the start of the func, this default case
|
||||
// should never happen.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
+23
-23
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_tailnetlock
|
||||
@@ -43,7 +43,7 @@ func (h *Handler) serveTKAStatus(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||
j, err := json.MarshalIndent(h.b.TailnetLockStatus(), "", "\t")
|
||||
if err != nil {
|
||||
http.Error(w, "JSON encoding error", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -72,7 +72,7 @@ func (h *Handler) serveTKASign(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockSign(req.NodeKey, req.RotationPublic); err != nil {
|
||||
if err := h.b.TailnetLockSign(req.NodeKey, req.RotationPublic); err != nil {
|
||||
http.Error(w, "signing failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -101,17 +101,17 @@ func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !h.b.NetworkLockAllowed() {
|
||||
if !h.b.TailnetLockAllowed() {
|
||||
http.Error(w, "Tailnet Lock is not supported on your pricing plan", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockInit(req.Keys, req.DisablementValues, req.SupportDisablement); err != nil {
|
||||
if err := h.b.TailnetLockInit(req.Keys, req.DisablementValues, req.SupportDisablement); err != nil {
|
||||
http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||
j, err := json.MarshalIndent(h.b.TailnetLockStatus(), "", "\t")
|
||||
if err != nil {
|
||||
http.Error(w, "JSON encoding error", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -122,7 +122,7 @@ func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *Handler) serveTKAModify(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||
http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
@@ -140,8 +140,8 @@ func (h *Handler) serveTKAModify(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockModify(req.AddKeys, req.RemoveKeys); err != nil {
|
||||
http.Error(w, "network-lock modify failed: "+err.Error(), http.StatusInternalServerError)
|
||||
if err := h.b.TailnetLockModify(req.AddKeys, req.RemoveKeys); err != nil {
|
||||
http.Error(w, "tailnet-lock modify failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
@@ -149,7 +149,7 @@ func (h *Handler) serveTKAModify(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *Handler) serveTKAWrapPreauthKey(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||
http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
@@ -172,7 +172,7 @@ func (h *Handler) serveTKAWrapPreauthKey(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
wrappedKey, err := h.b.NetworkLockWrapPreauthKey(req.TSKey, priv)
|
||||
wrappedKey, err := h.b.TailnetLockWrapPreauthKey(req.TSKey, priv)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -200,7 +200,7 @@ func (h *Handler) serveTKAVerifySigningDeeplink(w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
|
||||
res := h.b.NetworkLockVerifySigningDeeplink(req.URL)
|
||||
res := h.b.TailnetLockVerifySigningDeeplink(req.URL)
|
||||
j, err := json.MarshalIndent(res, "", "\t")
|
||||
if err != nil {
|
||||
http.Error(w, "JSON encoding error", http.StatusInternalServerError)
|
||||
@@ -212,7 +212,7 @@ func (h *Handler) serveTKAVerifySigningDeeplink(w http.ResponseWriter, r *http.R
|
||||
|
||||
func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||
http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
@@ -227,8 +227,8 @@ func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockDisable(secret); err != nil {
|
||||
http.Error(w, "network-lock disable failed: "+err.Error(), http.StatusBadRequest)
|
||||
if err := h.b.TailnetLockDisable(secret); err != nil {
|
||||
http.Error(w, "tailnet-lock disable failed: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -236,7 +236,7 @@ func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *Handler) serveTKALocalDisable(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||
http.Error(w, "tailnet-lock modify access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
@@ -251,8 +251,8 @@ func (h *Handler) serveTKALocalDisable(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockForceLocalDisable(); err != nil {
|
||||
http.Error(w, "network-lock local disable failed: "+err.Error(), http.StatusBadRequest)
|
||||
if err := h.b.TailnetLockForceLocalDisable(); err != nil {
|
||||
http.Error(w, "tailnet-lock local disable failed: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -274,7 +274,7 @@ func (h *Handler) serveTKALog(w http.ResponseWriter, r *http.Request) {
|
||||
limit = int(lm)
|
||||
}
|
||||
|
||||
updates, err := h.b.NetworkLockLog(limit)
|
||||
updates, err := h.b.TailnetLockLog(limit)
|
||||
if err != nil {
|
||||
http.Error(w, "reading log failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -300,7 +300,7 @@ func (h *Handler) serveTKAAffectedSigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
sigs, err := h.b.NetworkLockAffectedSigs(keyID)
|
||||
sigs, err := h.b.TailnetLockAffectedSigs(keyID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -343,7 +343,7 @@ func (h *Handler) serveTKAGenerateRecoveryAUM(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.b.NetworkLockGenerateRecoveryAUM(req.Keys, forkFrom)
|
||||
res, err := h.b.TailnetLockGenerateRecoveryAUM(req.Keys, forkFrom)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -374,7 +374,7 @@ func (h *Handler) serveTKACosignRecoveryAUM(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.b.NetworkLockCosignRecoveryAUM(&aum)
|
||||
res, err := h.b.TailnetLockCosignRecoveryAUM(&aum)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -405,7 +405,7 @@ func (h *Handler) serveTKASubmitRecoveryAUM(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.b.NetworkLockSubmitRecoveryAUM(&aum); err != nil {
|
||||
if err := h.b.TailnetLockSubmitRecoveryAUM(&aum); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user