Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Demo control page

A web page to drive the demo without a terminal: start k3s, deploy, check and remove a mode, see its links and accounts, run the chaos testing and read its verdicts.

demo/k3s/demo.sh ui          # starts cfg-demo-ui, prints the URL with a fresh token
demo/k3s/demo.sh ui stop     # stops it when no action runs; --force: anyway

ui prints http://127.0.0.1:32001/#token=... and opens nothing: paste the URL into the browser. The token serves once; run again, ui draws a new one for the running page (another tab, another browser).

What it shows

TabWhat
Controlk3s’s state, cluster-up and cluster-down; per mode: deploy (preset, boxes s3, oidc, proxy, otel), check, remove; per deployed mode: links (console, explorer, Keycloak, RustFS console, Grafana), the sftp command to copy, credentials on a click
Chaoseach pod’s live state (ready, /readyz, peers, backends, detectors, bans), one card per experiment: hypothesis, mechanism and rules, injection, signal timeline, verdict and delays, a link to Grafana over the run’s time range with --with-otel

The running action’s log streams live under the tabs. One action at a time per mode; cluster-up and cluster-down exclude any other. Secrets stay out of it: a deploy started from the page prints (hidden: in the page) in their place, and the page masks any secret value of an env.sh that shows up in a line. The “Show credentials” button reads them from demo/k3s/.work/<mode>/env.sh, on demand.

The security model

The container cfg-demo-ui holds the host’s Docker socket: whoever drives the page drives Docker, the equivalent of root on the machine. The page is therefore never started on its own, and every request goes through these guards:

GuardEffect
listeningCFG_DEMO_BIND when it is a loopback address (127.0.0.1 by default, 127.x, ::1), no other; port CFG_DEMO_UI_PORT (CFG_DEMO_PORT_BASE + 1, 32001)
token32 random bytes, drawn at each ui, in demo/k3s/.work/ui/token (mode 600); in the URL’s fragment (#token=), which the browser sends to no server; the page trades it once (POST /api/session) for a session secret, and takes it out of the address bar; the token is then spent
sessionthe secret, kept in the tab’s sessionStorage, sent as Authorization: Bearer with every API request; no cookie, so nothing the browser would attach on its own from another page or port; without it, 401 (the page’s files, holding no secret, are served without)
Host header127.0.0.1, localhost, [::1] or the listening address, with the port: a DNS name rebound to 127.0.0.1 is refused (403)
originevery action is a JSON POST (Content-Type: application/json, 4096 bytes at most) from the page itself: the page’s Origin (or Sec-Fetch-Site); otherwise 403
actionsa fixed list of demo.sh subcommands; modes read from modes/, presets from presets/, options among the four --with-*, experiments from chaos.json; no free text, demo.sh run without a shell
containerthe host’s user and group (plus the socket’s group), read-only file system, no capability; the kit mounted read-only at its own path, .work writable
headersContent-Security-Policy with no outside source nor inline script, X-Frame-Options: DENY, Referrer-Policy: no-referrer, Cache-Control: no-store

The image, cfg-demo-ui:<fingerprint>, is built at the first ui from demo/k3s/ui/Dockerfile: the kit’s Python image (python:3.13.9-alpine3.22), plus bash, the Docker CLI and coreutils (Alpine packages). The server (demo/k3s/ui/server.py) uses Python’s standard library alone; the page (index.html, app.js, style.css) loads nothing from elsewhere.

An action’s lock holds within the page: a demo.sh started in a terminal at the same time is not held back. cluster-down leaves the page running, and demo/k3s/.work/ui/ with it; ui stop removes them. ui stop refuses while an action of the page runs; --force stops the page, which first stops each action, an experiment’s restore included (300 s at most). chaos undoes at its start what an interrupted experiment would have left.

The browser test

demo/k3s/demo.sh cluster-up
demo/k3s/ui/test/run.sh       # starts the page if need be, draws a fresh token

run.sh runs demo/k3s/ui/test/ui.mjs in the Playwright image of the console’s tests (mcr.microsoft.com/playwright:v1.63.0-noble), headless Chromium, on the host’s network. It checks, in raw HTTP: 401 without a session, with a forged one or with the startup token in its place, 401 for a wrong token, 403 under another Host or from another origin, the token refused once traded, 415 for no JSON, 400 for a body over 4096 bytes, an option or an action out of the list, no cookie at all. Then, in the page: the token out of the address, deploy single started and its log followed to the end, the passwords neither in the log nor in the page, the credentials on a click, the tabs from the keyboard, the pod’s state, chaos single auth-burst and its card (verdict, timeline, hypothesis, rules), remove single. A browser console error fails it. The mode single must be free; log and screenshots: demo/k3s/.work/ui-test/.