NK-WP-0056-T09 done: syn-journey live, synthetic journey passes at aal2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Founder-run under CCR-2026-0057: KeyCape client registered, lane.py setup
(privacyIDEA-generated key, KV v1) and verify passed; the live journey
reached aal2 with otp against kc.coulomb.social. Receipts, findings and the
one teardown of a partial run in docs/evidence. syn-journey declared active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
This commit is contained in:
tegwick 2026-10-31 13:33:31 +01:00
parent 488f5f4ece
commit 661ab6a52e
7 changed files with 181 additions and 3 deletions

View file

@ -345,7 +345,7 @@
| task | NK-WP-0056-T06 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T07 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T08 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T09 | wait | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T09 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T10 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |

View file

@ -0,0 +1,12 @@
{
"action": "teardown",
"identity": "syn-journey",
"keycape_client_removal_separate": true,
"kv_removed": true,
"lldap_user_removed": true,
"phase": "done",
"pi_token_removed": true,
"source_sha256": "a213b5f9fc6703dfc5ad619320a2fd083596ece7a2a1895f42829159bea42dde",
"status": "identity_removed",
"values_recorded": false
}

View file

@ -0,0 +1,24 @@
{
"action": "setup",
"ccr_sha256": "4a9668856389f986d4220bbd424bd6b353d86393bb931ebad293f726219d8754",
"coding_agents_denied": true,
"created": {
"email": "syn-journey@synthetic.invalid",
"groups": [],
"kv_path": "platform/workloads/net-kingdom/synthetic-journey/syn-journey",
"lldap_user": "syn-journey",
"pi_realm": "coulomb",
"pi_token_serial": "SYNJOURNEY01"
},
"enrollment_confirmed_with_code": true,
"identity": "syn-journey",
"key_generated_by": "privacyidea",
"kv_version": 1,
"lldap_password_effective": true,
"phase": "done",
"source_ccr": "CCR-2026-0057",
"source_sha256": "a213b5f9fc6703dfc5ad619320a2fd083596ece7a2a1895f42829159bea42dde",
"status": "identity_prepared",
"values_recorded": false,
"workload_access": false
}

View file

@ -0,0 +1,91 @@
{
"action": "verify",
"ccr_sha256": "4a9668856389f986d4220bbd424bd6b353d86393bb931ebad293f726219d8754",
"coding_agents_denied": true,
"identity": "syn-journey",
"journey": {
"binds_on_path": {
"authelia_first_factor": "svc-authelia",
"keycape_otp": "svc-privacyidea",
"keycape_token": "svc-keycape"
},
"claims": {
"assurance_level": "aal2",
"assurance_methods": [
"pwd",
"otp"
],
"assurance_source": "key-cape",
"kid": "key-1",
"preferred_username_matches": true,
"signature_verified": true
},
"failed_step": null,
"finished_at": "2026-10-31T12:32:38Z",
"otp_time_step": 59781665,
"reason": null,
"result": "pass",
"run_id": "53251d54-80d1-4c23-bc6b-fd025e5d1a94",
"runner_sha256": "c639054597be7647a3ae88ac7da53490a43b38d9f4f95e24cf4e9f54b18f997c",
"schema": "netkingdom.synthetic-journey.receipt/v1",
"started_at": "2026-10-31T12:32:35Z",
"steps": [
{
"elapsed_ms": 117,
"name": "discovery",
"ok": true
},
{
"elapsed_ms": 97,
"name": "keycape_authorize",
"ok": true
},
{
"elapsed_ms": 771,
"name": "authelia_first_factor",
"ok": true
},
{
"elapsed_ms": 326,
"name": "authelia_authorization",
"ok": true
},
{
"elapsed_ms": 880,
"name": "keycape_callback",
"ok": true
},
{
"elapsed_ms": 410,
"name": "keycape_otp",
"ok": true
},
{
"elapsed_ms": 249,
"name": "keycape_token",
"ok": true
},
{
"elapsed_ms": 99,
"name": "id_token",
"ok": true
}
],
"target": {
"acr_values": "urn:netkingdom:aal2",
"client_id": "nk-synthetic-journey",
"host_mapped": [],
"issuer": "https://kc.coulomb.social",
"username": "syn-journey"
}
},
"journey_passed": true,
"kv_is_current": true,
"kv_version": 1,
"phase": "done",
"source_ccr": "CCR-2026-0057",
"source_sha256": "a213b5f9fc6703dfc5ad619320a2fd083596ece7a2a1895f42829159bea42dde",
"status": "identity_verified",
"values_recorded": false,
"workload_access": false
}

View file

