From e96dd0065202d5e255466eaaa21fc4d531ddc901 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 18 Apr 2022 10:06:41 -0700 Subject: [PATCH] ipn/ipnlocal: add capability for debugging peers over peerapi The default is still users can debug their own nodes. But like cd916b728b did, this adds support for admins to grant additional capabilities with the new tailcfg.CapabilityDebugPeer cap. Updates #4217 Change-Id: Ifce3d9a1f8e8845797970a4f97b393194663d35f Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/peerapi.go | 44 ++++++++++++++++++++--------------------- tailcfg/tailcfg.go | 3 +++ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 37d8c655c..7d65e7d35 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -622,21 +622,19 @@ func (f *incomingFile) PartialFile() ipn.PartialFile { // canPutFile reports whether h can put a file ("Taildrop") to this node. func (h *peerAPIHandler) canPutFile() bool { - if h.isSelf { - return true - } - if h.peerNode == nil { - // Shouldn't happen, but in case. - return false - } - for _, addr := range h.peerNode.Addresses { - if !addr.IsSingleIP() { - continue - } - for _, cap := range h.ps.b.PeerCaps(addr.IP()) { - if cap == tailcfg.CapabilityFileSharingSend { - return true - } + return h.isSelf || h.peerHasCap(tailcfg.CapabilityFileSharingSend) +} + +// canDebug reports whether h can debug this node (goroutines, metrics, +// magicsock internal state, etc). +func (h *peerAPIHandler) canDebug() bool { + return h.isSelf || h.peerHasCap(tailcfg.CapabilityDebugPeer) +} + +func (h *peerAPIHandler) peerHasCap(wantCap string) bool { + for _, hasCap := range h.ps.b.PeerCaps(h.remoteAddr.IP()) { + if hasCap == wantCap { + return true } } return false @@ -763,8 +761,8 @@ func approxSize(n int64) string { } func (h *peerAPIHandler) handleServeGoroutines(w http.ResponseWriter, r *http.Request) { - if !h.isSelf { - http.Error(w, "not owner", http.StatusForbidden) + if !h.canDebug() { + http.Error(w, "denied; no debug access", http.StatusForbidden) return } var buf []byte @@ -779,8 +777,8 @@ func (h *peerAPIHandler) handleServeGoroutines(w http.ResponseWriter, r *http.Re } func (h *peerAPIHandler) handleServeEnv(w http.ResponseWriter, r *http.Request) { - if !h.isSelf { - http.Error(w, "not owner", http.StatusForbidden) + if !h.canDebug() { + http.Error(w, "denied; no debug access", http.StatusForbidden) return } var data struct { @@ -799,8 +797,8 @@ func (h *peerAPIHandler) handleServeEnv(w http.ResponseWriter, r *http.Request) } func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Request) { - if !h.isSelf { - http.Error(w, "not owner", http.StatusForbidden) + if !h.canDebug() { + http.Error(w, "denied; no debug access", http.StatusForbidden) return } eng := h.ps.b.e @@ -814,8 +812,8 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req } func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Request) { - if !h.isSelf { - http.Error(w, "not owner", http.StatusForbidden) + if !h.canDebug() { + http.Error(w, "denied; no debug access", http.StatusForbidden) return } w.Header().Set("Content-Type", "text/plain") diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index c6b556d69..6c32afe43 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1587,6 +1587,9 @@ const ( // CapabilityFileSharingSend grants the ability to receive files from a // node that's owned by a different user. CapabilityFileSharingSend = "https://tailscale.com/cap/file-send" + // CapabilityDebugPeer grants the ability for a peer to read this node's + // goroutines, metrics, magicsock internal state, etc. + CapabilityDebugPeer = "https://tailscale.com/cap/debug-peer" ) // SetDNSRequest is a request to add a DNS record.