package authz import rego.v1 # ─── Data ───────────────────────────────────────────────────────────────────── # data.endpoint_permissions is loaded from policies/data/scopes.json # Structure: { "METHOD:/path/pattern": ["scope1", ...], ... } # ─── Default ────────────────────────────────────────────────────────────────── default allow := false default reason := "insufficient_scope" # ─── Path pattern normalisation ─────────────────────────────────────────────── # Converts a concrete request path to a pattern key by replacing UUID-like # segments with named placeholders. # # Supported patterns (longest-match wins via iteration): # /api/v1/agents/{uuid}/credentials/{uuid}/rotate # /api/v1/agents/{uuid}/credentials/{uuid} # /api/v1/agents/{uuid}/credentials # /api/v1/agents/{uuid} # /api/v1/agents # /api/v1/token/introspect # /api/v1/token/revoke # /api/v1/audit/{uuid} # /api/v1/audit # Build the lookup key from method + normalised path. lookup_key(method, path) := key if { normalised := normalise_path(path) key := concat(":", [method, normalised]) } # Normalise a concrete path to its pattern form. normalise_path(path) := "/api/v1/agents/:id/credentials/:credId/rotate" if { regex.match(`^/api/v1/agents/[^/]+/credentials/[^/]+/rotate$`, path) } normalise_path(path) := "/api/v1/agents/:id/credentials/:credId" if { regex.match(`^/api/v1/agents/[^/]+/credentials/[^/]+$`, path) } normalise_path(path) := "/api/v1/agents/:id/credentials" if { regex.match(`^/api/v1/agents/[^/]+/credentials$`, path) } normalise_path(path) := "/api/v1/agents/:id" if { regex.match(`^/api/v1/agents/[^/]+$`, path) } normalise_path(path) := "/api/v1/agents" if { path == "/api/v1/agents" } normalise_path(path) := "/api/v1/token/introspect" if { path == "/api/v1/token/introspect" } normalise_path(path) := "/api/v1/token/revoke" if { path == "/api/v1/token/revoke" } normalise_path(path) := "/api/v1/audit/:id" if { regex.match(`^/api/v1/audit/[^/]+$`, path) } normalise_path(path) := "/api/v1/audit" if { path == "/api/v1/audit" } # ─── Core allow rule ────────────────────────────────────────────────────────── # allow = true if every required scope for the endpoint is present in input.scopes. allow if { key := lookup_key(input.method, input.path) required := data.endpoint_permissions[key] every req_scope in required { req_scope in input.scopes } } # reason is populated only on deny. reason := "missing required scope for this endpoint" if { not allow }