97 lines
5 KiB
Markdown
97 lines
5 KiB
Markdown
|
|
# KeyCape scope reassessment — 2026-09-09
|
||
|
|
|
||
|
|
Source baseline: **5ae80d3**. Supersedes the
|
||
|
|
[2026-09-05 assessment](2026-09-05-011726-scope-intent-assessment.md) against
|
||
|
|
`b989de4`, which remains the record of the ten-gap backlog and its closures.
|
||
|
|
|
||
|
|
## Why now
|
||
|
|
|
||
|
|
SCOPE.md still declared itself reviewed against `b989de4`, 63 commits behind. Its
|
||
|
|
limits section had been kept honest by appending to bullets, which works until the
|
||
|
|
header asserts a review date that no longer describes what was reviewed. That is
|
||
|
|
the drift this document exists to prevent, in the file whose only job is to say
|
||
|
|
accurately what this repository does.
|
||
|
|
|
||
|
|
## Method
|
||
|
|
|
||
|
|
Every capability claim was checked against source at this revision rather than
|
||
|
|
carried forward from the previous text: routes from the mux registrations in
|
||
|
|
`cmd/keycape/main.go`, subcommands from the dispatch in `main.go` and
|
||
|
|
`internal/authclient/cli.go`, flags from the flag sets, claim behaviour from
|
||
|
|
`token.go` and `userinfo.go`. Live claims were checked against `docs/evidence/`.
|
||
|
|
Where the existing prose and the code disagreed, the code won.
|
||
|
|
|
||
|
|
That method was chosen because the opposite failed repeatedly over the preceding
|
||
|
|
two days: four times a statement about current state was written from a workplan
|
||
|
|
paragraph, a partial `git status`, or an inference about what was admitted,
|
||
|
|
without reading the authoritative artifact. Every one was wrong and every one was
|
||
|
|
cheap to check.
|
||
|
|
|
||
|
|
## What changed since 2026-09-05
|
||
|
|
|
||
|
|
Gaps G01 through G09 are closed; G10 is external and remains open. The
|
||
|
|
capabilities that reached SCOPE only piecemeal, now stated:
|
||
|
|
|
||
|
|
- the authorization-code grant binds the redirect URI, refuses clients not
|
||
|
|
registered for it, authenticates confidential clients and consumes codes
|
||
|
|
atomically; UserInfo checks algorithm, issuer and token purpose (G01);
|
||
|
|
- upstream Authelia ID tokens are verified — signature, issuer, audience,
|
||
|
|
validity window — failing closed and independently of transport, on an explicit
|
||
|
|
operator decision not to police the transport instead (G01);
|
||
|
|
- `/readyz` probes LLDAP by bind, plus Authelia and privacyIDEA, distinct from
|
||
|
|
`/healthz`; `SIGTERM` drains for 15s (G08);
|
||
|
|
- `keycape verify-client` checks a live registration's subject, tenant, roles,
|
||
|
|
lifetime and scope refusals without emitting values;
|
||
|
|
- a human tenant may be supplied by a client registration where the directory has
|
||
|
|
placed the user nowhere, and issuance is refused when the two conflict;
|
||
|
|
`assurance.at` is the authentication time rather than the mint time;
|
||
|
|
- `internal/jose` is the single RS256/JWKS verifier behind both the caller path
|
||
|
|
and upstream verification;
|
||
|
|
- the container packages all five binaries, and the dev stack is bootstrappable
|
||
|
|
from a clean checkout.
|
||
|
|
|
||
|
|
Three corrections to claims SCOPE previously made: caller commands omitted
|
||
|
|
`verify-client`; the container was said to package only `keycape`, untrue since
|
||
|
|
KEY-WP-0026; and the issuer API row omitted `/readyz`.
|
||
|
|
|
||
|
|
## The distinction this revision adds
|
||
|
|
|
||
|
|
SCOPE now states plainly that it describes **source**, and that source behaviour
|
||
|
|
and deployed behaviour are different claims. The evidence for each is different
|
||
|
|
too: 390 Go tests across 23 packages and the named harness runs for the first;
|
||
|
|
committed receipts in `docs/evidence/` for the second.
|
||
|
|
|
||
|
|
What is actually proven live is narrower than what the source does, and worth
|
||
|
|
stating exactly:
|
||
|
|
|
||
|
|
- the upstream issuer was probed and pinned in `sso/keycape-config`. The pre-pin
|
||
|
|
state did not match, so the fail-closed login outage that risk described was
|
||
|
|
real and was averted rather than hypothetical;
|
||
|
|
- both approval service clients were verified in an attended owner process: JWKS
|
||
|
|
signature verification, exact claims, excess-scope and wrong-secret denial at a
|
||
|
|
900s lifetime, no credential values emitted.
|
||
|
|
|
||
|
|
Those receipts also decline what they did not test — no real predecessor rotation,
|
||
|
|
no observed wall-clock expiry — and this document declines it with them.
|
||
|
|
|
||
|
|
The deployed image predates this revision, so the authorization-code bindings,
|
||
|
|
upstream verification, readiness, graceful shutdown and both tenant-related
|
||
|
|
changes are running nowhere yet. Two of them fail closed at startup, which is why
|
||
|
|
they are listed in `docs/operations.md` under "Unreleased fail-closed startup
|
||
|
|
changes" rather than only in a hub message.
|
||
|
|
|
||
|
|
## What remains
|
||
|
|
|
||
|
|
G10 is owner work: KEY-WP-0013-T05 waits on `informed-decision` for the approver
|
||
|
|
client ID and deployed callback, and KEY-WP-0014-T04 has no admitted execution
|
||
|
|
authority for a real predecessor rotation. Neither can be closed from this
|
||
|
|
repository.
|
||
|
|
|
||
|
|
From G04, relying-party behaviour and MFA against a migrated realm are still
|
||
|
|
unexercised, and credential and MFA migration is not supplied at all, so no
|
||
|
|
harness can establish it. Complete profile conformance and drop-in Keycloak
|
||
|
|
interchangeability remain unestablished — and the conformance suite found a
|
||
|
|
concrete reason they are not the same question: stock Keycloak advertises the
|
||
|
|
`implicit` and `password` grants this profile excludes, as server capabilities no
|
||
|
|
realm configuration removes.
|