mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-19 23:11:29 +02:00
The per-target middleware chain (slots, dispatcher, mutation gate, metadata merger), body capture, access-log terminal sink, and the proxy wiring that builds + runs chains for synthesized agent-network services.
82 lines
2.2 KiB
Go
82 lines
2.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"regexp"
|
|
)
|
|
|
|
var codeRegex = regexp.MustCompile(`^[a-z][a-z0-9._-]{0,63}$`)
|
|
|
|
// denyResponse is the on-wire shape rendered by RenderDenyResponse.
|
|
// Keeping this as a typed struct ensures we never leak
|
|
// middleware-supplied bytes outside known fields.
|
|
type denyResponse struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message,omitempty"`
|
|
Details map[string]string `json:"details,omitempty"`
|
|
Middleware string `json:"middleware,omitempty"`
|
|
}
|
|
|
|
// RenderDenyResponse writes a structured JSON deny body. Status is
|
|
// clamped to [400, 499] excluding 401 (to avoid conflicts with the
|
|
// proxy's auth flow). All middleware-supplied strings are redacted and
|
|
// truncated. On any validation failure the function writes a generic
|
|
// 403.
|
|
func RenderDenyResponse(w http.ResponseWriter, middlewareID string, reason *DenyReason, defaultStatus int) {
|
|
status := clampDenyStatus(defaultStatus)
|
|
|
|
if reason == nil || !codeRegex.MatchString(reason.Code) {
|
|
writeGenericDeny(w, middlewareID, status)
|
|
return
|
|
}
|
|
|
|
resp := denyResponse{
|
|
Code: reason.Code,
|
|
Message: truncate(Scan(reason.Message), 256),
|
|
Middleware: truncate(Scan(middlewareID), 64),
|
|
}
|
|
if n := len(reason.Details); n > 0 {
|
|
resp.Details = make(map[string]string, min(n, 8))
|
|
for k, v := range reason.Details {
|
|
if len(resp.Details) >= 8 {
|
|
break
|
|
}
|
|
safeKey := truncate(Scan(k), 64)
|
|
if safeKey == "" {
|
|
continue
|
|
}
|
|
resp.Details[safeKey] = truncate(Scan(v), 256)
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
func writeGenericDeny(w http.ResponseWriter, middlewareID string, status int) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(denyResponse{Code: "middleware.error", Middleware: truncate(Scan(middlewareID), 64)})
|
|
}
|
|
|
|
func clampDenyStatus(s int) int {
|
|
if s < 400 || s >= 500 {
|
|
return http.StatusForbidden
|
|
}
|
|
if s == http.StatusUnauthorized {
|
|
return http.StatusForbidden
|
|
}
|
|
return s
|
|
}
|
|
|
|
func truncate(s string, n int) string {
|
|
if len(s) <= n {
|
|
return s
|
|
}
|
|
return s[:n]
|
|
}
|