feat: add fail-closed Hub access profile foundation
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
df39fd5f43
commit
3e386147fd
35 changed files with 2009 additions and 195 deletions
131
docs/access-profile-v1.md
Normal file
131
docs/access-profile-v1.md
Normal 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.
|
||||
42
docs/evidence/hub-wp-0012-source-20260928.md
Normal file
42
docs/evidence/hub-wp-0012-source-20260928.md
Normal 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.
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue