key-cape/SCOPE.md
tegwick e9fc8544ab
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
Make the Keycloak transform preserve or name every policy field
keycape-to-keycloak called Transform, which passes no clients, so it wrote a
realm with an empty clients array and nothing said the service-identity contract
had not been migrated. Where clients were supplied, mapClient hardcoded
standardFlowEnabled — silently giving every client_credentials registration the
browser flow — and dropped audience, service subject, tenant, roles, lifetime,
MFA policy, secret reference and handoff URLs. Realm roles and client scopes were
emitted as empty containers.

The defect was not the missing mapping but that a dropped field and an
inapplicable one looked identical in the output.

Add -clients, reading registrations through a new config.Registrations() that
converts without resolving secrets, so migration tooling cannot load material it
has no business holding. Derive flows from the declared grants. Carry the profile
claims as protocol mappers, since Keycloak has no native concept for them, and
lifetime, handoff URLs and the secret reference as attributes — the reference,
never a value. Derive realm roles and client scopes from what is present.

Report what cannot be carried, in UnpreservedReport, kept deliberately separate
from ValidationReport: consistency with the snapshot and completeness of the
migration are different questions and one list cannot answer both. It names the
unmigrated secret, the unenforceable MFA policy, passwords and factor enrolment,
and subject continuity. An incomplete transform emits partial telemetry.

Closes the semantic-preservation half of G03; proof against a live provider is
G04 and stays open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WAsfsfQmDu4vcBhiMcmQp

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 867844@bnt-lap001
Assistant-Session: 3d45905e-0016-4b49-b828-231406881f7b
2026-09-07 13:48:48 +02:00

137 lines
9 KiB
Markdown

