7.8 KiB
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.pycan now be used ink_clientathttp://127.0.0.1:8766to show:- registration
- current registered-user list from
k_proxy - unregister from the browser page
- login with card approval/denial
- protected
k_servercounter access - logout
- explicit "k_server was not called" behavior when login is denied
What This Prototype Covers
k_proxycreates 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_servercounter 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_proxyandk_serverrun on one VM for app-local testing. - Split-VM chain:
k_proxyruns ink_proxy,k_serverruns ink_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:
python3 /home/user/chromecard/k_server_app.py --host 127.0.0.1 --port 8780 --proxy-token dev-proxy-token
In the same VM:
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:
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:
qvm-connect-tcp 9780:k_server:8780
Notes:
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:
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.crtfor TLS verification incurl-based checks. - Raw VM-IP routing is not the validated path for the current prototype.
Ownership And Concurrency
k_proxyis authoritative for session state.k_serveris authoritative for the protected counter state.- Sessions are in-memory only in
k_proxyand are lost on proxy restart. - The protected counter is in-memory only in
k_serverand resets on server restart. - Both services use
ThreadingHTTPServer. k_proxyguards its session store with a single process-local lock.k_serverguards 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:
9771fromk_client,8771insidek_proxy
Create a session (runs auth gate once):
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:
TOKEN='<paste-token>'
Check session:
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):
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:
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):
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:
/home/user/chromecard/phase5_chain_regression.sh
Defaults:
- Drives the test from
k_clientover SSH. - Uses
https://127.0.0.1:9771and/home/user/chromecard/tls/phase2/ca.crtinsidek_client. - Logs in as
alice. - Runs
20counter requests at parallelism8. - Verifies that returned counter values are unique and gap-free, then logs out and checks for
401after logout.
Useful overrides:
REQUESTS=50 PARALLELISM=12 /home/user/chromecard/phase5_chain_regression.sh
/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:
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_MSandCARD_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
20request /8worker concurrency burst returned unique, gap-free counter values23..42. - The Playwright browser regression for
k_client_portal.pyalso 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.pybehind--auth-mode fido2-direct:- direct
/enroll/registernow succeeds - direct
/session/loginnow succeeds and returnsauth_mode: "fido2_assertion" - direct
/session/status,/resource/counter, and/session/logoutalso succeed end-to-end - the mode remains optional for now; the deployed service was returned to default
probemode so the validated Phase 5 baseline stays reproducible
- direct
- Raw CTAP debugging helper exists at
/home/user/chromecard/raw_ctap_probe.py:- use it on
k_proxyto exercise low-levelmakeCredential/getAssertion - it logs keepalive callbacks and transport exceptions
- use it on
phase5_chain_regression.shnow supports card-interactive direct auth via:--interactive-card--expect-auth-mode fido2_assertion
Current Focus
- Keep the HTTPS split-VM chain reproducible in default
probemode. - Decide whether
fido2-directis 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.