All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
Closes gap G08. /healthz returned a constant without probing anything, the server called ListenAndServe with no signal handling, and the operational limits of in-memory state, startup-loaded keys and local-only logout lived in code comments rather than anywhere an operator would look. /readyz probes LLDAP, Authelia and privacyIDEA; /healthz stays liveness and probes nothing. Keeping them distinct matters: wiring liveness to dependency health means an orchestrator restarts KeyCape when a dependency blinks, and a restart also discards every in-flight login, so the reaction is worse than the condition it reacts to. LLDAP is probed with a bind rather than a dial, since a rotated or revoked service password leaves the port open and every lookup failing -- exactly what readiness should catch and exactly what a dial would miss. The response names the failing check but never the reason: the endpoint is unauthenticated and upstream error text carries hostnames and sometimes credentials-in-URLs. Results are cached for 2s so an unauthenticated endpoint cannot be used to drive unbounded upstream traffic, and probes run concurrently under a 3s bound so a hung dependency makes the endpoint answer rather than hang with it. SIGTERM and SIGINT now drain in-flight requests for 15s, under the 30s read/write timeouts so a stuck request cannot outlive the window before SIGKILL. docs/operations.md states the single-replica topology and why, and three limits easy to get wrong: the constant key-1 key ID makes same-kid rotation a trap for consumers caching JWKS, removing a client does not revoke its issued tokens, and /logout is local only. No throughput figures are given, since nothing here benchmarks KeyCape. Shared storage and refresh tokens stay excluded, as G08 allows. Verified in the running executable: 503 naming all three checks failed while /healthz returned 200, the LLDAP check flipping to ok once started, and 40/40 requests succeeding across a SIGTERM. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
158 lines
11 KiB
Markdown
158 lines
11 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 is wired through a
|
|
`tenantEngine` config block and is off unless `baseURL` is set (KEY-WP-0024).
|
|
When enabled it fails open: an unreachable source omits the claim rather than
|
|
failing issuance, so `tenant_roles` is a cache and must not be trusted for
|
|
privileged decisions.
|
|
- 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. Raw LDAP attribute keys are checked for descriptor validity,
|
|
canonical-mapping shadowing and case-only duplicates (KEY-WP-0021); attribute
|
|
values are not validated against a directory schema, and no allow-list of
|
|
permitted attribute names exists, since that field carries what the canonical
|
|
model does not name. 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 harnesses now run, and `src/tests/conformance` targets a
|
|
live issuer named by `KEYCAPE_TEST_ISSUER`, skipping when it is unset
|
|
(KEY-WP-0022). Run against Keycloak 26.0 it verified discovery, the
|
|
authorization surface, the published keys and a real token exchange — and
|
|
showed that stock Keycloak advertises the excluded `implicit` and `password`
|
|
grants, so it is not a drop-in for this profile. Directory migration is proved
|
|
end to end into a real OpenLDAP, with every migrated membership resolving, and a
|
|
realm built from a live export imports into Keycloak and serves discovery
|
|
(KEY-WP-0023). Relying-party behaviour and MFA against a migrated realm remain
|
|
unexercised, and credential/MFA migration is not supplied at all, so no harness
|
|
can establish it. Subject continuity is explicitly not preserved: the canonical
|
|
ID survives as an attribute while Keycloak mints its own `sub`.
|
|
- The server listens on HTTP; HTTPS termination is deployment-owned.
|
|
`/healthz` is liveness and probes nothing; `/readyz` probes LLDAP, Authelia and
|
|
privacyIDEA and gates traffic. `SIGTERM` drains in-flight requests for 15s.
|
|
The supported topology is a single replica, since login and authorization state
|
|
is process-local — see [operations](docs/operations.md) for that and for the
|
|
key-rotation, client-removal and logout limits. 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]
|
|
```
|