Document actual capabilities and assess scope against intent
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06e87-e039-7ed2-b85c-20ad37f8a21b
This commit is contained in:
parent
b989de4e90
commit
30fa8570aa
3 changed files with 394 additions and 89 deletions
283
history/2026-09-05-011726-scope-intent-assessment.md
Normal file
283
history/2026-09-05-011726-scope-intent-assessment.md
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# KeyCape scope against intent — 2026-09-05
|
||||
|
||||
Assessment timestamp: **2026-09-05 01:17:26 CEST (+02:00)**.
|
||||
Source baseline: **b989de4e90248ff0782a3e99bc12a6d0c01487a4**.
|
||||
Inputs: [INTENT](../INTENT.md), the previous SCOPE, implementation, tests,
|
||||
build/operational scaffolding, and open workplans. Result: updated
|
||||
[SCOPE](../SCOPE.md). INTENT is unchanged.
|
||||
|
||||
## Assessment
|
||||
|
||||
KeyCape substantially implements its identity-tooling purpose: it is a real
|
||||
issuer with browser and service authentication, identity adapters, MFA policy
|
||||
handling and caller commands. Its ownership boundary is consistent with INTENT:
|
||||
it produces identity claims and delegates resource authorization and custody.
|
||||
|
||||
The stronger maturity claims are not established. “Strict” complete profile
|
||||
conformance, stable identity through replacement, “seamless migration,” and
|
||||
interchangeability without application changes exceed current implementation and
|
||||
proof. The original 23-task workplan's completion is historical delivery evidence,
|
||||
not evidence that every current contract, migration path or operational need is
|
||||
complete. The accurate posture is **an implemented lightweight authentication
|
||||
subset with tested local behavior and significant conformance, migration and
|
||||
operational gaps**.
|
||||
|
||||
## Method and evidence limits
|
||||
|
||||
This is a source and documentation assessment, not a new live security audit or
|
||||
migration rehearsal. The preceding implementation session at this baseline passed
|
||||
`make test`, `make lint`, `make build` and `make contract-test`. No code changed in
|
||||
this assessment and those checks were not rerun solely for wording changes.
|
||||
Links and source claims were checked directly. Passing tests describe the covered
|
||||
behavior; they do not establish the missing properties below.
|
||||
|
||||
Deployment observations in KEY-WP-0013 are earlier, explicitly dated evidence;
|
||||
no fresh rollout, secret read, production token exchange or Keycloak migration
|
||||
was performed here. Native caller behavior was tested locally, not certified as
|
||||
a completed live consumer handoff. Findings marked as static risks need focused
|
||||
regression tests before claiming exploitability or remediation.
|
||||
|
||||
## Alignment with INTENT
|
||||
|
||||
| INTENT commitment | Assessment | Evidence and limit |
|
||||
| --- | --- | --- |
|
||||
| Lightweight authentication Tooling | Substantially implemented | [Server composition](../src/cmd/keycape/main.go), [OIDC handlers](../src/internal/server/oidc/) and [adapters](../src/internal/adapters/). KeyCape signs its own tokens; it is more than a packaged reverse proxy. |
|
||||
| Versioned, implementation-independent contract | Partial | Runtime supports newer service/audience policy than the v0.1 machine model and discovery metadata describe (G02). |
|
||||
| Strong constraints and explicit rejection | Partial | Exact redirects, PKCE, scope checks, client-secret validation and [enforcement middleware](../src/internal/server/errors/enforcement.go) exist. Important validation and code-consumption boundaries remain incomplete (G01). |
|
||||
| Canonical identity normalization | Partial | LDAP identities and group-derived tenant/roles are mapped. Directory portability, complete export and schema enforcement remain limited (G03/G05/G06). |
|
||||
| Complete migration and interchangeable modes | Not demonstrated | Basic transforms and fixture tests exist. Current identity/client policy is not preserved end to end and live replacement harnesses are incomplete (G03/G04). |
|
||||
| Deterministic, testable behavior | Strong local coverage; operational limits | Handler and CLI tests exercise actual local protocol code. Shared state, dependency readiness, complete exports and live backend replacement are not demonstrated (G04/G05/G08). |
|
||||
| Minimal, secure, operationally efficient deployment | Partial | Small Go/container implementation exists; no resource benchmark or production-readiness certification was established. Bootstrap scaffolding and custody lifecycle need work (G08/G09/G10). |
|
||||
| Authentication without resource authorization ownership | Aligned in scope | Claim issuance and local client/MFA rules are authentication policy; resource decisions remain with access-engine/consumers. This does not prove that every estate caller follows the engine-only integration rule. |
|
||||
|
||||
## Gaps and closure criteria
|
||||
|
||||
### G01 — Protocol trust and authorization-code consumption need hardening
|
||||
|
||||
**Priority: high. Kind: implementation gap / static security risk.**
|
||||
|
||||
[TokenHandler](../src/internal/server/oidc/token.go) validates PKCE, client ID and
|
||||
scopes, but the authorization-code path does not authenticate confidential
|
||||
clients or compare the submitted redirect URI with the stored one. Grant-type
|
||||
eligibility is explicitly enforced on the service path, not equivalently on the
|
||||
browser path. [SessionStore](../src/internal/server/oidc/session.go) retrieves a
|
||||
code and deletes it in separate operations after signing; simultaneous requests
|
||||
can reach the same session before deletion. This is not atomic single-use
|
||||
consumption.
|
||||
|
||||
[UserInfo](../src/internal/server/oidc/userinfo.go) verifies an RSA signature and
|
||||
expiry, but its `Issuer` field is unused in verification and the helper does not
|
||||
validate the JOSE header algorithm, audience or token purpose. It cannot claim
|
||||
the same verification contract as the new caller CLI.
|
||||
|
||||
The [Authelia adapter](../src/internal/adapters/authelia/adapter.go) deliberately
|
||||
decodes upstream ID-token claims without signature verification and does not
|
||||
validate their issuer/audience/expiry. Its comment assumes a trusted TLS service
|
||||
boundary; the configuration permits internal HTTP endpoints. This is an explicit
|
||||
trust assumption, not independent provider-token verification.
|
||||
|
||||
**Close when:** the accepted profile defines these bindings and trust boundaries;
|
||||
implementation enforces them; negative tests cover confidential clients,
|
||||
redirect/grant mismatch, issuer/token-purpose mismatch and concurrent code reuse.
|
||||
Validate upstream provider tokens or explicitly establish and test the chosen
|
||||
transport/trust contract. This assessment does not claim a demonstrated attack.
|
||||
|
||||
### G02 — Machine-readable contract and discovery lag the runtime
|
||||
|
||||
**Priority: high. Kind: contract drift.**
|
||||
|
||||
[spec/canonical-model.yaml](../spec/canonical-model.yaml) restricts grants to
|
||||
`authorization_code`, requires redirect URIs for every client, and omits newer
|
||||
service subject, tenant, audience, lifetime and MFA/handoff policy fields present
|
||||
in [domain/model.go](../src/internal/domain/model.go) and
|
||||
[config.go](../src/internal/config/config.go). Both the YAML and Go comments claim
|
||||
to be the source of truth, without a demonstrated generation/conformance link.
|
||||
|
||||
[Discovery](../src/internal/server/oidc/discovery.go) advertises both grants but
|
||||
uses a fixed basic scope/claim list that omits newer core claims and configured
|
||||
resource scopes. It is not a complete inventory of the current profile surface.
|
||||
|
||||
**Close when:** select a canonical version, reconcile schema/runtime/discovery,
|
||||
and add executable compatibility checks for human and service registrations.
|
||||
Distinguish required profile claims from optional discovery metadata rather than
|
||||
assuming every omission has the same protocol impact.
|
||||
|
||||
### G03 — Migration does not preserve the current authentication contract
|
||||
|
||||
**Priority: high. Kind: implementation gap.**
|
||||
|
||||
The [Keycloak CLI](../src/cmd/keycape-to-keycloak/main.go) calls `Transform`, which
|
||||
passes no clients. Only the library's `TransformWithClients` accepts them.
|
||||
The [transformer](../src/internal/migration/tokeycloak/transformer.go) leaves realm
|
||||
roles and client-scope definitions empty, always enables standard flow, and lacks
|
||||
mapping for service-account issuance, resource audiences, tenant/role claims,
|
||||
per-client lifetime, MFA policy and secret references. Its user mapping omits the
|
||||
canonical ID, tenant and roles; retaining the same `sub` is not established.
|
||||
LLDAP uses a DN as the canonical user ID, so directory relocation itself requires
|
||||
an explicit identity continuity strategy. Passwords and MFA credentials are not
|
||||
migrated; absence of MFA data is not proof that no re-enrollment is needed.
|
||||
|
||||
**Close when:** the CLI accepts the required complete snapshot/registrations,
|
||||
transforms preserve or explicitly reject every relevant policy/identity field,
|
||||
and migration proof demonstrates subject continuity, claims, MFA and client
|
||||
behavior. Until then, call these artifact generators rather than full migration.
|
||||
|
||||
### G04 — The replacement test harness does not prove a live provider swap
|
||||
|
||||
**Priority: high. Kind: verification and tooling gap.**
|
||||
|
||||
[Scenario B](../scripts/test-scenario-b.sh) and
|
||||
[Scenario C](../scripts/test-scenario-c.sh) reference absent
|
||||
`docker-compose.scenario-b.yml` / `docker-compose.scenario-c.yml`. They expect
|
||||
`src/bin/`, while [src/Makefile](../src/Makefile) builds into root `bin/`, and invoke
|
||||
a workstation-specific Go path from outside the Go module. Scenario C passes
|
||||
`--base-dn` to a generator whose flag is `--basedn`.
|
||||
|
||||
Both scripts set `KEYCAPE_TEST_ISSUER`, but the
|
||||
[profile suite](../src/tests/profile/profile_test.go) constructs its own
|
||||
`httptest` server and does not consume that variable. The
|
||||
[migration suites](../src/tests/migration/) validate generated structures with
|
||||
fixtures. Thus even repairing the shell prerequisites would not make those
|
||||
profile tests exercise the external Keycloak issuer.
|
||||
|
||||
**Close when:** reproducible stacks and an externally targeted conformance suite
|
||||
exercise actual replacement providers, directory migration, claims and MFA;
|
||||
record unchanged relying-party behavior and explicit migration limitations.
|
||||
|
||||
### G05 — Directory export can omit data without reporting it
|
||||
|
||||
**Priority: medium. Kind: implementation gap.**
|
||||
|
||||
The [exporter](../src/internal/migration/lldapexport/exporter.go) discovers groups
|
||||
through each user's memberships. Empty/unreferenced groups are not enumerated.
|
||||
Group lookup errors are skipped even though a comment says they are recorded in
|
||||
the incompatibility report. The emitted success event and report therefore do
|
||||
not establish a complete export. Iterating the group map also leaves group order
|
||||
unspecified.
|
||||
|
||||
**Close when:** enumerate all groups independently, report or fail on incomplete
|
||||
reads, define ordering, and test empty groups and backend failures. Preserve
|
||||
completeness evidence before claiming deterministic full snapshots.
|
||||
|
||||
### G06 — The validator is narrower than schema enforcement
|
||||
|
||||
**Priority: medium. Kind: implementation/claim gap.**
|
||||
|
||||
[validator.go](../src/internal/validator/validator.go) implements useful structural
|
||||
and semantic checks. However, `checkNoUnknownAttributes` is a placeholder that
|
||||
rejects blank keys rather than enforcing an attribute allow-list; group
|
||||
membership validation checks nonempty member IDs rather than complete referenced
|
||||
identity existence. It does not derive its checks from the YAML schema.
|
||||
|
||||
**Close when:** implement the intended allow-list/reference constraints or narrow
|
||||
the normative contract to the actual checks; add invalid-snapshot cases that
|
||||
prove each stated rule. SCOPE now calls this limited snapshot validation.
|
||||
|
||||
### G07 — Optional tenant-role support is not wired into the server
|
||||
|
||||
**Priority: medium. Kind: integration gap.**
|
||||
|
||||
The [tenant-engine client](../src/internal/adapters/tenantengine/adapter.go) and
|
||||
`TokenHandler.TenantEngine` are implemented and tested, including omission on
|
||||
failure. [main.go](../src/cmd/keycape/main.go) supplies no such client and exposes
|
||||
no configuration for it. The stock server therefore leaves it nil and omits
|
||||
`tenant_roles`.
|
||||
|
||||
**Close when:** wire an explicit opt-in configuration and verify the built
|
||||
executable, or document this as library-only support. It is an optional cache
|
||||
claim, so absence is not itself a resource authorization failure.
|
||||
|
||||
### G08 — Runtime lifecycle and readiness are intentionally minimal
|
||||
|
||||
**Priority: medium. Kind: operational maturity gap.**
|
||||
|
||||
[Authorization state](../src/internal/server/oidc/authorize.go),
|
||||
[code sessions](../src/internal/server/oidc/session.go),
|
||||
[login sessions](../src/internal/server/oidc/login_session.go) and
|
||||
[handoffs](../src/internal/server/oidc/handoff.go) reside in memory. Restart loses
|
||||
in-flight and login state; multi-replica behavior is not supported by a shared
|
||||
store. `/healthz` in [main.go](../src/cmd/keycape/main.go) returns a constant process
|
||||
response without testing dependencies. The server uses `ListenAndServe`; TLS
|
||||
termination is external. The signing key and registrations load
|
||||
at startup; a fixed `key-1` identifier is used by token issuance.
|
||||
|
||||
[Logout](../src/internal/server/oidc/logout.go) clears the local login session;
|
||||
it is not upstream logout or JWT revocation. No general refresh/introspection/
|
||||
revocation or automatic rotation service is exposed. These need explicit
|
||||
operational limits rather than an unqualified “high stability” label.
|
||||
|
||||
**Close when:** document/test the supported deployment topology, readiness and
|
||||
restart behavior, and coordinate key/client lifecycle and consumer refresh.
|
||||
Shared storage or refresh tokens need not be added if the accepted profile
|
||||
explicitly excludes them. Benchmark before asserting resource-efficiency bounds.
|
||||
|
||||
### G09 — Packaging/bootstrap and older CLI credential handling need reconciliation
|
||||
|
||||
**Priority: medium. Kind: operational/tooling gap.**
|
||||
|
||||
[Development Compose](../docker-compose.dev.yml) refers to `config/dev-key.pem`
|
||||
and `config/authelia`, neither supplied in the checkout. It is a scaffold requiring
|
||||
bootstrap material. The [Dockerfile](../Dockerfile) packages only `keycape`, not
|
||||
the migration/validator binaries. Image publication still points at the older
|
||||
registry address in [.gitea/workflows/image.yaml](../.gitea/workflows/image.yaml);
|
||||
that requires reconciliation with the Forgejo image location recorded by the
|
||||
live workplan, not an assumption that every push deployed the current source.
|
||||
|
||||
The older [LLDAP exporter CLI](../src/cmd/lldap-export/main.go) accepts the bind
|
||||
password on argv, and the migration scripts use that path. This falls short of
|
||||
the newer caller CLI's private credential transport posture. Snapshot and realm
|
||||
output are written with mode 0644, so operators must also consider identity-data
|
||||
handling even though credential secrets are not part of those exports.
|
||||
|
||||
**Close when:** supply a reproducible bootstrap procedure, correct executable/
|
||||
artifact locations and release references, and adopt safe credential input and
|
||||
appropriate export permissions. A documented external bootstrap may satisfy
|
||||
scope without storing secrets in this repository.
|
||||
|
||||
### G10 — Source capability is ahead of live custody and consumer adoption
|
||||
|
||||
**Priority: high for rollout; medium for handoff hygiene. Kind: external dependency/proof gap.**
|
||||
|
||||
[KEY-WP-0013](../workplans/KEY-WP-0013-approval-engine-resource-audience.md) records
|
||||
approval clients awaiting custody admission, exact human callback and live proof.
|
||||
Its earlier deployment observation identifies `main-153258b`, not the assessed
|
||||
source revision. The [provisioning packet](../docs/approval-engine-provisioning-request.yaml)
|
||||
is proposed metadata, not executable authorization or a live registration.
|
||||
|
||||
[KEY-WP-0014](../workplans/KEY-WP-0014-native-credential-lane-handoff.md) records
|
||||
native JWT commands as implemented, but coordinated Qonto rotation and consumer
|
||||
handoff remain open. The existing ops-warden login route obtains an **OpenBao
|
||||
token**, so replacing it with issuer-JWT output would change the consumer
|
||||
contract. [KEY-WP-0009](../workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md)
|
||||
was reopened because claimed handoff delivery lacks matching current receipts.
|
||||
|
||||
**Close when:** named custody/platform owners admit and provision exact lanes,
|
||||
register the real callback, deploy and verify the new contracts, reconcile token
|
||||
types at consumer boundaries, and retain handoff receipts. Repo-local source
|
||||
changes cannot alone establish these outcomes.
|
||||
|
||||
## Deliberate exclusions are not defects
|
||||
|
||||
INTENT excludes general-purpose IAM, weakened flows and expanded-mode operations.
|
||||
Dynamic registration, implicit/password grants, wildcard redirects and arbitrary
|
||||
brokering should stay excluded unless the accepted profile changes. Resource
|
||||
policy decisions and secret custody likewise remain with their owners.
|
||||
|
||||
The meaningful gaps are incomplete delivery or proof of the repo's claimed
|
||||
subset, plus unqualified maturity claims. Full Keycloak feature parity, building
|
||||
an authorization engine, or taking over OpenBao is not the proposed remedy.
|
||||
|
||||
## Recommended order
|
||||
|
||||
1. Resolve G01 protocol bindings and G02 canonical contract drift before claiming
|
||||
complete profile conformance or widening rollout.
|
||||
2. Treat G03–G06 as a migration workstream: completeness and semantic preservation
|
||||
first, then actual provider-replacement proof.
|
||||
3. Decide G07 opt-in wiring and document/test the G08 supported topology.
|
||||
4. Reconcile G09 bootstrap/release paths and complete G10 owner admissions and
|
||||
live proof. Do not confuse passing local tests with those handoffs.
|
||||
|
||||
The findings above are an assessment backlog, not completed fixes or newly
|
||||
approved production changes. Existing workplan references are retained where
|
||||
applicable; new engineering work needs scoped implementation plans. This task
|
||||
changes documentation only.
|
||||
Loading…
Add table
Add a link
Reference in a new issue