mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
o11y carried a second identity system: email/password sign-in, Google,
OIDC and SAML callbacks, self-registration, invites, reset-password
tokens, roles-on-users, SSO auth domains, and a pair of tokenizers that
minted and stored its own sessions. Hanzo IAM answers all of it, once,
at the edge. Two credential stores is one too many, and the one nobody
signs into is the one that rots.
What is left is Identity: WHO a request is, resolved from what the edge
asserted or from a service-account key, never issued here. The routes are
not merely unrouted — the handlers, stores, tables and templates behind
them are gone, and anonymous_test.go names each dead address so that
re-exempting one fails even though no route can come back.
The census moves with it: 366 addresses to 329, 353 typed ops to 319, ten
escape hatches to seven. zipdoc_gen.go loses the 34 descriptions whose
addresses left, because zip keys its doc registry by method and path, so
a description outliving its declaration attaches this module's prose to
whatever a host serves at that address. zipdoc_test.go pins that.
Cherry-picked from delete/o11y-identity 89854a68ae.
171 lines
6.6 KiB
Go
171 lines
6.6 KiB
Go
package o11y_test
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/o11y"
|
|
)
|
|
|
|
// fixedExemptions mirrors anonymous.go's openOps table, spelled a second time
|
|
// ON PURPOSE: the assertion below is "every one of these is a real route", and
|
|
// a list derived from the thing under test could not make it. Adding to openOps
|
|
// without adding here fails TestEveryExemptOpIsNamed.
|
|
var fixedExemptions = []string{
|
|
"GET /v1/o11y/livez",
|
|
"GET /v1/o11y/healthz",
|
|
"GET /v1/o11y/readyz",
|
|
"GET /v1/o11y/version",
|
|
"GET /v1/o11y/health",
|
|
"GET /v1/o11y/global/config",
|
|
"GET /v1/o11y/users/me",
|
|
"GET /v1/o11y/service_accounts/me",
|
|
"PUT /v1/o11y/service_accounts/me",
|
|
}
|
|
|
|
// noCredentialSurfaceIsExempt is the list this file used to be half made of:
|
|
// registration, sign-in, session rotation, sign-out, three SSO callbacks and
|
|
// three password routes. o11y authenticates nobody now, so none of them is a
|
|
// route and none may be an exemption. Naming them keeps the deletion honest —
|
|
// re-adding any one of them to openOps fails here even if the route never comes
|
|
// back, because an exemption without a route is the hole that outlives the door.
|
|
var noCredentialSurfaceIsExempt = []string{
|
|
"POST /v1/o11y/register",
|
|
"POST /v1/o11y/sessions/email_password",
|
|
"GET /v1/o11y/sessions/context",
|
|
"POST /v1/o11y/sessions/rotate",
|
|
"DELETE /v1/o11y/sessions",
|
|
"GET /v1/o11y/complete/google",
|
|
"GET /v1/o11y/complete/oidc",
|
|
"POST /v1/o11y/complete/saml",
|
|
"POST /v1/o11y/reset_password_tokens/verify",
|
|
"POST /v1/o11y/resetPassword",
|
|
"POST /v1/o11y/factor_password/forgot",
|
|
"PUT /v1/o11y/users/me/factor_password",
|
|
}
|
|
|
|
func TestNoCredentialSurfaceIsExemptOrRouted(t *testing.T) {
|
|
have := registered(t, mounted(t))
|
|
for _, route := range noCredentialSurfaceIsExempt {
|
|
method, path, _ := strings.Cut(route, " ")
|
|
if o11y.Anonymous(method, path) {
|
|
t.Errorf("%q is exempt from authentication and must not be: o11y holds no credentials", route)
|
|
}
|
|
if have[route] {
|
|
t.Errorf("%q is still registered — Hanzo IAM owns credentials", route)
|
|
}
|
|
}
|
|
}
|
|
|
|
// THE DRIFT GUARD. The unified binary refused every public o11y op — /version,
|
|
// /health, the three probes, sign-in — because the embedding host's exemption
|
|
// list named /v1/o11y/api/v2/livez and three siblings under an internal /api/
|
|
// namespace this module had stopped rewriting onto. Four names, zero routes,
|
|
// and nothing anywhere said so. This test is what says so: every path the
|
|
// exemption names must be a route Mount registers, at that method.
|
|
//
|
|
// It is the arithmetic routes_test.go runs over the whole surface, applied to
|
|
// the one list allowed to name a subset of it.
|
|
func TestAnonymousNamesOnlyRealRoutes(t *testing.T) {
|
|
have := registered(t, mounted(t))
|
|
for _, route := range fixedExemptions {
|
|
method, path, ok := strings.Cut(route, " ")
|
|
if !ok {
|
|
t.Fatalf("malformed exemption %q", route)
|
|
}
|
|
if !o11y.Anonymous(method, path) {
|
|
t.Errorf("%q is listed here but Anonymous says false — this table and anonymous.go disagree", route)
|
|
}
|
|
if !have[route] {
|
|
t.Errorf("Anonymous exempts %q, which Mount does not register — an exemption matching no route is how the unified binary lost its public ops", route)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEveryExemptOpIsNamed is the converse: no route may be exempt without
|
|
// being declared above, where it can be reviewed and counted. The two
|
|
// parameterized families are exempt by SHAPE rather than by name — the census
|
|
// in routes_test.go counts them and the tables below pin them.
|
|
func TestEveryExemptOpIsNamed(t *testing.T) {
|
|
named := map[string]bool{}
|
|
for _, route := range fixedExemptions {
|
|
named[route] = true
|
|
}
|
|
for route := range registered(t, mounted(t)) {
|
|
method, path, _ := strings.Cut(route, " ")
|
|
switch {
|
|
case !o11y.Anonymous(method, path), named[route]:
|
|
case strings.HasPrefix(path, "/v1/o11y/public/dashboards/"), o11y.IngestWire(method, path):
|
|
default:
|
|
t.Errorf("%s is exempt from the principal gate but is not named in fixedExemptions", route)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A public op that stops being exempt is a 403 on the front page — the outage
|
|
// this whole file exists to prevent recurring.
|
|
func TestPublicOpsAreExempt(t *testing.T) {
|
|
for _, route := range []string{
|
|
"GET /v1/o11y/version",
|
|
"GET /v1/o11y/health",
|
|
"GET /v1/o11y/livez",
|
|
"GET /v1/o11y/healthz",
|
|
"GET /v1/o11y/readyz",
|
|
"GET /v1/o11y/global/config",
|
|
"GET /v1/o11y/users/me",
|
|
"GET /v1/o11y/public/dashboards/d1",
|
|
"GET /v1/o11y/public/dashboards/d1/widgets/0/query_range",
|
|
"POST /v1/event/proj/envelope/",
|
|
"POST /v1/event/proj/store",
|
|
"POST /v1/o11y/api/proj/envelope/",
|
|
"POST /v1/o11y/api/proj/store",
|
|
} {
|
|
method, path, _ := strings.Cut(route, " ")
|
|
if !o11y.Anonymous(method, path) {
|
|
t.Errorf("Anonymous(%s) = false, want true — this op serves callers who hold no principal", route)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The gate is still a gate. Telemetry is a tenant's own data; if these ever
|
|
// answer true the route is open, which is worse than the 403 it replaced.
|
|
func TestTenantOpsStayGated(t *testing.T) {
|
|
for _, route := range []string{
|
|
"GET /v1/o11y/logs",
|
|
"GET /v1/o11y/dashboards",
|
|
"GET /v1/o11y/features",
|
|
"GET /v1/o11y/service_accounts", // the LIST, not /me
|
|
"GET /v1/o11y/users", // the roster, not /users/me
|
|
"GET /v1/o11y/public/dashboards", // the collection is not a share
|
|
"GET /v1/o11y/public/dashboards//", // an empty share id
|
|
"GET /v1/o11y/public/dashboards/d1/widgets/0/query_rangex",
|
|
"GET /v1/event/proj/envelope/", // ingest is a WRITE; a GET is not it
|
|
"POST /v1/o11y/api/v3/issues", // a read API under the DSN prefix
|
|
"POST /v1/o11y/sentinel/issues", // the face is a face, whatever the suffix
|
|
// Ingest moved off the face. The old spelling must buy nothing, or
|
|
// the move left an unauthenticated hole where the route used to be.
|
|
"POST /v1/o11y/sentinel/proj/envelope/",
|
|
"POST /v1/o11y/sentinel/proj/store",
|
|
"POST /v1/o11y/api/proj/envelopes",
|
|
"POST /v1/event", // the product event endpoint is not the Sentry wire
|
|
"GET /v1/o11y/version/", // a route that does not exist is not public
|
|
"GET /V1/O11Y/VERSION", // and the match is exact, never folded
|
|
} {
|
|
method, path, _ := strings.Cut(route, " ")
|
|
if o11y.Anonymous(method, path) {
|
|
t.Errorf("Anonymous(%s) = true, want false — this op must keep its gate", route)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The exemption is a fact about the ROUTE, not about the caller's method: a
|
|
// probe path answered for a write would be a hole with a health check's name.
|
|
func TestExemptionIsPerMethod(t *testing.T) {
|
|
for _, m := range []string{http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch} {
|
|
if o11y.Anonymous(m, "/v1/o11y/version") {
|
|
t.Errorf("Anonymous(%s /v1/o11y/version) = true, want false", m)
|
|
}
|
|
}
|
|
}
|