Files
gateway/auth_middleware_ingest_class_test.go
zeekay 5fd1e59baf SuperAdmin is a person whose home org is admin, on every door the gateway has
The relay gate and the money bits asked authz's PlatformSudo, which granted
platform authority to an admin membership held at any position, so a brand
org's member added to admin got X-User-IsAdmin and the Admin bit here while
cloud and IAM said no. authz v1.10.47 has no PlatformSudo; both transports
now ask Claims.Sudo.

cmd/admin-api asked the owner CLAIM, the org of the application that minted
the token, behind an IAM_ADMIN_ORG knob. A bearer is now asked Sudo, and a
session the server's isSuperAdmin verdict. The knob and the row-level
is_global_admin column, a copy of the predicate nothing read, are gone.

authz v1.10.47 brings zip v1.37.3, whose untyped routes are Raw, and brand
orgs that open only to the people who run them, so the fixtures' plain
member now lives in a customer org.
2026-10-18 21:17:58 -07:00

112 lines
4.0 KiB
Go

// Copyright © 2026 Hanzo AI. MIT License.
//go:build legacy
// +build legacy
// gin-driven: this suite drives the LEGACY Lura edge's transports
// (legacy_transports.go), which exist only under this tag. The policies it
// exercises are framework-free values shared with the zip edge, and
// transport_parity_test.go asserts the two edges answer alike.
package gateway
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
// These tests pin the authed class: every /v1/* validates the IAM-JWT and WRITES
// identity FROM the JWT over a stripped slate — the gateway is the SOLE identity
// source.
// The global ingress strip is load-bearing (several identity headers are written only
// conditionally, so a forged copy would otherwise survive on the authed class).
type backendSaw struct {
reached bool
org string
user string
glob string // X-User-IsGlobalAdmin
owner string // X-User-Owner (home org)
}
func recordingBackend(r *gin.Engine, saw *backendSaw) {
r.Any("/*path", func(c *gin.Context) {
saw.reached = true
saw.org = c.Request.Header.Get("X-Org-Id")
saw.user = c.Request.Header.Get("X-User-Id")
saw.glob = c.Request.Header.Get("X-User-IsGlobalAdmin")
saw.owner = c.Request.Header.Get("X-User-Owner")
c.Status(http.StatusOK)
})
}
func send(r *gin.Engine, method, path, auth string, forged map[string]string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
req.Host = "api.hanzo.ai"
if auth != "" {
req.Header.Set("Authorization", "Bearer "+auth)
}
for k, v := range forged {
req.Header.Set(k, v)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
// Scenarios 1, 2, 6: an authed Sentry READ with no JWT (with or without a forged
// X-Org-Id) is 401 — the read routes to the AUTHED class, never the ingest class.
func TestIngestClass_AuthedReadRequiresJWT(t *testing.T) {
r, _, jwks := setupMiddlewareWithJWKS(t, nil)
defer jwks.Close()
var saw backendSaw
recordingBackend(r, &saw)
if w := send(r, http.MethodGet, "/v1/sentinel/issues?project=019f5339", "", nil); w.Code != http.StatusUnauthorized {
t.Errorf("read with no JWT: got %d, want 401", w.Code)
}
// A forged X-Org-Id must NOT buy a read (the gate rejects for lack of a valid JWT).
if w := send(r, http.MethodGet, "/v1/sentinel/issues", "", map[string]string{"X-Org-Id": "victim-org"}); w.Code != http.StatusUnauthorized {
t.Errorf("read with forged X-Org-Id + no JWT: got %d, want 401", w.Code)
}
if saw.reached {
t.Error("SECURITY: an unauthenticated read reached the backend")
}
}
// Scenario 3 (+ the load-bearing-strip proof): a valid JWT with a forged X-Org-Id
// AND a forged X-User-IsGlobalAdmin — the backend must see the JWT's org (hanzo),
// NOT the forged org, and must NOT see SuperAdmin (the JWT asserts neither, and
// the conditional write does not re-set it, so the strip is what defeats the forge).
func TestIngestClass_AuthedWriteOverwritesForgedIdentity(t *testing.T) {
r, tj, jwks := setupMiddlewareWithJWKS(t, nil)
defer jwks.Close()
var saw backendSaw
recordingBackend(r, &saw)
tok := tj.signToken(t, validClaims("https://hanzo.id", "https://api.hanzo.ai")) // a customer, not a SuperAdmin
w := send(r, http.MethodGet, "/v1/sentinel/issues", tok, map[string]string{
"X-Org-Id": "victim-org",
"X-User-IsGlobalAdmin": "true",
"X-User-Owner": "admin",
})
if w.Code != http.StatusOK {
t.Fatalf("valid-JWT read: got %d, want 200", w.Code)
}
if !saw.reached {
t.Fatal("valid-JWT read did not reach the backend")
}
if saw.org != "acme" {
t.Errorf("SECURITY: backend saw X-Org-Id %q, want the token's home org \"acme\" (forge not overwritten)", saw.org)
}
if saw.owner != "acme" {
t.Errorf("SECURITY: backend saw X-User-Owner %q, want the token's home org \"acme\" (orgs[0]; forged \"admin\" overwritten)", saw.owner)
}
if saw.glob != "" {
t.Errorf("SECURITY: forged legacy X-User-IsGlobalAdmin survived (%q) — never written now (platform sudo = org==admin); the ingress strip must drop a forged copy", saw.glob)
}
}