@ -0,0 +1,43 @@
# NK-WP-0056-T09: synthetic journey identity live (2026-10-31)
Founder-run, attended. claude-code prepared the tooling and guided the run. Under CCR-2026-0057
(railiance-platform `2ac9786`, sha256 `4a966885…8754`) and the accepted SecurityCase
`nk-synthetic-journey` (net-kingdom `d654f8a`):
1. The KeyCape client `nk-synthetic-journey` (public, PKCE, MFA pinned) was registered with
`keycape-client.py apply` (check-config from the deployed `e2b5fa1` binary) and KeyCape restarted.
2. `lane.py setup` fenced coding agents from the KV path and created `syn-journey` in LLDAP with no
groups and a generated password. It enrolled TOTP token `SYNJOURNEY01` in realm `coulomb` with a
key generated by privacyIDEA, confirmed with a code, and wrote `PASSWORD` and `TOTP_SEED` to
`platform/workloads/net-kingdom/synthetic-journey/syn-journey` (KV v1, CAS=0). No workload role.
3. `lane.py verify`: the KV holds exactly the two fields, the fence is present, no workload role
exists, the KV password signs in to LLDAP, and **the synthetic journey passed against
`https://kc.coulomb.social`**: aal2, methods `pwd`+`otp`, signature verified against KeyCape's
JWKS, `preferred_username` matched. About 3 s end to end.
| Receipt | Result |
| --- | --- |
| `00-setup-refused-absent.json` | refused, nothing changed (privacyIDEA token lookup by a user that did not resolve yet, or the pi-admin login) |
| `00-setup-refused-pi-admin-login.json` | refused at the pi-admin sign-in, nothing changed |
| `00-verify-before-setup.json` | expected failure: no KV yet |
| `01-setup.json` | stopped at `/token/init` after the user and KV v1 were written (see finding 1) |
| `10-teardown.json` | removed that partial state: token, user, KV |
| `11-setup.json` | `identity_prepared`, key generated by privacyIDEA, enrollment confirmed with a code |
| `12-verify.json` | `identity_verified`, journey `pass` (embedded, value-free) |
All receipts were scanned for secret-shaped strings before commit. They hold identifiers, versions
and booleans only.
## Findings
1. **privacyIDEA silently replaces a supplied TOTP key for `pi-admin`.** An admin policy with
`action=*` (as `pi-admin-all-rights`) enables `totp_force_server_generate`, so `otpkey` is ignored
and the confirmation with a code from the supplied key fails (`ERR905`). This was reproduced on the
pinned 3.12.2 image. The lane now enrolls with `genkey=1` and takes the seed from the enrollment
response, in memory only. Any tool that enrolls a known seed as `pi-admin` has the same problem.
2. Listing privacyIDEA tokens by a user that does not resolve is an error, not an empty list. The
lane looks tokens up by serial.
3. Live KeyCape issues tokens to a user with no groups and no tenant mapping. The open point in the
runbook is answered.
4. Still open, recorded in CCR-2026-0057: KeyCape marks the token `principal_type: human`, and
KeyCape has no per-client group gate.

View file

@ -94,7 +94,7 @@ identities:
consumers: [{name: synthetic-journey, kind: service}]
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "lane.py teardown then setup (CCR-2026-0057); password and TOTP seed together", coupling: [privacyidea]}
status: planned
status: active
exceptions:
- id: SI-X6
rules: [SI-3]

View file

@ -305,7 +305,7 @@ or clear the resolver cache, and the attended `--rotate-bind` MFA proof has the
```task
id: NK-WP-0056-T09
status: wait
status: done
priority: medium
assignee: claude-code
needs_human: true
@ -331,6 +331,14 @@ reader), SecurityCase `security-cases/nk-synthetic-journey.yaml` (public PKCE cl
Open, recorded: KeyCape marks every token `principal_type: human`; KeyCape has no per-client group gate.
Waiting on: CCR approval, case acceptance, founder run of the runbook.
**Done 2026-10-31** ([evidence](../docs/evidence/2026-10-31-nk56-t09-synthetic-identity/README.md)): CCR-2026-0057
approved; case accepted; client registered; founder-run `lane.py setup` and `verify` passed. The live journey
reached aal2 (`pwd`+`otp`, signature verified) against `kc.coulomb.social`. Two lane fixes on the way: privacyIDEA
tokens are looked up by serial, and enrollment uses a privacyIDEA-generated key, because `pi-admin`'s `action=*`
enables `totp_force_server_generate`, which silently replaced the supplied key (reproduced on 3.12.2). One
partial run was removed with `lane.py teardown`. Next: T10 needs the executor identity (T03) and a
`workload-kv-read` request for it.
## Unattended live journey
```task