# SCOPE
Reviewed 2026-09-05 against source revision `b989de4`.
## Purpose and boundary
KeyCape is Go identity **Tooling** for NetKingdom's lightweight IAM deployment.
It runs its own OIDC-style issuer, uses Authelia for upstream browser
authentication, reads identities from LLDAP, and delegates factor checks to
privacyIDEA. It also provides caller authentication commands, directory snapshot
validation, and migration artifact generators.
It issues identity, role, scope and assurance claims. Resource authorization
belongs to access-engine and consuming services; secret custody and OpenBao
policy/token enforcement belong to their platform owners. The implemented subset
supports NetKingdom integrations, but complete profile conformance and drop-in
Keycloak interchangeability are not established.
## Implemented capabilities
| Surface | What the current source provides |
| --- | --- |
| Issuer HTTP API | Discovery, `/authorize` and its callback/registration-return routes, `/token`, `/jwks`, `/userinfo`, local `/logout`, and `/healthz`. |
| Human authentication | Authorization code with S256 PKCE, exact registered redirects, scope allow-lists, Authelia login, privacyIDEA MFA challenges, client-specific assurance requirements, browser session reuse, step-up/fresh-login handling, and configured registration/enrollment handoffs. |
| Service authentication | Static confidential `client_credentials` clients authenticated with form-encoded `client_secret_basic`; configured subject, tenant, roles, scopes and per-client token lifetime. Secrets are resolved from environment references at startup. |
| Tokens and identity | Locally signed RS256 JWTs; configurable access-token resource audience while ID tokens retain the client audience; human/service principal types, tenant, groups, roles, scope and assurance claims. UserInfo resolves canonical directory subjects and filters profile/email/groups by scope. |
| Caller commands | `keycape login` for public-client browser PKCE login and `keycape service-token` for service exchange. HTTPS discovery/JWKS verification and private JSON token-file delivery outside Git; no token output on stdout. |
| Validation and migration | Canonical snapshot checks; deterministic LLDAP user/group/membership export that records whether it enumerated the whole directory; basic Keycloak realm JSON; LDIF generation for OpenLDAP, 389 Directory Server and AD targets. These generate artifacts rather than execute a complete migration. |
| Diagnostics and packaging | Structured authentication/enforcement/migration events, a process health response, Go build/test/vet targets, a container containing the KeyCape binary, and development/CI scaffolding. |
## Material limits
- Client registrations, service secrets and the signing key are loaded at startup.
Login, authorization and handoff state are process-local. There is no shared
session store, general refresh-token flow, token introspection/revocation API,
or automatic signing-key/client-secret rotation service. Logout clears the
local KeyCape session, not every upstream or downstream session/token.
- The optional tenant-engine `tenant_roles` adapter and handler support exist,
but the server entry point does not configure them. That claim is not an
enabled capability of the stock executable.
- The LLDAP export enumerates the group subtree directly, so groups with no
members are present, and every snapshot carries a `groupEnumeration` field
saying whether that enumeration ran or the membership-derived fallback did
(KEY-WP-0018). A failed enumeration aborts the export rather than writing a
smaller snapshot, and read failures are reported rather than skipped. Users,
groups and memberships are sorted, so an unchanged directory exports
identically. Completeness beyond users, groups and memberships — passwords and
MFA credentials in particular — is still not covered.
- The Keycloak CLI takes a `-clients` config and migrates client registrations:
flows follow the declared grants, and audience, tenant, service subject and
roles are carried as protocol mappers with per-client lifetime, handoff URLs
and the secret *reference* as attributes (KEY-WP-0020). What it cannot carry it
names, in a report separate from the consistency check: secret values, MFA
policy enforcement, passwords and factor enrolment, and subject continuity —
Keycloak mints its own `sub`, so the LLDAP canonical ID survives only as an
attribute. The output is a reviewed artifact, not a proven migration; that
proof needs a live provider swap and is not established.
- Snapshot validation is a limited Go rule set, not full machine-readable schema
enforcement. The canonical YAML model and discovery metadata now match the
runtime client-registration surface and are held there by a conformance check
(KEY-WP-0017); the Go model is the runtime authority and the YAML the reviewed
contract. That is narrower than schema enforcement in general.
- The authorization-code grant now binds the redirect URI,
enforces grant-type eligibility, authenticates confidential clients and
consumes codes atomically, and UserInfo enforces algorithm, issuer and
access-token purpose (KEY-WP-0016). Upstream Authelia ID tokens are verified
before any claim is trusted — signature against the provider's published keys,
advertised issuer, own client ID in the audience, validity window — failing
closed and independently of transport (KEY-WP-0019). KeyCape deliberately does
not check or gate on the upstream transport. Enforcing these bindings
is not complete profile conformance. Relying parties must repeat `redirect_uri`
on the token exchange and present the access token, not the ID token, to
`/userinfo`; see [authorization-code bindings](docs/authorization-code-bindings.md).
See the assessment below.
- Tests cover local handlers, adapters, transformations and CLI protocol behavior.
They do not establish complete replacement against a running Keycloak/full-LDAP
stack. The Scenario B/C shell harnesses are incomplete.
- The server listens on HTTP; HTTPS termination is deployment-owned.
`/healthz` reports process status without probing dependencies. Development
Compose needs configuration/key material absent from the checkout. Production
deployment and custody are external; a source implementation or example client
fragment does not prove live registration or consumer cutover.
- Approval-client provisioning, coordinated Qonto rotation, native consumer
handoffs and notification receipt closure remain open in KEY-WP-0009/0013/0014.
## Deliberate exclusions
General-purpose IAM, dynamic client registration, implicit/password grants,
wildcard redirects, arbitrary identity brokering, resource authorization policy,
OpenBao credential custody, and Keycloak operations are outside this repository's
ownership. Registration/enrollment handoffs do not implement user provisioning
or factor enrollment themselves.
## Entry points and verification
Run the issuer with `keycape --config PATH`; `keycape server` and
`keycape migrate` are not implemented subcommands. Caller commands are
`keycape login` and `keycape service-token`.
Separate binaries under `src/cmd/` are `validator`, `lldap-export`,
`keycape-to-keycloak`, and `lldap-to-ldap`. `make -C src build` places binaries in
root `bin/`; the container packages only `keycape`. Tests live under `src/tests/`
and alongside packages in `src/internal/`.
`make test`, `make lint` (Go vet), `make build`, and `make contract-test` are the
root checks. The capability-contract check requires the sibling NetKingdom
validator. These checks passed for the reviewed revision in the preceding
implementation session; the scope assessment is a source/documentation review.
Use this repo for its bounded issuer, caller JWT acquisition, identity adapters
and migration preparation. Consult [INTENT.md](INTENT.md) for the architectural
target, [native authentication](docs/native-authentication.md) for caller
constraints, and the [scope/intent assessment](history/2026-09-05-011726-scope-intent-assessment.md)
for evidence, priorities and remaining gaps.
## Provided capabilities
```capability
type: security
title: Bounded OIDC and service-token issuance
description: Implements static-client browser PKCE and client-credentials authentication with RS256 identity claims through Authelia, LLDAP and privacyIDEA; complete Keycloak interchangeability remains unproven.
keywords: [oidc, pkce, authentication, iam, sso, mfa, identity, service-token]
```
```capability
type: security
title: Verified caller JWT acquisition
description: Provides native browser login and service-token exchange commands with HTTPS discovery, JWKS verification and private token-file delivery.
keywords: [oidc, pkce, jwt, cli, authentication, jwks]
```
```capability
type: security
title: Directory validation and migration artifact generation
description: Checks canonical snapshots and produces LLDAP exports, basic Keycloak realm JSON and target-specific LDIF; does not perform or prove a complete live migration.
keywords: [migration, identity, lldap, keycloak, ldif, validation]
```