feat: add fail-closed Hub access profile foundation
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 3s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 11:44:50 +02:00
parent df39fd5f43
commit 3e386147fd
35 changed files with 2009 additions and 195 deletions

131
docs/access-profile-v1.md Normal file
View file

@ -0,0 +1,131 @@
# Hub access profile 1.0.0 — implementation candidate
HUB-WP-0012 source implementation, 2026-09-28. Owner review and live acceptance
remain open. This profile does not grant platform access or enable public exposure.
## Runtime behavior
`HUB_CORE_ACCESS_MODE=auto` enables enforcement whenever `HUB_CORE_ENV` is not
`development` or `test`. `enforce` also enables it locally. `development` is
rejected in other environments. **Do not deploy this candidate as a routine
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
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
fail closed. Clients must use exact paths. Catalog admission does not override
compatibility feature flags or single-writer/read-only gates. Legacy bearer checks
remain in the development lane; production enforcement never falls back to them.
`hub_core/security/routes.json` is the candidate action catalog. It names each
runtime method, path template and handler, with a stable action per handler/method.
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.
## Composition and trust
A host composes `create_app(access_controller=AccessController(...))` with:
- `OIDCVerifier`: an explicitly trusted HTTPS issuer and Hub audience, an owned
`httpx.AsyncClient`, RS256 discovery/JWKS, token and assurance lifetime at most
300 seconds, a 60-second key cache, and unknown-key rotation refresh (rate limited
to once per second). Access tokens require `at+jwt` or the KeyCape `typ: Bearer`
payload marker. ID tokens, local issuers/profiles, weak keys, AAL0 and delegated
agents are refused. Strict integer NumericDates and assurance time are required.
- `FactSource.resolve`: an **owner implementation still owed** that queries
authoritative account, root entitlement, actor tenant and target tenant state.
Its result binds issuer, subject, actor tenant, evidence reference and permitted
producer addresses; its age may not exceed five seconds, including after policy
and audit finish. No token role or incoming tenant/identity header supplies facts.
- `FlexPolicy`: a dedicated HTTPS Hub PDP, exact admitted ServiceAccount principal,
a rotating projected caller-token file and an owner-delivered trusted public-key
set. The token is reread on every call and is separate from the end-user token.
Remote `/v1/keys` responses never establish their own trust. Current/previous keys
can coexist in the mounted trust file; removing a key takes effect next call.
- `Audit.append`: an **owner implementation still owed** that returns only after
durable acceptance. Every allow must reach this sink before handler execution;
a failed sink blocks reads as well as writes. Authorization receipts say
`authorized`, not “operation completed.” Domain commit/outcome audit remains a
separate requirement; this source seam does not claim transactional audit.
- The root's existing immutable issuer and subject, supplied after owner resolution.
No username, email, first-login promotion or generic role establishes root.
Do not implement these missing adapters as a constant allow, in-memory audit sink,
or an assertion copied from token claims. Tests use synthetic owners explicitly.
The current CLI deliberately provides no fixture adapter or production bypass.
A deployment composition factory and dependency health probes remain T02–T04 work.
For this candidate all Hub resources are explicitly **platform-owned**. Other
target tenants are refused. Root requires AAL2/3, active account and tenants,
current root entitlement, and a fresh policy allow for every action. Workloads
receive no root shortcut: a policy grant and current authoritative facts are always
required. Real workload admission remains unproven. Cross-tenant owner administration
and Phase 2 storage/query isolation are not implemented by this classification.
## Policy interoperability and failure handling
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/
policy/audit chain has a ten-second timeout; individual HTTP calls have three seconds.
A separate refusal-audit attempt is bounded at three seconds.
The verifier requires Ed25519 signing, the submitted request digest, matching
request ID and structured actor/action/resource/tenant/context, enforced caller
provenance, policy version/digest, a decision age at most 30 seconds and a valid
allow lifetime. It never caches decisions. Every unimplemented obligation and
non-allow/non-deny effect fails closed; approval requirements cannot be waived.
A malformed/untrusted/unavailable decision returns 503, a verified denial 403,
and invalid authentication 401. Responses are `no-store` and do not expose backend
exceptions. There is no local allow fallback.
Interoperability tests retain flex-auth's real Go-signed fixture, tampered pair,
public test key and original submitted request. Go `encoding/json` emits struct
fields in declaration order and map keys in sorted order. The verifier retains
wire order and HTML escaping for signatures and reproduces the request structs
for `submitted_request_digest`. This is **not** RFC 8785. Duplicate JSON keys and
non-integer decision numbers are outside this candidate profile and fail closed;
a reordered envelope also fails signature verification. Agree broader canonical
encoding with flex-auth before expanding this profile.
## Producers, MCP and embedded hosts
`from_address`, `from_agent` and `author`, when present in a top-level JSON command,
must match the live owner's producer-address set. Native events get a reserved
`payload._hub_access` record containing verified actor/tenant/correlation identity;
client assertions under that key are overwritten. Domain `subject_refs` remain
business data, never proof of origin. Compatibility event implementations and
external publishers still need owner acceptance of equivalent attribution.
Embedded hosts can install `AccessBoundary` and an explicit host route catalog.
Tests prove the common seam on an embedded host; each real host's mounted paths,
features and routers still require inventory and admission. Do not treat the
standalone runtime catalog as admission for every embedded API.
MCP hosts may supply `token_provider`, a callable resolving a **Hub-audience**
credential from the current invocation, with `require_credentials=True`. Credentials
are never retained on the server; requests do not follow redirects when carrying
one. `trailing_slash=False` targets the standalone runtime's native paths. The
standalone production MCP has no human flow/provider yet and fails closed.
Many legacy tools target APIs the standalone Hub does not implement. They remain
unsupported, not silently translated or authorized. Cross-audience delegation,
browser PKCE sessions/logout and real MCP root login remain open.
## Remaining release gates
1. T01: cross-owner contract review, concrete policy vocabulary, effective host and
service-route expansion; the 250-object snapshot is not full route discovery.
2. T02: immutable root binding, registered audience/redirects, PKCE/MFA/recovery,
admitted live facts adapters, attended login/logout and revocation receipts.
3. T03: dedicated Hub policy and fact provenance review; authenticated deployment,
credential/key custody, durable audit implementation and native rotation probes.
4. T04–T05: composed deployable runtime, dependency health probes, all client/extension
migrations, domain outcome audit, legacy lane rollback and full root journeys.
5. T06–T08: every platform/Railiance receipt, separate public-enable approval and
later role/delegation/tenant isolation. No milestone is closed by local fixtures.

