feat: add OIDC browser sessions with live access enforcement
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
fdea2f5192
commit
a15fe032b0
14 changed files with 807 additions and 31 deletions
|
|
@ -12,7 +12,7 @@ upgrade:** the default production factory has no admitted owner adapters yet and
|
|||
returns 401 for missing credentials and 503 for credential-bearing requests.
|
||||
The current deployed image and its release configuration have not been changed.
|
||||
|
||||
Only exact `GET /healthz` is public, returning `{"status":"ok"}`. The shared
|
||||
Without explicit browser composition, only exact `GET /healthz` is public, returning `{"status":"ok"}`. The shared
|
||||
ASGI boundary protects docs, readiness, native ports, projections, compatibility
|
||||
aliases and subsequently attached routes. Unknown method/route/handler combinations,
|
||||
WebSockets, mounts without admission, and slash redirects without catalog entries
|
||||
|
|
@ -26,6 +26,10 @@ It is packaged in the wheel and tested against actual route construction. Source
|
|||
inventory generation does **not** auto-admit a new route. Duplicate docs handlers
|
||||
remain separately inventoried; the boundary selects the first effective route.
|
||||
These technical action names require flex-auth/owner review before policy delivery.
|
||||
Optional browser composition adds four exact `/auth/*` protocol routes and the
|
||||
`hub.browser.session` action; login initiation is public, callback state is
|
||||
browser-bound, and session access requires current root authority. See the
|
||||
[browser integration contract](owner-access-integration.md#browser-composition-source-candidate).
|
||||
|
||||
## Composition and trust
|
||||
|
||||
|
|
@ -76,7 +80,7 @@ and Phase 2 storage/query isolation are not implemented by this classification.
|
|||
The PDP request carries actor, target tenant, action, concrete resource path,
|
||||
assurance, root entitlement, authoritative evidence reference, and a digest of
|
||||
HTTP method/path/query/body. Client bodies and bearer tokens are not sent to the
|
||||
PDP or audit sink. Body size is bounded at 1 MiB. The controller's identity/facts/
|
||||
PDP or audit sink. Body size is bounded at 1 MiB with a ten-second receive deadline. The controller's identity/facts/
|
||||
policy/audit chain has a ten-second timeout; individual HTTP calls have three seconds.
|
||||
A separate refusal-audit attempt is bounded at three seconds.
|
||||
|
||||
|
|
|
|||
43
docs/evidence/hub-wp-0012-browser-20260928.md
Normal file
43
docs/evidence/hub-wp-0012-browser-20260928.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# HUB-WP-0012 browser source evidence — 2026-09-28
|
||||
|
||||
This is local implementation evidence, not live identity, entitlement, policy,
|
||||
archive-custody or deployment acceptance.
|
||||
|
||||
Implemented explicit browser composition with confidential OIDC code exchange,
|
||||
S256 PKCE, one-time browser-bound state, signed ID-token validation, nonce,
|
||||
authentication freshness, optional access-token hash binding and live root
|
||||
access authorization before session creation. Secure host-only cookies contain
|
||||
opaque IDs; tokens remain in bounded process-local memory. Sessions last no
|
||||
longer than five minutes or either token, and restart invalidates them.
|
||||
Cookie writes require exact Origin and CSRF; every protected request still
|
||||
rechecks identity/facts/policy/audit. Session validity is rechecked after authority
|
||||
awaits. Local logout remains available during owner outages. Browser refusal
|
||||
records exclude authorization codes, tokens and query parameters.
|
||||
|
||||
Two additional regressions are fixed: explicit controllers cannot silently run
|
||||
without enforcement, and slow request bodies time out before authority/handlers.
|
||||
|
||||
Validation:
|
||||
|
||||
- `HUB_CORE_AUDIT_CORE_SOURCE=/home/worsch/audit-core .venv/bin/python -m pytest -q --disable-warnings`:
|
||||
**328 passed**, one existing Starlette/httpx deprecation warning.
|
||||
- After the final correlation-ID adjustment, the browser suite passed again:
|
||||
**22 passed**. It uses actual RSA signatures, mock OIDC discovery/code exchange
|
||||
and synthetic authority/policy/audit owners. It covers replay/browser binding,
|
||||
bad nonce/audience/subject/authorized party/type/time/hash, non-root/non-platform/
|
||||
non-MFA denial, expiry/restart, grant withdrawal, CSRF and mixed credentials,
|
||||
logout during authority awaits, capacity and owner outage denial. A real native
|
||||
message handler accepts a valid session write and refuses a spoofed sender.
|
||||
- Wheel/source distribution build passes; the browser module is packaged.
|
||||
- Inventory drift/coverage check passes: **165 Hub source surfaces**, 48 platform
|
||||
rows and 250 dated cluster objects. Four optional browser protocol routes have
|
||||
their own profile; inventory coverage is not live conformance.
|
||||
- `git diff --check` passes.
|
||||
|
||||
Remaining gates: confidential issuer registration and real MFA behavior, immutable
|
||||
root and authoritative owner-facts integration, Hub policy admission including
|
||||
`hub.browser.session`, durable production audit delivery, proxy logging/rate limits,
|
||||
consumer adoption and complete platform acceptance. The source candidate provides
|
||||
neither distributed sessions, upstream IdP logout, automatic renewal nor operation
|
||||
completion auditing. T01–T04 remain in progress. No public listener or production
|
||||
entitlement changed.
|
||||
|
|
@ -99,3 +99,56 @@ A composed fixture journey verifies a real RSA JWT, fresh facts, an Ed25519
|
|||
policy decision, receiver custody and native Hub write, followed by entitlement
|
||||
withdrawal denial. Real root enrollment/login, service deployment, live key and
|
||||
sender custody, and operational acceptance remain open.
|
||||
|
||||
## Browser composition (source candidate)
|
||||
|
||||
Pass `browser_settings=BrowserSettings(origin="https://hub.example",
|
||||
client_id="hub-browser", client_secret_file=Path("/run/secrets/hub-oidc-client"))`
|
||||
to `create_app`, alongside the enforced controller or authoritative-facts security
|
||||
composition described above. Import `BrowserSettings` from
|
||||
`hub_core.security.browser`. Browser configuration cannot activate a development
|
||||
runtime or a missing controller. Its confidential-client credential is separate
|
||||
from the policy/audit workload credentials and is reread at each code exchange.
|
||||
|
||||
Register the exact HTTPS origin plus `/auth/callback` with the admitted issuer.
|
||||
The source candidate requires authorization code, S256 PKCE, RS256 ID tokens and
|
||||
`client_secret_basic` discovery support. It requests fresh authentication and
|
||||
AAL2; validated access-token assurance and current owner facts decide access.
|
||||
Issuer registration, actual MFA behavior, audience mapping and live owner
|
||||
acceptance remain deployment gates. The implementation follows the
|
||||
[OIDC ID token validation contract](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)
|
||||
and [OAuth security best practices](https://www.rfc-editor.org/rfc/rfc9700.html).
|
||||
|
||||
- `GET /auth/login` initiates login; arbitrary query parameters are refused.
|
||||
- `GET /auth/callback` consumes browser-bound state once, exchanges the code with
|
||||
PKCE, checks signature/issuer/audience/nonce/authentication time/access binding,
|
||||
and requires root authorization and audit custody before issuing a session.
|
||||
- `GET /auth/session` rechecks authority and returns expiry and a CSRF token.
|
||||
- `POST /auth/logout` requires the session, exact `Origin` and `X-Hub-CSRF`, then
|
||||
destroys the local session even during identity/policy/audit outages.
|
||||
|
||||
Sessions contain access tokens only in server memory. Cookies carry random opaque
|
||||
IDs with `Secure`, `HttpOnly`, `SameSite=Lax`, host-only scope and `/` path. Sessions
|
||||
expire within five minutes or either token's expiry, whichever comes first;
|
||||
there is no refresh-token renewal. Process restart/shutdown invalidates sessions.
|
||||
Multiple workers need sticky routing or a separately reviewed shared session
|
||||
store; this candidate does not provide distributed sessions. Pending logins and
|
||||
sessions have bounded capacity. Production ingress must also rate-limit login.
|
||||
|
||||
All protected API requests recheck identity, current facts, signed policy and
|
||||
durable audit. Cookie-authenticated writes additionally require exact `Origin`
|
||||
and `X-Hub-CSRF`; browser clients obtain the latter from `/auth/session`. The
|
||||
bundled Swagger UI does not automatically inject that header. Mixed cookies and
|
||||
bearer credentials are rejected. Session expiry/logout is checked again after
|
||||
authority awaits and before dispatch. Logout cannot cancel already executing
|
||||
requests or log out the upstream identity provider; subsequent login requests
|
||||
fresh authentication. Logout is local invalidation, not a durable audited
|
||||
business mutation. Refused browser flows are recorded without codes/tokens/query
|
||||
parameters; audit failure returns 503. Login initiation does not require authority.
|
||||
|
||||
The app must observe the configured HTTPS origin. Do not trust arbitrary
|
||||
forwarded headers; configure an authenticated trusted proxy separately. Callback
|
||||
query strings are cleared before response-time application access logging, but
|
||||
reverse proxies and tracing collectors must independently suppress callback
|
||||
queries, cookies and authorization headers. No public listener is enabled by
|
||||
these source changes.
|
||||
|
|
|
|||
|
|
@ -118,6 +118,23 @@
|
|||
"audience": "none: minimal process liveness",
|
||||
"test_owner": "hub-core",
|
||||
"enforcement": "No identity required; no sensitive details"
|
||||
},
|
||||
"browser-session": {
|
||||
"actor_tenant": "tenant:platform for root; named workload tenant otherwise",
|
||||
"target_tenant": "resolved server-side from resource; root cross-tenant action explicitly audited",
|
||||
"action_resource_rule": "hub.browser.session for login/session authority; protected API uses existing route action",
|
||||
"cases": [
|
||||
"ROOT",
|
||||
"OTHER",
|
||||
"INVALID",
|
||||
"REVOKE",
|
||||
"OUTAGE",
|
||||
"BYPASS",
|
||||
"CALLER"
|
||||
],
|
||||
"audience": "hub-browser (OIDC ID token); hub-core (access token); deployment registration required",
|
||||
"test_owner": "hub-core",
|
||||
"enforcement": "Explicit BrowserSessions: login is public initiation; callback requires one-time browser-bound state, PKCE and nonce; session requires current authority; logout requires session and CSRF"
|
||||
}
|
||||
},
|
||||
"acceptance_cases": {
|
||||
|
|
@ -399,6 +416,39 @@
|
|||
"conditional": false,
|
||||
"handler": "list_widgets"
|
||||
},
|
||||
{
|
||||
"id": "http:GET:/auth/callback:hub_core.security.browser.browser_flow",
|
||||
"kind": "runtime-http",
|
||||
"method": "GET",
|
||||
"path": "/auth/callback",
|
||||
"profile": "browser-session",
|
||||
"current_gate": "OIDC state/PKCE callback or server-side session; explicit browser composition only",
|
||||
"source": "hub_core.security.browser",
|
||||
"conditional": true,
|
||||
"handler": "browser_flow"
|
||||
},
|
||||
{
|
||||
"id": "http:GET:/auth/login:hub_core.security.browser.browser_flow",
|
||||
"kind": "runtime-http",
|
||||
"method": "GET",
|
||||
"path": "/auth/login",
|
||||
"profile": "browser-session",
|
||||
"current_gate": "OIDC state/PKCE callback or server-side session; explicit browser composition only",
|
||||
"source": "hub_core.security.browser",
|
||||
"conditional": true,
|
||||
"handler": "browser_flow"
|
||||
},
|
||||
{
|
||||
"id": "http:GET:/auth/session:hub_core.security.browser.browser_flow",
|
||||
"kind": "runtime-http",
|
||||
"method": "GET",
|
||||
"path": "/auth/session",
|
||||
"profile": "browser-session",
|
||||
"current_gate": "OIDC state/PKCE callback or server-side session; explicit browser composition only",
|
||||
"source": "hub_core.security.browser",
|
||||
"conditional": true,
|
||||
"handler": "browser_flow"
|
||||
},
|
||||
{
|
||||
"id": "http:GET:/console:hub_core.runtime.compat.console",
|
||||
"kind": "runtime-http",
|
||||
|
|
@ -982,6 +1032,17 @@
|
|||
"conditional": false,
|
||||
"handler": "create_widget"
|
||||
},
|
||||
{
|
||||
"id": "http:POST:/auth/logout:hub_core.security.browser.browser_flow",
|
||||
"kind": "runtime-http",
|
||||
"method": "POST",
|
||||
"path": "/auth/logout",
|
||||
"profile": "browser-session",
|
||||
"current_gate": "OIDC state/PKCE callback or server-side session; explicit browser composition only",
|
||||
"source": "hub_core.security.browser",
|
||||
"conditional": true,
|
||||
"handler": "browser_flow"
|
||||
},
|
||||
{
|
||||
"id": "http:POST:/decision-records:hub_core.runtime.compat.accept_deferred",
|
||||
"kind": "runtime-http",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Platform-root access inventory — 2026-09-28
|
||||
|
||||
This is the coverage baseline for HUB-WP-0012-T01, not an access grant or a
|
||||
passing security test. It enumerates 161 Hub source surfaces (88 runtime route
|
||||
passing security test. It enumerates 165 Hub source surfaces (92 runtime route
|
||||
registrations, 42 embedded router operations, 31 MCP tools), 39 observed cluster
|
||||
namespaces and nine additional extension/native-management boundaries. All 250
|
||||
observed Deployment/StatefulSet/DaemonSet/CronJob/Service/Ingress objects map to
|
||||
|
|
@ -19,7 +19,9 @@ pending owner confirmation; none establishes an effective platform-root grant.
|
|||
|
||||
Hub source revision is recorded in the JSON. Runtime routes are constructed
|
||||
locally without running startup, sending requests or connecting to a database.
|
||||
The optional inbox router is included separately. Compatibility aliases and
|
||||
The optional inbox and browser-session routers are included separately. Browser
|
||||
login initiation and callback are exact protocol entry points; they do not
|
||||
grant access without verified tokens and live root authorization. Compatibility aliases and
|
||||
FastAPI built-in documentation endpoints are included even when absent from
|
||||
OpenAPI; disabled compatibility groups still belong in the coverage contract.
|
||||
Embedded factories are scanned with their default prefixes and include optional
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue