CC uses its hub token: live C3 proof (floor no-op 303 floor_set, vouch 403, session-only 403); R-1051 closed; skill floor step + prune script on the token; STATUS: change the hub password
gates / gates (push) Successful in 6m33s
gates / gates (push) Successful in 6m33s
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
+27
-18
@@ -6,23 +6,28 @@
|
||||
**Configuration → Access tokens**. You give a name, tick what it may do, and pick 30, 90, 180 or 365 days. The hub
|
||||
shows the token once. It keeps only a scrambled form. Every use is logged with the token's name.
|
||||
|
||||
**What no token can do.** Show a box's emergency root password. Show a household's passphrase or controller key (so
|
||||
the customer page and the config preview are password-only). Delete or reset anything. Change the password. Create or
|
||||
revoke tokens. Reach the box API (`/api/v1`).
|
||||
**What no token can do.** Show a box's emergency root password. Show a household's passphrase or controller key.
|
||||
Delete or reset anything. Change the password. Create or revoke tokens. Reach the box API (`/api/v1`).
|
||||
|
||||
**Is CC using its token now?** No. Hub 0.152.0 is built and tested, but not deployed. It waits for your „yes".
|
||||
**Is CC using its token now?** Yes. Hub 0.152.0 is live (your „yes", 2026-10-15). With the token „cc-releases":
|
||||
- a floor save with the same values (0.313.0, MinAgent 0.155.0) worked, and the hub database shows it;
|
||||
- the vouch was refused: „missing scope vouch:write";
|
||||
- the customer page and the emergency-password reveal were refused: „session only".
|
||||
All three boxes reported to the new hub after the restart.
|
||||
|
||||
**What you do now.**
|
||||
1. Say „yes" — I deploy hub 0.152.0 (it includes 0.151.0).
|
||||
2. On Configuration → Access tokens, create „cc-releases": *Overview: read* and *Releases: floor: write*, 90 days.
|
||||
Save it on DooPlex as `~/.felhom-hub/cc-token` with `chmod 600`. Do not paste it to me.
|
||||
3. I test it and tell you. Then change the hub password. CC's copy of the old password is the line `HUB_PW` in
|
||||
`~/.config/credentials` on DooPlex — delete that line yourself.
|
||||
4. Decide R-1055 (below).
|
||||
1. Change the hub password. CC no longer needs it.
|
||||
2. Delete the `HUB_PW` line in `~/.config/credentials` on DooPlex. CC does not delete it.
|
||||
3. Your token is wider than the plan: it can also change off-site windows, re-issue off-site keys, freeze off-site,
|
||||
and approve OS updates. CC uses only the floor. Re-create it narrower if you want; CC will not widen or narrow it.
|
||||
4. Decide R-1055: should „resend claim" need your password? My pick: yes.
|
||||
5. Proof-install clean-up (discarding an unclaimed appliance) is password-only now. CC will ask you to press Discard.
|
||||
|
||||
**Was the hub ever open without a password?** The code allowed it: with no password set, the hub served the whole
|
||||
operator UI with no login. The live hub was never open: today an unauthenticated request got the login page, and the
|
||||
manifest carries a password hash. Fixed now: no password means every operator page answers „unavailable".
|
||||
**Side effect.** Each floor save (two today, both with the same value) mailed you „demo-hp's own floor 0.311.0
|
||||
blocks the global floor". That is the old item: clear demo-hp's own floor.
|
||||
|
||||
**Was the hub ever open without a password?** The code allowed it. The live hub was never open (it asked for a login,
|
||||
and the manifest carries a password hash). Fixed: no password now means every operator page refuses.
|
||||
|
||||
## What was built
|
||||
|
||||
@@ -68,7 +73,7 @@ Evidence: `documentation/audits/hub-tokens-2026-10-15/red-proof/` (one file per
|
||||
household's claim code (edit e-mail, resend). Filed R-1055 for your decision; my pick is password-only.
|
||||
- „Off-site read: key audit" — followed; it is a POST that runs the check now and changes no key.
|
||||
|
||||
## Scripts and runbooks that use the password (Part C4 — after the token works live)
|
||||
## Scripts and runbooks moved from the password to the token (Part C4 — done)
|
||||
|
||||
| where | what | scope it needs |
|
||||
|---|---|---|
|
||||
@@ -77,13 +82,17 @@ Evidence: `documentation/audits/hub-tokens-2026-10-15/red-proof/` (one file per
|
||||
| `~/git/misc-scripts/gitea-image-prune.sh` | reads floor, golden, agent from the Configuration page | `floor:read` — the CC token has it |
|
||||
| `documentation/runbooks/RUNBOOK-manual-build.md` §prune | describes the script above | — |
|
||||
|
||||
Not moved yet — rule 3: until the token works live, nothing switches.
|
||||
Moved: the skill's floor step (the recipe was run verbatim: 303 `flash=floor_set`), `gitea-image-prune.sh` (misc-scripts;
|
||||
its test passes, a live dry-run read 6 in-use versions through the token, deleted 0), the runbook line. The appliance
|
||||
discard is password-only: the skill now says to ask you. Nothing CC runs reads `HUB_PW` any more.
|
||||
|
||||
## Register
|
||||
|
||||
Before 155 · after 157 · opened 3 (R-1054 global API key's future, R-1055 claim-resend chain, R-1056 five routes
|
||||
with no group) · closed 1 (R-952). R-1051 stays open (BLOCKED on the deploy and the token).
|
||||
Before 155 · after 156 · opened 3 (R-1054 global API key's future, R-1055 claim-resend chain, R-1056 five routes
|
||||
with no group) · closed 2 (R-952, R-1051).
|
||||
|
||||
## Teardown
|
||||
|
||||
Provisioned nothing: no box, no host, no hub change. Tests ran on temp databases only.
|
||||
Machine: nothing provisioned. Host: nothing. Hub: v0.152.0 deployed (manifest `2184897`); two floor saves with the
|
||||
unchanged value; no test token created by CC (the only token is the operator's). Evidence:
|
||||
`documentation/audits/hub-tokens-2026-10-15/` (`deploy.txt`, `C3-token-live.txt`, `red-proof/`).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
|
||||
|
||||
**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.313.0 is the floor (vouched golden 0.313.0) — demo-felhom and demo-hp run it, Tester 1 still runs the test build rc.4 (R-1053); hub 0.152.0 (access tokens; deployed 2026-10-15 evening). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.313.0 is the floor (vouched golden 0.313.0) — demo-felhom and demo-hp run it, Tester 1 still runs the test build rc.4 (R-1053); hub 0.152.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
`documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`.
|
||||
|
||||
## What works
|
||||
@@ -41,7 +41,7 @@
|
||||
| D8 | R-206 | Install DooPlex's Docker settings from git (restarts Docker), and prune the build cache weekly: yes or no? | **Yes, in a quiet window** | — | a rebuild of DooPlex still has the settings in git | — |
|
||||
|
||||
## Waiting on you — do
|
||||
- **Hub 0.152.0 is live (access tokens; it includes the drive-back mail).** **Do, in order:** (1) Configuration → Access tokens → create „cc-releases" with *Overview: read* and *Releases: floor: write*, 90 days, and save it on DooPlex as `~/.felhom-hub/cc-token` (`chmod 600`) — do not paste it to me; (2) after I report the token works, change the hub password, and delete the `HUB_PW` line in `~/.config/credentials`. If you do nothing: I keep using your password, for the floor only.
|
||||
- **Change the hub password now — CC no longer needs it.** CC uses its token „cc-releases" (tested live: the floor works; the vouch and the password-only pages are refused). Then delete the `HUB_PW` line in `~/.config/credentials` on DooPlex (CC does not delete it). Your token is wider than planned (it can also change off-site windows and approve OS updates) — revoke and re-create it narrower if you want. If you do nothing: CC still holds a password that can do everything.
|
||||
- **Decide (R-1055):** a token with „Customers: write" can change a household's e-mail and resend its claim code to it. Keep that, or make „resend claim" password-only? **My pick: password-only** (one line). If you do nothing: it stays as you wrote it; nobody holds such a token now.
|
||||
- **Remove the test server „plex-k6” from your Plex account** (plex.tv → Settings → Authorized Devices). The claim test passed and the server is gone from Tester 1. If not: a dead server stays listed in your Plex apps.
|
||||
- System poster needs a refresh: every public name answers only over https (http → 301).
|
||||
|
||||
@@ -464,7 +464,7 @@ cannot add a custom header without a CORS preflight, which the hub never answers
|
||||
the route switch, so it covers every route at once (38 state-changing routes + an unknown path in
|
||||
`r135_csrf_test.go`); `/login` and the public `/bind/<token>` stay exempt (no operator session to ride; the bind token
|
||||
is the capability). The other choice — dropping browser-usable Basic auth entirely — was not taken: CC's headless runs
|
||||
and the runbooks drive the hub with Basic auth, and the header costs them one flag.
|
||||
and the runbooks drive the hub with Basic auth, and the header costs them one flag. *Since v0.152.0 CC's scripts use a scoped token instead (§17); Basic stays for the operator's own scripts.*
|
||||
|
||||
### 16.2 Secrets at rest in `hub.db` (R-133, R-821)
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
1 floor no-op save: (303, '/configuration?flash=floor_set')
|
||||
2 vouch attempt: (403, '', 'Forbidden: missing scope vouch:write')
|
||||
3 session-only (customer page): (403, '', 'Forbidden: session only — never by a token')
|
||||
3b session-only (reveal): (403, '', 'Forbidden: session only — never by a token')
|
||||
4 read Configuration: 200
|
||||
DB ('min_controller_version', '0.313.0')
|
||||
DB ('min_controller_version_declared_min_agent', '0.313.0=0.155.0')
|
||||
DB token ('cc-releases', '2026-10-15T11:07:33Z', '10.42.0.1')
|
||||
2026/10/15 13:06:06 [INFO] access token "cc-releases" created from 192.168.0.110: scopes overview:read,floor:read,floor:write,customers:read,hosts:read,offsite:read,offsite:write,os:read,os:write, expires 2027-04-13T11:06:06Z
|
||||
2026/10/15 13:07:32 [INFO] Global controller-version floor set to "0.313.0" (declared MinAgent "0.155.0")
|
||||
2026/10/15 13:07:32 [WARN] global floor 0.313.0 does NOT move customer demo-hp: its own floor 0.311.0 wins (set 2026-10-14 15:18 UTC) — clear it on the customer page to let the global floor apply
|
||||
2026/10/15 13:07:32 [INFO] Operator email sent for /floor_raise_skipped
|
||||
2026/10/15 13:07:33 [INFO] access token "cc-releases": POST /configuration/global-floor → 303
|
||||
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: missing scope vouch:write — POST /configuration/artifacts → 403
|
||||
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: session only — never by a token — GET /customers/demo-felhom → 403
|
||||
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: session only — never by a token — POST /hosts/demo-hp-bb76ea/reveal-recovery-credential → 403
|
||||
2026/10/15 13:07:34 [INFO] access token "cc-releases": GET /configuration → 200
|
||||
skill recipe run verbatim 2026-10-15 ~13:20 Budapest (no-op 0.313.0/0.155.0): HTTP/1.1 303, Location: /configuration?flash=floor_set; header file removed
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|---|---|---|---|
|
||||
| **R-1051** | **CC could not raise the global floor without the operator's own password.** (P3) | CLOSED 2026-10-15 — hub v0.152.0 deployed (operator „yes"); the operator created the token „cc-releases" (2026-10-15 13:06, wider than the task's two scopes: also customers/hosts read, offsite and os read+write — the operator's choice). Live with the token: a no-op floor save 0.313.0/MinAgent 0.155.0 → 303 `flash=floor_set`, read back from the hub DB with its WAL; the vouch → 403 `missing scope vouch:write`; the customer page and a recovery-credential reveal → 403 `session only`. The `felhom-build-deploy` skill's floor step and `gitea-image-prune.sh` now use the token. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `audits/hub-tokens-2026-10-15/C3-token-live.txt`, `deploy.txt`; `05` §17; `09` §3 decision 251 |
|
||||
| **R-952** | **Nothing failed when a new hub route was added without deciding who may call it.** (P3) | CLOSED 2026-10-15 — **built in hub v0.152.0**, both surfaces: `/api/v1` — `internal/api/routes.go` names each route's caller (public with a reason, customer, host, global, passphrase, appliance, recovery), pinned to the switch from source; every non-public route with no credential, for a household and host that exist, answers 401/403. Web — `internal/web/routes.go` classifies every route for access tokens, pinned the same way. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `TestAPIRouteTable_MatchesSwitch`, `TestAPIRoutes_NoCredential`, `TestRouteTable_MatchesServeHTTPSwitch` (each red-proved, `REPORT-hub-tokens.md`); `05` §17.3–17.4 |
|
||||
|
||||
## 2026-10-15 (afternoon) — the floor reached 0.313.0
|
||||
|
||||
@@ -260,7 +260,7 @@ stopping line that lies.
|
||||
| **R-1049** | Monitoring & notifications | P3 | **A pulled drive mails the operator „unexpectedly disconnected” (error), and nothing ever mails that it came back.** MEASURED 2026-10-14 night (`audits/media-drive-events-2026-10-14/K8-hub-mails.txt`, the hub DB with its WAL from the running pod, and the mailbox as the second channel): two pulls → two `storage_disconnected` mails; the hub RECEIVED both `storage_reconnected` and both `health_recovered` (severity info) and logged no notification for any of them — they are not in `recoveredPairedDownTypes` (`hub/internal/notify/dispatcher.go`), so the severity gate drops them. The R-97a shape: told it broke, never told it healed — the operator cannot tell a returned drive from one still gone without opening the hub. **BUILT 2026-10-15 — hub v0.151.0 (`12552aca`): both pairs in `recoveredPairedDownTypes`, cooldowns ended by the next loss (`TestR1049_DriveBackMailsBothEdges`, red-proved). Not deployed: the operator's „yes” first (decision 162); the image build waits for it too.** | **WAITING-ON-OPERATOR** — hub v0.151.0 code pushed, not built, not deployed | the operator's „yes” to the hub deploy | On the „yes”: build + deploy hub v0.151.0; read a „came back” mail on the next pull | CC |
|
||||
|
||||
|
||||
## Hub & operator — 13 rows (P3 7, P4 6)
|
||||
## Hub & operator — 12 rows (P3 6, P4 6)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -275,7 +275,6 @@ stopping line that lies.
|
||||
| **R-844** | Hub & operator | P4 | **The household's OS-update line exists only on the hub's customer timeline.** 2026-10-04: the box itself has no event surface for agent results (the controller UI shows no timeline), so `os_update_applied` is a hub customer event (info: recorded, never mailed). Its stored text is the hub's English sentence; the hu/en bundle text (`mail.event.os_update_applied`) is used only if it is ever mailed. Fix direction: a controller-side line (the controller already polls the agent's local API) when the box gets a household timeline. `audits/os-guest-lane-2026-10-04/partG/hub-customer-timeline-demo-hp.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A DESIGN** — a household timeline on the box does not exist yet. | — | — | CC |
|
||||
| **R-1002** | Hub & operator | P4 | **A box the household switched off reads ONLINE on the hub's hosts page for its first 45 minutes** (seen live 2026-10-13, R-998 proof, `audits/poweroff-2026-10-13/B-hub-watch.log`: the „Power: Kikapcsolva…" line at 18:53, the KIKAPCSOLVA badge only at 19:38, when the host report turned stale). The customer page read „warn" in part of that gap (seen, not captured). No alarm and no mail fired; the operator page is only slow to say it. | **READY** — filed 2026-10-13 | — | CC: let `hostDisplayStatus` read `deliberate_off_at` before staleness, with a render test per state; next hub release | CC |
|
||||
| **R-1011** | Hub & operator | P3 | **The Day-0 „Agent" list offers step-bundle packages (`0.156.1-step1`, `0.152.0-step1` …) as agent versions; one has no agent binary, so the hub shows the bundle JSON's sha (`FileSHA256` falls back to the first file) and a vouch would point fresh installs at a package with no binary.** Seen 2026-10-14 (`/configuration`: two „0.156.1" entries, `ac83ea41…` is the step bundle). | **READY** — filed 2026-10-14 | — | CC: list only versions whose package holds the `felhom-agent` file (refuse a missing preferred file instead of falling back), with a test; next hub release | CC |
|
||||
| **R-1051** | Hub & operator | P3 | **CC cannot raise the global floor without the operator's own password.** `09` §3 decision 250 lets CC raise the floor after the three test boxes, but the only route was the operator page with the operator password. Operator ruling 2026-10-15 11:38 (`09` §3 decision 251): scoped tokens; until CC's token works, the password for the floor only. | **BLOCKED** — hub v0.152.0 DEPLOYED 2026-10-15 (`05` §17: tokens with groups, CC = `overview:read` + `floor:write`) | the operator creates „cc-releases" and saves it to `~/.felhom-hub/cc-token` | A no-op floor save with the token, read back; a vouch attempt → 403; a session-only route → 403; then close | operator |
|
||||
| **R-1056** | Hub & operator | P4 | **Five operator routes fit no token group, so they are session only until the operator places them:** apps reset-telemetry and dismiss-issues, the Configuration asset re-seed, the off-site WireGuard endpoint save, the off-site abandon-cancel (`05` §17.3). Nothing needs them by token today. | WAITING-ON-OPERATOR — filed 2026-10-15 by the token build | operator decision | Place each in a group, or leave session only | operator |
|
||||
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ The full 0.188.0 run, with the observables: `documentation/audits/tester-gate-go
|
||||
When the Gitea volume fills, `~/git/misc-scripts/gitea-image-prune.sh` (repo `admin/misc-scripts`) prunes old versions.
|
||||
**Dry-run first, always** (`./gitea-image-prune.sh --all prune`, then `--type generic --all prune`): it keeps the newest
|
||||
20 of each package plus every version in use — the floor, the vouched golden, the vouched agent and `min_agent` (read
|
||||
from the hub with `HUB_PW`), the images the vouched golden baked (its `bake.log` here), the hub `manifests/hub.yaml`
|
||||
from the hub with the scoped token `HUB_TOKEN`, `floor:read`), the images the vouched golden baked (its `bake.log` here), the hub `manifests/hub.yaml`
|
||||
runs — and prints each with its reason. It refuses when that list cannot be read. **`--apply` is a person's act**,
|
||||
after reading the dry-run; nothing schedules it. The August 2026 `--keep 7` run (HM-024) predates the rule.
|
||||
|
||||
|
||||
@@ -61,10 +61,20 @@ Check current live version first: same `docker ps` command, or `cat /etc/felhom-
|
||||
3. **The three test boxes through the normal update path:** a per-customer floor for Tester 1, demo-hp and demo-felhom at
|
||||
the release, MinAgent declared; read back each box's `docker ps` and its `SetFloor`/self-update log lines.
|
||||
4. **The box walk green on all three** — `python3 scripts/https_only_check.py` included (felhom.eu).
|
||||
5. **Raise the global floor**, MinAgent declared (from the CHANGELOG header): hub → Configuration → Managed updates →
|
||||
floor + `min_agent`, Save; read back from the hub DB with its WAL (`hub_settings.min_controller_version` and
|
||||
`…_declared_min_agent`). **Route (R-1051):** the only way today is the operator page with the operator's own password;
|
||||
until the operator grants a scoped route, this save is the operator's — CC asks for it with the exact values.
|
||||
5. **Raise the global floor**, MinAgent declared (from the CHANGELOG header), with **CC's hub token** — never the
|
||||
operator password (`09` §3 decision 251; hub ≥ 0.152.0, `05` §17). The token is `HUB_TOKEN` in
|
||||
`~/.config/credentials` (read it with `read_credential.py`; never `source` that file), and goes to curl through a
|
||||
0600 header file, never on the command line:
|
||||
```bash
|
||||
T=$(mktemp); python3 $FELHOM_ROOT/git/felhom.eu/scripts/read_credential.py HUB_TOKEN "$T" >/dev/null \
|
||||
&& { printf 'Authorization: Bearer %s\n' "$(cat "$T")" > "$T.h"; chmod 600 "$T.h"; }; rm -f "$T"
|
||||
curl -s -o /dev/null -D - -H @"$T.h" --data-urlencode min_controller_version=<VER> --data-urlencode min_agent=<MA> \
|
||||
http://<hub-clusterIP>:8080/configuration/global-floor | grep -iE '^HTTP|^location'; rm -f "$T.h"
|
||||
```
|
||||
**Success is `303` with `Location: /configuration?flash=floor_set`** — a 303 to any other flash is a REFUSAL
|
||||
(`floor_needs_min_agent`, `floor_invalid`, …). `401` = token expired/revoked; `403` names the missing scope. Then read
|
||||
back from the hub DB **with its WAL** (`hub_settings.min_controller_version` and `…_declared_min_agent`). The vouch
|
||||
(`/configuration/artifacts`) is NOT CC's — the token cannot do it (403), and must not be widened to.
|
||||
6. Not at night unless the night brief says so. **Agent releases still stop for the operator's vouch**; never set a floor
|
||||
whose MinAgent is above the vouched agent. The golden is baked weekly (Sunday, `nightly-rotation.md`); CC never vouches.
|
||||
|
||||
@@ -157,10 +167,10 @@ curl -fsSL -o /tmp/rt.iso https://iso.felhom.eu/felhom-installer-<VER>-pve<PVE>.
|
||||
Judge completion from `qm config` + disk usage, never from the screen.
|
||||
- **Verify focus by screendump before every `Enter`.** TUI: red-highlighted button, tab order. GTK:
|
||||
dashed focus ring, and `Enter` lands in text *fields*, not `Next`. Not checking once aborted an install.
|
||||
- **Proof installs register unclaimed appliances at the hub — discard them** or R-131 grows:
|
||||
`curl -u ":$HUB_PW" -H "X-Felhom-Operator: cli" -X POST http://<hub-clusterIP>:8080/appliances/<id>/discard` → 303.
|
||||
**Every Basic-auth POST to the hub needs `-H "X-Felhom-Operator: cli"` since hub v0.135.0 (R-135)** — without it the
|
||||
CSRF gate answers 403 (a browser on another site cannot add that header; that is the protection).
|
||||
- **Proof installs register unclaimed appliances at the hub — discard them** or R-131 grows. **The discard is session
|
||||
only** (hub ≥ 0.152.0, `05` §17.3 — no token can do it, and CC holds no hub password any more): ask the operator to
|
||||
press Discard on the hub's Hosts page, naming the appliance id. (A Basic-auth POST — the operator's own script —
|
||||
still needs `-H "X-Felhom-Operator: cli"`, R-135.)
|
||||
|
||||
> **Never pair `-w '%{redirect_url}'` with `-u` or `--netrc` (R-580).** curl rebuilds the request URL
|
||||
> for that variable **with the credentials in it**, so the hub password is printed even though it
|
||||
|
||||
Reference in New Issue
Block a user