View file

@ -0,0 +1,42 @@
# HUB-WP-0012 source implementation evidence — 2026-09-28
This is local source evidence, not attended login, deployed policy, extension or
Railiance acceptance. The workplan remains active with T01–T04 in progress.
Validation:
- `.venv/bin/python -m pytest -q --disable-warnings`: **271 passed** in 50.43s.
One existing FastAPI/Starlette TestClient deprecation warning.
- `tools/build_access_inventory.py --inventory docs/platform-access-inventory.json
--check`: **161 Hub surfaces, 48 platform rows, 250 cluster objects**; checks pass.
- `uv build`: source distribution and wheel build successfully; wheel contains
`hub_core/security/routes.json`.
- `git diff --check`: passes.
Tests cover catalog-wide anonymous denial, exact minimal health exception,
production default denial without owner adapters, immutable root/assurance checks,
live fact freshness and suspension/entitlement withdrawal, denied/unavailable
policy, durable-audit failure, body replay and producer binding, event provenance,
concurrent request contexts, an embedded host, MCP invocation credential isolation
and error redaction, signed JWT claim validation and issuer-key rotation, signed
PDP binding/lifetime/caller/obligation rejection, and projected caller-token rotation.
Interoperability uses flex-auth's original public test fixtures (signed, tampered,
public verification key and original request). Both signature verification and
`submitted_request_digest` reproduction pass against the Go-generated artifacts.
Synthetic current decisions exercise the live-time checks; the historical fixture
is never treated as an active authorization grant.
The [profile candidate](../access-profile-v1.md) states configuration, bounded
lifetimes, serialization limits, extension/host responsibilities, and release gates.
No live credential, grant, policy, workload, public listener or retirement state
was changed. No private production signing key or root subject was invented.
State Hub implementation decision:
`6edd5720-c894-46e3-8130-fd6c09b9f311`.
Remaining requirements include owner review and per-service route expansion;
attended root binding/PKCE/MFA/logout; real account/tenant and durable audit adapters;
a dedicated Hub PDP with authenticated caller and signing-key delivery; deployment
composition and owner health checks; all client/extension/platform receipts;
separately approved exposure; and Phase 2 tenant isolation/delegation.

View file

@ -1,12 +1,16 @@
# Hub Core and extension access through NetKingdom
Status: proposed implementation blueprint, 2026-09-28. Owner: `hub-core`.
Status: reviewed source blueprint; owner/live acceptance pending, 2026-09-28. Owner: `hub-core`.
Execution record: [HUB-WP-0012](../workplans/HUB-WP-0012-netkingdom-platform-root-access.md).
Requested outcome: the person signing in as `platform-root` can access and
administer the whole platform, including Hub Core, its extensions, and Railiance.
Other human users are denied initially. Public exposure follows proven access
enforcement. Fine-grained delegation is a later phase of the same workplan.
The [access profile candidate](access-profile-v1.md) records the subsequent source
implementation and remaining integration gates. The table below is the original
pre-implementation baseline, not a claim that the new enforcement is deployed.
## Findings and evidence boundary
This is a source/configuration review plus read-only runtime observation, not

File diff suppressed because it is too large Load diff

View file

@ -44,8 +44,9 @@ PYTHONDONTWRITEBYTECODE=1 .venv/bin/python tools/build_access_inventory.py \
Omit `--check` to refresh source rows after intentional changes, then review the
diff. Cluster metadata is a dated reviewed input, not silently refreshed by this
command. The checker detects source drift, missing profile/test references and
missing/duplicate cluster-object mappings. It does not test authorization. Actual
allow/deny cases are all marked `not-run`; implementation tasks must supply the
missing/duplicate cluster-object mappings. It does not test authorization. Live
allow/deny cases remain marked `not-run`; the [source candidate](access-profile-v1.md)
adds local enforcement tests. Implementation tasks must still supply the
client fixtures, isolated mutations, independent readbacks and live receipts.
## Findings that affect implementation

View file

@ -144,3 +144,11 @@ it against an isolated runtime with `hub-core conformance --base-url <url>`.
`docs/core-hub-absorption-plan.md` defines the capability-sized `/api/v2`
route and data move order, single-writer dual-run controls, evidence gates,
rollback, and final cutover criteria shared with `CORE-WP-0010`.
## Access enforcement candidate
See [access profile v1](access-profile-v1.md). Production now defaults to the shared
access boundary; the default factory fails closed until real identity/facts/policy/
audit adapters are composed. Only exact GET `/healthz` is public. Do not deploy this
source candidate over the current release before the HUB-WP-0012 admission gates.
Development/test retains the existing unauthenticated/native and legacy-key lanes.