Replaces #559, which conflicted. Model: opus-5-5
This commit is contained in:
+76
-11
@@ -1,12 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi"
|
||||
"sneak.berlin/go/webhooker/internal/referer"
|
||||
)
|
||||
|
||||
// sentryRedacted stands in for a withheld field on every event shipped
|
||||
@@ -55,7 +57,7 @@ func sentryClientOptions(dsn, release string) sentry.ClientOptions {
|
||||
//
|
||||
// URL is the third such field. NewRequest builds it as
|
||||
// scheme://host/path (interfaces.go:183), and on the receiver route
|
||||
// that path is /webhook/<uuid> in full — a write capability, not an
|
||||
// that path is /h/<uuid> in full — a write capability, not an
|
||||
// identifier. It is rebuilt here from the chi route pattern, on every
|
||||
// route, keeping the scheme and the host.
|
||||
//
|
||||
@@ -71,10 +73,15 @@ func scrubSentryRequest(
|
||||
|
||||
pattern := sentryRoutePattern(hint)
|
||||
|
||||
// Only transaction events carry a Transaction name, and the SDK
|
||||
// Only transaction events carry a Transaction name. sentryhttp
|
||||
// builds it from the concrete path too (sentryhttp.go:105 via
|
||||
// tracing.go:553). Rewritten on the same terms.
|
||||
if event.Transaction != "" {
|
||||
// tracing.go:553) and marks it SourceURL, so it is rewritten on
|
||||
// the same terms. Only a name nameSentryTransaction set from the
|
||||
// route pattern, which it marks SourceRoute, is kept.
|
||||
namedByRoute := event.TransactionInfo != nil &&
|
||||
event.TransactionInfo.Source == sentry.SourceRoute
|
||||
|
||||
if event.Transaction != "" && !namedByRoute {
|
||||
event.Transaction = sentryTransactionName(
|
||||
event.Transaction, pattern,
|
||||
)
|
||||
@@ -120,7 +127,8 @@ func scrubSentryRequest(
|
||||
// The transaction dispatch has no such request: Span.doFinish calls
|
||||
// hub.CaptureEvent (tracing.go:356), which passes a nil hint that the
|
||||
// client replaces with an empty one (client.go:620-622). The pattern
|
||||
// is therefore always "" there, and the callers fall back.
|
||||
// is therefore always "" there, and the callers fall back. A routed
|
||||
// transaction is named by nameSentryTransaction instead.
|
||||
func sentryRoutePattern(hint *sentry.EventHint) string {
|
||||
if hint == nil || hint.Context == nil {
|
||||
return ""
|
||||
@@ -142,6 +150,52 @@ func sentryRoutePattern(hint *sentry.EventHint) string {
|
||||
return rctx.RoutePattern()
|
||||
}
|
||||
|
||||
// nameSentryTransaction renames the request's Sentry transaction to
|
||||
// "METHOD <route pattern>", such as "POST /h/{uuid}". sentryhttp names
|
||||
// it after the concrete path, which scrubSentryRequest withholds;
|
||||
// since Sentry groups transactions by name, every transaction in the
|
||||
// service would otherwise land in one group per method, such as
|
||||
// "POST /(redacted)".
|
||||
//
|
||||
// It has to run inside the sentryhttp handler, where the transaction
|
||||
// is on the request context. It renames on the way out, once chi has
|
||||
// routed the request, and does so in a defer so that a request whose
|
||||
// handler panics is named too. The context is taken when the defer is
|
||||
// set up, before routing, but the chi routing context on it is a
|
||||
// pointer that chi fills in as it routes.
|
||||
func nameSentryTransaction(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
defer setSentryTransactionName(r.Context(), r.Method)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// setSentryTransactionName does the rename for nameSentryTransaction.
|
||||
// A path that matches nothing at the top level has no pattern; its
|
||||
// name is left as the SDK set it, for scrubSentryRequest to withhold.
|
||||
// Inside a route group, a path that matches no route in the group, or
|
||||
// a request the group's own middleware turns away before routing, has
|
||||
// the group's pattern, such as "/pages/*", and is named after it.
|
||||
func setSentryTransactionName(ctx context.Context, method string) {
|
||||
transaction := sentry.TransactionFromContext(ctx)
|
||||
rctx := chi.RouteContext(ctx)
|
||||
|
||||
if transaction == nil || rctx == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pattern := rctx.RoutePattern()
|
||||
if pattern == "" {
|
||||
return
|
||||
}
|
||||
|
||||
transaction.Name = method + " " + pattern
|
||||
transaction.Source = sentry.SourceRoute
|
||||
}
|
||||
|
||||
// sentryRouteURL rebuilds an event's request URL with the route
|
||||
// pattern in place of the concrete path.
|
||||
//
|
||||
@@ -200,6 +254,7 @@ func sentryTransactionName(name, pattern string) string {
|
||||
// gorilla/csrf accepts in place of the form field — and the shared
|
||||
// secrets senders put on the receiver route (X-Gitlab-Token and the
|
||||
// per-provider signature headers) would otherwise ship verbatim.
|
||||
// The Referer it keeps is cut down to its scheme and host.
|
||||
func keptSentryHeaders(headers map[string]string) map[string]string {
|
||||
if len(headers) == 0 {
|
||||
return headers
|
||||
@@ -208,9 +263,15 @@ func keptSentryHeaders(headers map[string]string) map[string]string {
|
||||
kept := make(map[string]string, len(headers))
|
||||
|
||||
for name, value := range headers {
|
||||
if sentryKeepsHeader(name) {
|
||||
kept[name] = value
|
||||
if !sentryKeepsHeader(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if http.CanonicalHeaderKey(name) == "Referer" {
|
||||
value = referer.SchemeAndHost(value)
|
||||
}
|
||||
|
||||
kept[name] = value
|
||||
}
|
||||
|
||||
return kept
|
||||
@@ -218,10 +279,14 @@ func keptSentryHeaders(headers map[string]string) map[string]string {
|
||||
|
||||
// sentryKeepsHeader reports whether a request header is routing or
|
||||
// content metadata rather than client-chosen payload. Referer is kept
|
||||
// on the reasoning that it is browser-set, that this service emits
|
||||
// only ?page= in its own links, and that Referrer-Policy is set to
|
||||
// strict-origin-when-cross-origin. X-Request-Id ties the event to the
|
||||
// local access log line, which holds the rest of the detail.
|
||||
// only as its scheme and host, through referer.SchemeAndHost, which
|
||||
// the access log uses too: its path can be an entrypoint URL, which
|
||||
// carries the UUID, as when a sender follows a redirect from http://
|
||||
// to https:// or a browser fetches the 404 page's stylesheet from
|
||||
// under /h/. The scheme and host are all gorilla/csrf compares a
|
||||
// Referer by, so a CSRF rejection is still readable from the event.
|
||||
// X-Request-Id ties the event to the local access log line, which
|
||||
// holds the rest of the detail.
|
||||
func sentryKeepsHeader(name string) bool {
|
||||
switch http.CanonicalHeaderKey(name) {
|
||||
case "Accept",
|
||||
|
||||
Reference in New Issue
Block a user