INTENT.md pinned standard_version: "0.7" in the frontmatter §11 requires. That conflated two things the standard separates itself: assent "records assent to a BOUNDARY, given at the version named. It is not assent to the current text." flex-auth is Engine/PDP at v0.6, v0.7, v0.8 and after; the role does not change when the text is amended. The field was also decorative — parsed into Declaration.StandardVersion and never validated — so the version was load-bearing only via a test asserting it equalled 0.7. That test is inverted rather than deleted: internal/layer now rejects a version pin in the declaration and requires conformance_record to name a file that exists. Version-scoped state moves to docs/conformance/security-layer-conformance.md, a derived artifact carrying what it derives from and the version derived at, as §11 requires of derived artifacts. SCOPE.md: gap assessment replaces "conforming with one declared gap" with three gaps, each with an owner and a route. G2 is new — flex-auth declares no emission guarantee where §11 requires one of every §4 source of evidence. It is recorded as a gap rather than as conformance because the flattering reading, that audit-core is the source and flex-auth merely produces, has been asserted by nobody but flex-auth. Also corrects the stance register from two rows to five. Fixing one line meant reading what the declaration asserts, and a boundary is only half held here. docs/conformance/boundaries-review.md checks the other halves across twelve counterparts and finds four security-relevant repositories with no layer declaration at all — including key-cape, the identity source whose claims flex-auth consumes as normative input. That boundary is asserted from one side only. Recorded as unstated, never as agreed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 28468@bnt-lap001 Assistant-Session: c76569b2-6056-4dad-aea4-49cd7a018f5d
347 lines
18 KiB
Markdown
347 lines
18 KiB
Markdown
# flex-auth Scope
|
|
|
|
## One-liner
|
|
|
|
flex-auth is a policy-as-code authorization registry and decision engine for
|
|
NetKingdom-aligned systems. Under the NetKingdom Security Layer Model
|
|
(declared 2026-08-29) it is **Engine / PDP** — the estate's only policy decision
|
|
point.
|
|
|
|
## Core Idea
|
|
|
|
flex-auth turns verified identity claims, resource manifests, relationship
|
|
facts, policy packages, and request context into inspectable authorization
|
|
decisions. It is intentionally separate from identity providers and from the
|
|
applications that enforce decisions.
|
|
|
|
The product should start as a useful standalone local authorization system,
|
|
then grow into delegated mode where Topaz, OpenFGA, SpiceDB, OPA, Cedar,
|
|
Keycloak Authorization Services, Entra, Graph, SCIM, LDAP, and similar systems
|
|
can be coordinated behind a stable flex-auth API.
|
|
|
|
## In Scope
|
|
|
|
- Protected-system registration.
|
|
- Resource namespaces, resource hierarchy, and action vocabularies.
|
|
- Subject, group, team, tenant, role, and service-account records needed for
|
|
authorization.
|
|
- Relationship facts and inherited access.
|
|
- Versioned policy packages with metadata, fixtures, validation, tests, and
|
|
activation state.
|
|
- Deterministic check, batch-check, list-allowed, explain, and decision-record
|
|
capabilities.
|
|
- Local decision logs with policy version and provenance.
|
|
- CLI and service entry points for local development and integration tests.
|
|
- Markitect as the first concrete protected-system consumer.
|
|
- Delegated PDP and directory adapter boundaries after the standalone core is
|
|
stable.
|
|
- The **decision-record schema**, published as flex-auth's own contract
|
|
(Security Layer Model §17).
|
|
- The **canonical request digest** as the published replay test for consumers
|
|
(§6.4.2) — `binding.submitted_request_digest`, over the request as sent.
|
|
`request_digest` is over the *enriched* request and is flex-auth's own
|
|
audit-replay identity, not a consumer check (`FLEX-DEC-2026-012`).
|
|
- **Decision lifetimes** on every allow, and **revocation visibility deadlines
|
|
stated per input class** (§9.7).
|
|
- **Registry-snapshot provenance**, so a decision that turned on registry
|
|
content is replayable from its own record (§6.1, §9.7.2).
|
|
|
|
## Out of Scope
|
|
|
|
- Acting as an identity provider.
|
|
- Replacing key-cape, NetKingdom SSO, Keycloak, Entra, or other identity
|
|
systems.
|
|
- Owning login, MFA, PKCE, token issuance, token lifecycle, or canonical
|
|
identity profile behavior.
|
|
- Embedding protected-system enforcement logic inside flex-auth.
|
|
- Making Markitect-specific assumptions part of the generic authorization
|
|
model.
|
|
- Forcing a single PDP backend or directory provider.
|
|
- Hiding policy behavior behind opaque administrative toggles.
|
|
- Expressing fail-open. When flex-auth is unreachable there is no evaluator in
|
|
the path; the residue is the consumer's declared stance (§9.3).
|
|
- Owning enforcement. A PEP's obligations under §6.4 are the consumer's,
|
|
including its published stance map.
|
|
- Owning the approval object, its storage, or its lifecycle — that is
|
|
`approval-engine`'s (§9.4). flex-auth validates approvals as input claims and
|
|
never mutates them.
|
|
- Owning evidence of authentication or assurance. flex-auth consumes assurance
|
|
claims and never redefines them; it owns evidence of the decision
|
|
(`FLEX-DEC-2026-002`, recorded as declined in §13).
|
|
- Authoring security doctrine. gate-house owns invariants, authority ceilings,
|
|
operating modes, and the authority context flex-auth consumes as claims.
|
|
|
|
## Relevant When
|
|
|
|
- A protected system needs resource-specific authorization decisions.
|
|
- A team needs policy-as-code packages that can be reviewed, tested, versioned,
|
|
and explained.
|
|
- Authorization must combine identity claims, groups, roles, tenants,
|
|
relationships, context, and resource metadata.
|
|
- A local development workflow needs deterministic authorization behavior
|
|
before enterprise PDP integrations exist.
|
|
- Markitect resources need central authorization decisions and durable audit
|
|
records.
|
|
- A delegated backend needs to be introduced without changing the
|
|
protected-system-facing API.
|
|
|
|
## Not Relevant When
|
|
|
|
- The task is only about authentication, login, token issuance, or MFA.
|
|
- The desired outcome can be handled by coarse identity-provider roles alone.
|
|
- The protected system only needs local development labels and no central
|
|
policy administration.
|
|
- A team is choosing identity-provider configuration rather than
|
|
resource-specific authorization.
|
|
|
|
## Current State
|
|
|
|
The standalone core is implemented and **all seven baseline workplans
|
|
(`FLEX-WP-0001` through `FLEX-WP-0007`) are complete.** The repository carries
|
|
the intent baseline, authorization landscape research, ADR set, and a working
|
|
Go service (`cmd/flex-auth`) with `validate`, `load-registry`, `serve`, and
|
|
`POST /v1/check` plus registry, policy, decision, audit, Markitect, and
|
|
delegated-adapter internals. The standalone policy-as-code core (`FLEX-WP-0002`),
|
|
Markitect consumer integration (`FLEX-WP-0003`, manifest ingest, decisions, and
|
|
fixtures), and the delegated PDP/directory adapter shapes (`FLEX-WP-0004`,
|
|
Topaz/OpenFGA/OPA/Cedar/Keycloak/Entra tradeoffs documented with at least one
|
|
controlled adapter shape) all landed in May 2026.
|
|
|
|
Standalone decisions include a structured `binding` containing the normalized
|
|
subject, action, resource, context, and a full request digest. This makes the
|
|
execute-time decision contract inspectable without parsing reason or diagnostic
|
|
prose. Durable human approval lifecycle, supersession, and authenticated
|
|
multi-approver evidence are `approval-engine` responsibilities; the proposed
|
|
cross-system object is documented in
|
|
`schemas/action_authorization.schema.json` and is not yet a deployed
|
|
`approval-engine` endpoint.
|
|
|
|
**secrets-engine is a shipped consumer as of 2026-09-06** (`FLEX-WP-0021`):
|
|
`secrets-engine.catalog-lane.lifecycle` v2 over `resource.type:
|
|
secret-catalog-lane`, twelve actions delivered by secrets-engine rather than
|
|
inferred, `destroy` gated on a dual-control approval claim, and a dedicated
|
|
`flex-auth-secrets-engine` pin at
|
|
`http://flex-auth-secrets-engine.flex-auth.svc.cluster.local.:8080` —
|
|
trailing dot required, in-cluster only — with `callerAuth.mode: warn`. Adoption is not complete: secrets-engine is a CLI
|
|
rather than a workload, and the pin's default-deny ingress admits a pod, so an
|
|
operator-run access path is still undecided.
|
|
|
|
v1 of that package **had no tenant rule and allowed a foreign tenant**; v2
|
|
supersedes rather than amends it, because a fail-open correction must be visible
|
|
to a consumer as a version change (`FLEX-DEC-2026-008`). The sweep that finding
|
|
prompted shows `tenant-engine` unscoped on tenant as well, carried by
|
|
`FLEX-WP-0022`.
|
|
|
|
The **first shipped protected-system consumer is ops-warden**: its opt-in
|
|
pre-sign gate calls `POST /v1/check` for `resource.type: ssh-certificate`,
|
|
`action: sign` decisions (`examples/ops-warden/`, policy package, allow/deny
|
|
fixtures, and tests). `FLEX-WP-0006` published that gate and `FLEX-WP-0007`
|
|
deployed flex-auth as a reachable production runtime for it. The joint
|
|
OpenBao-backed smoke is verified (2026-06-29: vault-backed allow recorded
|
|
`decision:032b096c433ad80c`; TTL-over-max denied `ttl_out_of_bounds` by
|
|
flex-auth before OpenBao). `FLEX-WP-0016` then shipped an independently rollable
|
|
in-cluster ops-warden pin and moved it to `callerAuth.mode: enforce` once
|
|
ops-warden adopted a calling identity (`WARDEN-WP-0031`): anonymous `/v1/check`
|
|
is 401, a token bound to another protected system is 403.
|
|
|
|
Production `policy.enabled` is **deliberately off, and is not coming back as a
|
|
boolean.** ops-warden's `ADR-0009` (2026-08-22, superseding `ADR-0006`) settles it:
|
|
`policy.enabled` is one repo-wide flag, and with `fail_closed: true` it makes
|
|
flex-auth a hard dependency of every `warden sign` — including the SSH
|
|
certificates the ops-bridge tunnels depend on, one of which carries the policy
|
|
call itself. `ADR-0009` retires `policy.enabled` and the global `policy.fail_closed`
|
|
outright and replaces them with a total per-zone stance map in the consumer PEP
|
|
(`ops-warden/pep-stance.yaml`): open for `z0`-`z2` and `unknown`, closed for
|
|
`z3-critical`. That map is the reference shape for the Security Layer Model's
|
|
§6.4 obligation 3. It is no longer the only published one: `secrets-engine`
|
|
published `pep-stance.yaml` (total over catalog stage plus `unknown`, runtime-read
|
|
and test-pinned), so §13.1's register grew past the single row the standard
|
|
recorded as itself the finding. It now carries **five** — `ops-warden`,
|
|
`user-engine`, `tenant-engine`, `secrets-engine`, and `ops-mason` with no map at
|
|
all — two of them marked non-conformant. flex-auth reviewed the pair in
|
|
`docs/stance-register-review.md` — the first exercise of the aggregate-divergence
|
|
capability it claimed on 2026-08-29 — and found that the two maps take opposite
|
|
stances on `unknown` (`fail_open` vs `fail_closed`) and scope on different axes
|
|
(security-zone vs catalog-stage), which makes the rows conformant but not
|
|
comparable.
|
|
|
|
flex-auth reviewed the zone model as the PDP on 2026-08-19 and holds one
|
|
boundary: zone **membership** compiles into
|
|
the registry snapshot flex-auth already consumes (no schema change needed), while
|
|
per-zone **stance** belongs in the versioned policy package, because registry
|
|
content is absent from decision provenance. **Fail-open is not expressible by a
|
|
PDP at all** and stays a consumer-side setting. The enforcing pins stay
|
|
enforcing regardless — an enforcing pin with no enforcing consumer still turns
|
|
the anonymous path into a 401 rather than a decision.
|
|
|
|
**Layer position (declared 2026-08-29).** flex-auth is **Engine / PDP** under
|
|
the NetKingdom Security Layer Model, declared machine-readably in `INTENT.md`
|
|
frontmatter per §11. That declaration deliberately carries **no standard
|
|
version**: a layer is a boundary and does not change when the text is amended,
|
|
which is the distinction the standard itself draws when it says assent is *"to a
|
|
BOUNDARY, given at the version named. It is not assent to the current text."* The
|
|
absence is enforced by test in `internal/layer`, and raised to `gate-house` as
|
|
`FLEX-WP-0030` B4 in case §11 should say so generally.
|
|
|
|
Version-scoped state therefore lives in
|
|
`docs/conformance/security-layer-conformance.md` — a derived artifact that names
|
|
what it derives from and the version it was derived at, as §11 requires. It
|
|
carries the assent history (v0.4 `FLEX-DEC-2026-002`, v0.6 `FLEX-DEC-2026-003`,
|
|
v0.8 `FLEX-DEC-2026-011`) and the gap table below.
|
|
|
|
Findings of flex-auth's adopted into the standard: §9.3's two-owner split for
|
|
degraded mode, §6.4.2 scoped to the decision's own binding with the canonical
|
|
request digest as its mechanical test and negative caching ruled explicitly,
|
|
§9.7.2 split by role, §17 moving the decision-record schema to flex-auth, and
|
|
four more in the v0.8 round (`FLEX-DEC-2026-011`), of which F1 — that a decision
|
|
must be *attributable* to flex-auth and that a digest comparison does not
|
|
discharge it — was the finding of the round.
|
|
|
|
Conformance state is **conforming with three declared gaps**, each with an owner
|
|
and a route rather than a sentence:
|
|
|
|
| # | Gap | Owner | Route |
|
|
| --- | --- | --- | --- |
|
|
| G1 | Registry-snapshot digest absent from decision provenance (§9.7.2 conformance prerequisite) | `flex-auth` | `FLEX-WP-0019` |
|
|
| G2 | No emission guarantee declared, where §11 requires one of every §4 source of evidence | `gate-house` to rule, then `flex-auth` | `FLEX-WP-0030` B3 |
|
|
| G3 | Published stance-register review stale — written at two register rows, §13.1 now carries five | `flex-auth` | `FLEX-WP-0029` |
|
|
|
|
G2 is new and is recorded as a gap rather than as conformance on purpose: whether
|
|
flex-auth is a §4 *source of evidence* or only the producer of an artifact
|
|
`audit-core` sources has been asserted by nobody but flex-auth, and §11 says a
|
|
source declaring no emission guarantee is not conforming. The conservative entry
|
|
is the honest one until the boundary is ruled.
|
|
|
|
**Boundaries review (2026-09-21).** `FLEX-WP-0030` reviews flex-auth's boundary
|
|
against every security-relevant counterpart and raises five items: inconsistent
|
|
`layer:` casing across the estate against §11's mechanical-checkability claim
|
|
(B1); four security-relevant repositories with no layer declaration at all —
|
|
`gate-house`, `key-cape`, `ops-mason`, `net-kingdom` — of which `key-cape` is the
|
|
identity source whose claims flex-auth consumes as normative input, making that
|
|
boundary asserted from one side only (B2); G2 above (B3); B4; and canon naming
|
|
`access-engine` while the repository still answers to `flex-auth` (B5).
|
|
|
|
State Hub integration is present through:
|
|
|
|
- `.custodian-brief.md`
|
|
- `state_hub_workstream_id` fields in workplans
|
|
- `state_hub_task_id` fields in task blocks
|
|
- active State Hub dispatch for the `flex-auth` repo
|
|
|
|
## How It Fits
|
|
|
|
flex-auth sits between identity and protected systems:
|
|
|
|
```text
|
|
key-cape / NetKingdom SSO
|
|
-> verified identity claims
|
|
-> flex-auth authorization registry and policy decisions
|
|
-> protected systems such as Markitect
|
|
```
|
|
|
|
Identity systems establish who the caller is and provide coarse claims.
|
|
flex-auth decides what that caller can do to a specific resource under a
|
|
specific context. Protected systems enforce the decision and keep their own
|
|
local diagnostics.
|
|
|
|
## Terminology
|
|
|
|
- **Subject:** Human, service account, group, team, tenant, role, or emergency
|
|
principal.
|
|
- **Resource:** Protected object registered by a system.
|
|
- **Namespace:** Resource type and ownership boundary.
|
|
- **Action:** Operation requested against a resource.
|
|
- **Context:** Request, environment, assurance, workflow, or runtime
|
|
attributes.
|
|
- **Policy package:** Versioned policy-as-code bundle with metadata and tests.
|
|
- **Relationship fact:** Subject-resource or resource-resource relation.
|
|
- **Decision:** Allow, deny, redact, audit-only, or not-applicable outcome with
|
|
reason and provenance.
|
|
- **PDP:** Policy decision point, either flex-auth standalone logic or a
|
|
delegated backend.
|
|
- **PEP:** Policy enforcement point in the protected system.
|
|
|
|
## Related / Overlapping
|
|
|
|
- key-cape / NetKingdom SSO: identity source and coarse claims provider;
|
|
flex-auth consumes the **NetKingdom IAM Profile**
|
|
(`~/net-kingdom/canon/standards/iam-profile_v0.2.md`).
|
|
- ops-warden: first **shipped** protected-system consumer. Its opt-in
|
|
pre-sign gate calls flex-auth for `ssh-certificate` / `sign` decisions
|
|
before issuing a short-lived SSH certificate (`FLEX-WP-0006`,
|
|
`FLEX-WP-0007`). ops-warden owns the SSH CA, OpenBao signing, and actor
|
|
inventory; flex-auth owns the policy decision. ops-warden's routing
|
|
charter names flex-auth as the owner of every "may I perform action X?"
|
|
question.
|
|
- Markitect: first **knowledge-pipeline** consumer. Integration is complete on
|
|
the flex-auth side (`FLEX-WP-0003` — resource-manifest ingest, Markitect-
|
|
compatible decisions, and fixtures); a live Markitect runtime calling the gate
|
|
in production is the next consumer milestone after ops-warden.
|
|
- Topaz: aligned evaluator. Per ADR-003 the standalone core is shaped
|
|
to match Topaz's Rego + directory model from day one; the Topaz
|
|
adapter in `FLEX-WP-0004` is therefore a small step rather than a
|
|
conversion.
|
|
- OpenFGA and SpiceDB: candidate relationship authorization backends.
|
|
- OPA and Cedar: candidate rule and typed-policy engines.
|
|
- Keycloak Authorization Services: adapter path for Keycloak-centric
|
|
deployments. Default architecture is "Keycloak as SSO only,
|
|
flex-auth owns authorization"; Keycloak AuthZ is one optional
|
|
delegated PDP.
|
|
- Entra, Graph, SCIM, LDAP, and Keycloak APIs: directory and group resolver
|
|
sources.
|
|
|
|
## Disjoint From
|
|
|
|
- **ops-warden** is a flex-auth *consumer*, not an overlap (see Related /
|
|
Overlapping). The two remain disjoint on **identity surface**: ops-warden
|
|
issues SSH certificates for ops actors (`adm`/`agt`/`atm`) and is not a
|
|
resource-policy engine; flex-auth decides whether a given sign request is
|
|
allowed and never issues certificates. The once-hypothetical flow of
|
|
surfacing an `agt` actor as a flex-auth subject is now realized through
|
|
the signing policy gate.
|
|
- **ops-bridge** owns SSH reverse-tunnel connectivity and explicitly
|
|
disclaims being a credential authority or policy engine. No overlap.
|
|
|
|
## Provided Capabilities
|
|
|
|
```capability
|
|
type: orientation
|
|
title: Agent orientation
|
|
status: current
|
|
description: Repo intent, boundaries, research, workplan map, and State Hub agent brief are available for new coding agents.
|
|
```
|
|
|
|
```capability
|
|
type: workplan
|
|
title: Standalone policy core plan
|
|
status: current
|
|
description: Workplan for schemas, local registry, policy packages, check APIs, list/explain, decision log, CLI/service skeleton, tests, and examples.
|
|
```
|
|
|
|
```capability
|
|
type: workplan
|
|
title: Markitect consumer integration plan
|
|
status: current
|
|
description: Workplan for Markitect resource namespace, manifest import, action vocabulary, check fixtures, contract tests, and integration flow.
|
|
```
|
|
|
|
```capability
|
|
type: workplan
|
|
title: Delegated adapter plan
|
|
status: current
|
|
description: Workplan for Topaz evaluation, relationship PDP adapters, rule PDP adapters, Keycloak AuthZ path, directory resolvers, and operations docs.
|
|
```
|
|
|
|
```capability
|
|
type: contract
|
|
title: Decision-record contract
|
|
status: current
|
|
description: Published decision-record schema, the submitted-request digest as the replay test for consumers, request-enrichment precedence, and decision provenance — flex-auth's own contract under the Security Layer Model section 17.
|
|
```
|
|
|
|
```capability
|
|
type: orientation
|
|
title: Layer declaration
|
|
status: current
|
|
description: Machine-readable Engine/PDP boundary declaration in INTENT.md frontmatter, carrying no standard version by design, with version-stamped conformance state and three declared gaps in docs/conformance/security-layer-conformance.md, per Security Layer Model section 11.
|
|
```
|