k_card/PHASE5_RUNBOOK.md

241 lines
7.8 KiB
Markdown

# Phase 5 Runbook (Session Reuse Prototype)
This runbook starts a minimal `k_server` + `k_proxy` prototype for session reuse testing.
Last updated: 2026-04-26
Related browser demo:
- `k_client_portal.py` can now be used in `k_client` at `http://127.0.0.1:8766` to show:
- registration
- current registered-user list from `k_proxy`
- unregister from the browser page
- login with card approval/denial
- protected `k_server` counter access
- logout
- explicit "k_server was not called" behavior when login is denied
## What This Prototype Covers
- `k_proxy` creates short-lived sessions.
- Session creation uses a card-presence check (`fido2_probe.py --json`) as the current auth gate.
- Valid sessions can repeatedly access a protected `k_server` counter endpoint without re-running card auth each request.
- Session status and logout/invalidation paths are implemented.
## Modes
There are two useful ways to run this prototype:
- Same-VM quickstart: `k_proxy` and `k_server` run on one VM for app-local testing.
- Split-VM chain: `k_proxy` runs in `k_proxy`, `k_server` runs in `k_server`, and the Qubes forwarding layer must permit the chain.
## Start Services
### Same-VM quickstart
This matches the code defaults and is useful for basic app behavior only.
In the chosen VM:
```bash
python3 /home/user/chromecard/k_server_app.py --host 127.0.0.1 --port 8780 --proxy-token dev-proxy-token
```
In the same VM:
```bash
python3 /home/user/chromecard/k_proxy_app.py \
--host 127.0.0.1 \
--port 8770 \
--session-ttl 300 \
--server-base-url http://127.0.0.1:8780 \
--proxy-token dev-proxy-token
```
### Split-VM chain
This is the current Qubes target shape.
In `k_server` VM:
```bash
python3 /home/user/chromecard/k_server_app.py \
--host 127.0.0.1 \
--port 8780 \
--proxy-token dev-proxy-token \
--tls-certfile /home/user/chromecard/tls/phase2/k_server.crt \
--tls-keyfile /home/user/chromecard/tls/phase2/k_server.key
```
In `k_proxy` VM:
```bash
qvm-connect-tcp 9780:k_server:8780
```
Notes:
```bash
python3 /home/user/chromecard/k_proxy_app.py \
--host 127.0.0.1 \
--port 8771 \
--session-ttl 300 \
--server-base-url https://127.0.0.1:9780 \
--server-ca-file /home/user/chromecard/tls/phase2/ca.crt \
--proxy-token dev-proxy-token \
--tls-certfile /home/user/chromecard/tls/phase2/k_proxy.crt \
--tls-keyfile /home/user/chromecard/tls/phase2/k_proxy.key
```
In `k_client` VM:
```bash
qvm-connect-tcp 9771:k_proxy:8771
```
Notes:
- Current validated split-VM path is `k_client localhost:9771 -> k_proxy localhost:8771 -> k_proxy localhost:9780 forward -> k_server localhost:8780`.
- Use `--cacert /home/user/chromecard/tls/phase2/ca.crt` for TLS verification in `curl`-based checks.
- Raw VM-IP routing is not the validated path for the current prototype.
## Ownership And Concurrency
- `k_proxy` is authoritative for session state.
- `k_server` is authoritative for the protected counter state.
- Sessions are in-memory only in `k_proxy` and are lost on proxy restart.
- The protected counter is in-memory only in `k_server` and resets on server restart.
- Both services use `ThreadingHTTPServer`.
- `k_proxy` guards its session store with a single process-local lock.
- `k_server` guards counter increments with a single process-local lock.
- Qubes localhost forwarders are transport plumbing only; they are not a source of state authority.
## Test Flow
Use the proxy port that matches the mode you started:
- Same-VM quickstart: `8770`
- Split-VM chain: `9771` from `k_client`, `8771` inside `k_proxy`
Create a session (runs auth gate once):
```bash
curl -sS --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/session/login \
-H 'Content-Type: application/json' \
-d '{"username":"alice"}'
```
Copy `session_token` from response, then:
```bash
TOKEN='<paste-token>'
```
Check session:
```bash
curl -sS --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/session/status \
-H "Authorization: Bearer $TOKEN"
```
Call protected resource multiple times (should not require new login):
```bash
curl -sS --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/resource/counter \
-H "Authorization: Bearer $TOKEN"
curl -sS --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/resource/counter \
-H "Authorization: Bearer $TOKEN"
```
Logout/invalidate:
```bash
curl -sS --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/session/logout \
-H "Authorization: Bearer $TOKEN"
```
Re-check after logout (should fail with 401):
```bash
curl -i --cacert /home/user/chromecard/tls/phase2/ca.crt -X POST https://127.0.0.1:<proxy-port>/resource/counter \
-H "Authorization: Bearer $TOKEN"
```
## Regression Script
For the split-VM chain, use the host-side regression helper:
```bash
/home/user/chromecard/phase5_chain_regression.sh
```
Defaults:
- Drives the test from `k_client` over SSH.
- Uses `https://127.0.0.1:9771` and `/home/user/chromecard/tls/phase2/ca.crt` inside `k_client`.
- Logs in as `alice`.
- Runs `20` counter requests at parallelism `8`.
- Verifies that returned counter values are unique and gap-free, then logs out and checks for `401` after logout.
Useful overrides:
```bash
REQUESTS=50 PARALLELISM=12 /home/user/chromecard/phase5_chain_regression.sh
```
```bash
/home/user/chromecard/phase5_chain_regression.sh --username alice --client-host k_client
```
For the browser-facing `k_client` page, use the Playwright regression spec:
```bash
npm install
npx playwright install
npm run test:k-client
```
Notes:
- default target is `http://127.0.0.1:8766`
- override with `PORTAL_BASE_URL=http://127.0.0.1:8766`
- the spec expects manual card confirmation during register and login
- timeouts can be tuned with `CARD_REGISTRATION_TIMEOUT_MS` and `CARD_LOGIN_TIMEOUT_MS`
- from this host, a forwarded portal URL was used successfully:
- `PORTAL_BASE_URL=http://127.0.0.1:18766 npm run test:k-client`
Verified result on 2026-04-25:
- Live split-VM chain passed end-to-end.
- Login, session status, counter reuse, and logout all worked from `k_client`.
- A `20` request / `8` worker concurrency burst returned unique, gap-free counter values `23..42`.
- The Playwright browser regression for `k_client_portal.py` also passed end-to-end:
- register
- login
- protected counter
- logout
- unregister
## Current Limitation
- The stable deployed baseline still uses card-presence probing, not full assertion verification, for the default auth gate.
- Session and counter state are still process-local only; restart loses state.
- Upstream trust still relies on a shared static `X-Proxy-Token`.
- Experimental direct FIDO2 mode exists in `k_proxy_app.py` behind `--auth-mode fido2-direct`:
- direct `/enroll/register` now succeeds
- direct `/session/login` now succeeds and returns `auth_mode: "fido2_assertion"`
- direct `/session/status`, `/resource/counter`, and `/session/logout` also succeed end-to-end
- the mode remains optional for now; the deployed service was returned to default `probe` mode so the validated Phase 5 baseline stays reproducible
- Raw CTAP debugging helper exists at `/home/user/chromecard/raw_ctap_probe.py`:
- use it on `k_proxy` to exercise low-level `makeCredential` / `getAssertion`
- it logs keepalive callbacks and transport exceptions
- `phase5_chain_regression.sh` now supports card-interactive direct auth via:
- `--interactive-card`
- `--expect-auth-mode fido2_assertion`
## Current Focus
- Keep the HTTPS split-VM chain reproducible in default `probe` mode.
- Decide whether `fido2-direct` is ready to become the default deployed auth path.
- Continue Phase 6.5 concurrency work; the active system limit is still higher-fan-out Qubes forwarding on the browser-facing path rather than basic Phase 5 functionality.