Campaign 10: Phase A complete + gated; Phase B not run; R-156 filed
Phase A passed every gate on a fresh box built from the PUBLISHED ISO 1.26.1: install, claim, two drives enrolled through the real endpoints with the backup target healthy, four apps spanning both sides of D5's secret split, and a working discriminator across all four. Isolation gate: both denials captured, each with a positive control. The PBS control FAILED first — four clean-looking 403s were worthless because the token was denied on its own datastore too (PBS token privilege separation). Fixed and re-run; the denials stand. R-156 (new, register grepped): papra's data is neither persisted nor backed up, and it reports healthy. The template mounts papra_data:/app/data; the app writes /app/app-data/db/db.sqlite. Volume empty and root-owned against a -rootless image, real DB in the container writable layer, healthcheck only probes the HTTP port. Its Tier-1/2 backup is real, verifiable and contains nothing. Not fixed. Tier 3 could not be isolated so it was not run: offsite hard-requires the DR tier (configs.go:1300) and the DR tier only provisions on ep0 (per-endpoint allocation deferred, hub/README.md:260). Both are recorded deliberate positions, so no R-n minted. The campaign touched neither ep0 nor the Storage Box. Phase B did not start. Phase A was budgeted at ~1h and took ~5.5h (1.26.1 is a public release image with no auto-install path, so the install was a blind screendump+sendkey walk). That left the runner — which judges eleven invariants and fires destructive atoms unattended — to be written at 04:00 with ~3h of night left. Stopped on the brief's own fence: a rig producing false negatives is worse than no rig. The rig is built and idle; teardown is OWED and itemised, including hub customer c10-soak (disposition: DELETE).
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# PBS isolation proof — token c10@pbs!box (secret redacted, never echoed)
|
||||
# DooPlex PBS https://localhost:8007 2026-07-31T20:14:40Z
|
||||
# ACL: c10@pbs and c10@pbs!box hold DatastoreBackup on /datastore/felhom-c10 ONLY.
|
||||
# felhom-spike (production, demo-felhom's backups) carries NO grant for this auth-id.
|
||||
|
||||
## POSITIVE CONTROL — same token, its OWN datastore (must SUCCEED, else the 403s below prove nothing)
|
||||
$ GET /api2/json/admin/datastore/felhom-c10/snapshots
|
||||
{"data":[]}
|
||||
HTTP 200
|
||||
|
||||
## DENIAL 1 — list snapshots of PRODUCTION datastore felhom-spike
|
||||
permission check failed - missing Datastore.Audit|Datastore.Backup on /datastore/felhom-spike
|
||||
HTTP 403
|
||||
|
||||
## DENIAL 2 — list NAMESPACES of felhom-spike
|
||||
permission check failed
|
||||
HTTP 403
|
||||
|
||||
## DENIAL 3 — DELETE namespace demo-felhom in felhom-spike (destructive)
|
||||
permission check failed - missing Datastore.Modify on /datastore/felhom-spike
|
||||
HTTP 403
|
||||
|
||||
## DENIAL 4 — status of felhom-spike
|
||||
permission check failed
|
||||
HTTP 403
|
||||
@@ -0,0 +1,53 @@
|
||||
# restic/Storage-Box isolation proof — subaccount u629488-sub4 (home felhom-campaign10)
|
||||
# box 611714 storage-box-pool-1 · server u629488.your-storagebox.de · 2026-07-31T20:15:55Z
|
||||
# Production subaccounts on the SAME box: sub1=felhom-demo-felhom sub2=felhom-peti-felhom sub3=felhom-demo-hp
|
||||
# Password never echoed; supplied to sshpass via env.
|
||||
|
||||
## POSITIVE CONTROL — sub4 in its OWN home (must SUCCEED)
|
||||
$ sftp> pwd ; ls -a ; put canary ; ls
|
||||
Warning: Permanently added '[u629488.your-storagebox.de]:23' (ED25519) to the list of known hosts.
|
||||
Connected to u629488.your-storagebox.de.
|
||||
sftp> pwd
|
||||
Remote working directory: /home
|
||||
sftp> ls -a
|
||||
. ..
|
||||
|
||||
## DENIAL 1 — reach Peti's REAL customer repo path from sub4
|
||||
$ sftp> cd /home/felhom-peti-felhom / cd ../felhom-peti-felhom / ls /felhom-peti-felhom
|
||||
Connected to u629488.your-storagebox.de.
|
||||
sftp> cd /home/felhom-peti-felhom
|
||||
stat remote: No such file or directory
|
||||
sftp> cd ../felhom-peti-felhom
|
||||
stat remote: No such file or directory
|
||||
sftp> ls /felhom-peti-felhom
|
||||
Can't ls: "/felhom-peti-felhom" not found
|
||||
sftp> ls ..
|
||||
remote readdir("/home/../"): Permission denied
|
||||
|
||||
## DENIAL 2 — reach demo-felhom's and demo-hp's production repo paths
|
||||
Connected to u629488.your-storagebox.de.
|
||||
sftp> cd /home/felhom-demo-felhom
|
||||
stat remote: No such file or directory
|
||||
sftp> ls /felhom-demo-felhom
|
||||
Can't ls: "/felhom-demo-felhom" not found
|
||||
sftp> cd /home/felhom-demo-hp
|
||||
stat remote: No such file or directory
|
||||
sftp> ls /felhom-demo-hp
|
||||
Can't ls: "/felhom-demo-hp" not found
|
||||
|
||||
## POSITIVE CONTROL (STRENGTHENED) — sub4 must be able to WRITE and READ BACK in its own home,
|
||||
## proving the credential is fully functional and the denials above are AUTHORIZATION, not a dud login.
|
||||
Connected to u629488.your-storagebox.de.
|
||||
sftp> put /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0d68ad3e-5c39-4852-8e38-9c029dff2562/scratchpad/c10-canary.txt c10-canary.txt
|
||||
Uploading /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0d68ad3e-5c39-4852-8e38-9c029dff2562/scratchpad/c10-canary.txt to /home/c10-canary.txt
|
||||
sftp> ls -l
|
||||
-rw-rw-r-- ? u629488-sub4 1059 56 Jul 31 22:16 c10-canary.txt
|
||||
sftp> get c10-canary.txt /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0d68ad3e-5c39-4852-8e38-9c029dff2562/scratchpad/c10-canary-readback.txt
|
||||
Fetching /home/c10-canary.txt to /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0d68ad3e-5c39-4852-8e38-9c029dff2562/scratchpad/c10-canary-readback.txt
|
||||
sftp> rm c10-canary.txt
|
||||
Removing /home/c10-canary.txt
|
||||
sftp> ls -l
|
||||
readback content: campaign10 write-capability canary 2026-07-31T20:16:23Z
|
||||
|
||||
## VERDICT: write+read succeed in own home; every production path is outside the chroot
|
||||
## ('No such file or directory') and the parent directory is an explicit 'Permission denied'.
|
||||
@@ -0,0 +1,20 @@
|
||||
# R-156 evidence ��� papra persists OUTSIDE the volume the compose mounts
|
||||
## 1. compose mounts papra_data at /app/data
|
||||
volume papra_papra_data -> /app/data
|
||||
|
||||
## 2. the container runs as nonroot and CANNOT write there
|
||||
uid=999(nonroot) gid=999(nonroot) groups=999(nonroot)
|
||||
drwxr-xr-x 2 root root 4096 Jul 31 23:18 /app/data
|
||||
touch: cannot touch '/app/data/.w': Permission denied
|
||||
## 3. the volume is empty apart from the canary this campaign wrote AS ROOT
|
||||
total 12
|
||||
drwxr-xr-x 2 root root 4096 Jul 31 23:18 .
|
||||
drwxr-xr-x 1 nonroot nonroot 4096 Jul 31 23:12 ..
|
||||
-rw-r--r-- 1 root root 19 Jul 31 23:18 cc_proof.txt
|
||||
## 4. the REAL database is in the container writable layer, not in any volume
|
||||
total 472
|
||||
drwxr-xr-x 1 nonroot nonroot 4096 Jul 31 23:12 .
|
||||
drwxr-xr-x 1 nonroot nonroot 4096 Jul 4 22:19 ..
|
||||
-rw-r--r-- 1 nonroot nonroot 475136 Jul 31 23:12 db.sqlite
|
||||
## 5. healthcheck only probes the HTTP port, so this reads healthy
|
||||
"healthy"
|
||||
@@ -0,0 +1,156 @@
|
||||
# Campaign 10 — Phase A journal (2026-07-31)
|
||||
|
||||
All times CEST unless marked Z.
|
||||
|
||||
## Venue
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Host | `demo-hp` (HP t740), Tier 0, the designated drill/build host |
|
||||
| VM | **311 `c10-appliance`** — q35 / OVMF (`pre-enrolled-keys=0`), 4 cores, 8 GB, cpu=host |
|
||||
| Disks | `scsi0` 200 G (system) · `scsi1` 50 G · `scsi2` 50 G — all qcow2 on `c10-scratch` |
|
||||
| Storage | **`c10-scratch`**, `dir` at **`/mnt/nvme-1tb` — the mount ROOT**, `content=images`. Root chosen deliberately: a storage at a *subdirectory* sets `exactMount=false` → `reachable=false` → `StorageStateDisconnected` (`felhom-agent/internal/storage/observe.go:336`), which would emit `storage_disconnected` for demo-hp for the whole run — the exact signal I1/I2 test. Coexists with `felhom-backup` on the same path; that storage was **not modified**. |
|
||||
| Guest network | demo-hp's vmbr0 is `192.168.0.87/24`, a **different physical LAN** from DooPlex's own 192.168.0.0/24. Reached via `ssh -J demo-hp`. |
|
||||
| Console | no browser on DooPlex → `qm monitor screendump` → PPM → PNG, read visually; keystrokes via `sendkey` |
|
||||
| Untouched | `drill-r50` (VM 300), guest 9201 on both demo boxes, `local-lvm`, both backup targets |
|
||||
|
||||
## Baselines — every value read fresh at the start of this run
|
||||
|
||||
| What | Value | How read |
|
||||
|---|---|---|
|
||||
| controller `main` | **v0.188.0** | `CHANGELOG.md` top; `HEAD`=`origin/main`=`4115e88`, tree clean |
|
||||
| **golden's baked controller** | **0.188.0** (sha `7353d8be…`) | hub `/configuration`, *selected* option |
|
||||
| agent published + vouched | **0.119.0** (sha `e37aca82…`) | hub `/configuration`, *selected* option |
|
||||
| agent on both demo boxes | 0.119.0 | `felhom-agent --version` |
|
||||
| agent `main` | v0.119.0 | `felhom-agent/CHANGELOG.md` |
|
||||
| hub | **0.86.0** | live `deploy/hub` image tag == `hub/CHANGELOG.md` top |
|
||||
| ISO | **1.26.1**, sha256 `f3cc86d5…`, 1 705 322 496 B | live round trip from `https://iso.felhom.eu/` |
|
||||
| `felhom.eu` HEAD | `e9a74a0`, clean | `git rev-parse` |
|
||||
| managed floor | v0.156.0 (DB override); env fallback v0.120.0 | hub `/configuration` |
|
||||
|
||||
**Golden vs `main`: NOT behind — both 0.188.0.** The deliberate decision the brief asks for is therefore
|
||||
vacuous this run: the campaign tests what is shipping *and* what is current, with no divergence.
|
||||
|
||||
**Two brief assumptions were stale and were corrected by reading fresh:**
|
||||
|
||||
1. The brief was written against ISO **v1.25.0**; `iso.felhom.eu` began serving **1.26.1** at 18:37 Z
|
||||
the same day — ~90 minutes before this run started.
|
||||
2. The brief (and `CLAUDE.md`, `target-selection.md:41-42`) say demo-hp has **no baked SSH key**.
|
||||
`ssh -o BatchMode=yes demo-hp` authenticated **by key, first try**. Already filed as **R-129** by the
|
||||
2026-07-31 tester-gate run — **not re-filed** (register grep per the fences).
|
||||
|
||||
## A1 — build the appliance from the published ISO
|
||||
|
||||
**1.26.1 is a PUBLIC RELEASE image and cannot auto-install.** Verified against the ISO bytes on
|
||||
demo-hp, not inferred from the manifest:
|
||||
|
||||
| Probe | Hits |
|
||||
|---|---|
|
||||
| `auto-installer-mode.toml` | **0** |
|
||||
| `answer.toml` | **0** |
|
||||
| `proxmox-auto-install` | **0** |
|
||||
| `felhom-bootstrap_1.26.1_all.deb` | 1 |
|
||||
| Felhom grub menuentries | exactly **2**, both interactive |
|
||||
|
||||
Manifest agrees: `mode: release … NO answer.toml, NO baked credential, interactive disk selection`,
|
||||
`root-password: NONE — the installer prompts`, `loader: shim (stock MS-signed chain)`.
|
||||
|
||||
Driven blind through the **Terminal UI** entry (the graphical entry is not drivable — the QEMU monitor's
|
||||
mouse events do not move the guest cursor, per `REPORT-iso-release.md`). Screendump before every
|
||||
keystroke.
|
||||
|
||||
**Traps hit and handled, each verified rather than assumed:**
|
||||
|
||||
- **GRUB default is the graphical entry.** The first attempt auto-booted it because the `down` keystroke
|
||||
landed after the 15 s countdown expired. Reset, and sent `down` inside the window in one round trip.
|
||||
- **Guest keymap was Hungarian**, but `sendkey` emits US scancodes — every symbol afterwards (notably
|
||||
`@`, which is AltGr+V on a Hungarian layout) would have been silently mangled, *including the root
|
||||
password*, locking the box out. Switched to **U.S. English** before typing anything. Confirmed
|
||||
positively: `c10@felhom.eu` rendered with a correct `@` and `.`.
|
||||
- **Disk-selection safety:** only `scsi0` was attached during the install, so the installer offered
|
||||
exactly one target (`/dev/sda`, 200 GiB) and could not reach a data drive. The two 50 G data drives
|
||||
were attached afterwards.
|
||||
- **Boot-order trap** (`REPORT-iso-release.md`, recorded twice there): `--boot` was set in its **own**
|
||||
`qm set` after the disks existed, and verified from `qm config` (`boot: order=ide2;scsi0`). After the
|
||||
install, the order was flipped to `order=scsi0` — again verified from `qm config`, not from the screen.
|
||||
|
||||
**Real completion signal** (not "an artifact changed"): 4.2 GB actually written to
|
||||
`vm-311-disk-1.qcow2`, the installed system booted on its own, and the console showed the PVE banner at
|
||||
`https://192.168.0.105:8006/`.
|
||||
|
||||
**Positive observables — all three present:**
|
||||
|
||||
| Observable | Evidence |
|
||||
|---|---|
|
||||
| package delivered | `ii felhom-bootstrap 1.26.1 all` on the box |
|
||||
| unit fired at first boot | `systemctl is-enabled felhom-bootstrap` = `enabled`; `is-active` = `activating` |
|
||||
| registers as an unclaimed appliance | hub `/hosts`: uuid `55ab3776-…` (matches VM smbios), pairing code **FD6-A6S**, MAC `bc:24:11:46:25:fe`, 3 SSH host keys, hardware read |
|
||||
|
||||
**A1 — PASS.**
|
||||
|
||||
## A2 — claim
|
||||
|
||||
Customer created: **`c10-soak`** — "Campaign 10 two-storage soak (SCRATCH - delete at teardown)",
|
||||
domain `c10.felhom.eu`, email `nagyfenyvesi.viktor@gmail.com`. Deliberately **not** `drill-r50`,
|
||||
`sess-*`, or any existing record.
|
||||
|
||||
Appliance 19 bound to it. Hub log, verbatim:
|
||||
|
||||
22:50:03 appliance 19 BOUND to customer c10-soak (mode=appliance) — delivery staged for its next poll
|
||||
22:50:12 appliance credentials DELIVERED once to appliance 19 (passphrase withheld)
|
||||
22:50:19 [claim] claim code (gen 1) emailed to the registered address of c10-soak
|
||||
22:50:49 host enrolled: c10-soak-8a405e (customer c10-soak)
|
||||
22:50:49 vaulted break-glass recovery credential for host c10-soak-8a405e (user=root@pam, secret 32 chars)
|
||||
22:50:51 Artifact manifest served for customer c10-soak (agent=0.119.0 golden=0.188.0)
|
||||
22:51:13 wg registered: host=c10-soak-8a405e ip=10.77.0.6/32 gen=1 sync=ok
|
||||
22:51:14 host-report from c10-soak-8a405e (0 guests, 2 storage targets, ...)
|
||||
22:51:14 DR-recipe host-half stored for customer c10-soak (host c10-soak-8a405e, v1)
|
||||
|
||||
**The installer's root password stops working at 22:50:49** — day-0 replaces it with a hub-vaulted
|
||||
managed credential (G1 `host_recovery`). Retrieved via `POST /hosts/c10-soak-8a405e/reveal-recovery-credential`
|
||||
→ `root@pam`, 32 chars, stored file→file at `~/.config/campaign10/host-recovery.json` (0600), never printed.
|
||||
|
||||
Box state after day-0:
|
||||
|
||||
felhom-agent 0.119.0 (== published+vouched)
|
||||
/etc/felhom-controller-image : …/felhom-controller:0.188.0 (== golden == main)
|
||||
guest 9201 c10-soak running
|
||||
docker: controller 0.188.0 (healthy), traefik v3.6.7, filebrowser 1.3.3-stable (healthy)
|
||||
block devices: sda 200G, sdb 50G, sdc 50G
|
||||
|
||||
Claim gate, the positive discriminator, read over the path the UI uses (traefik on 443, `Host:` header
|
||||
mandatory — the controller does **not** listen on 8080; 80 is a 301 to HTTPS):
|
||||
|
||||
GET https://127.0.0.1/api/apps Host: felhom.c10.felhom.eu
|
||||
→ {"ok":false,"error":"dashboard not yet claimed"}
|
||||
|
||||
**A2 — BLOCKED on the emailed claim code (R-119, confirmed live again).** The code exists only in the
|
||||
customer's mailbox; there is no operator-side route.
|
||||
|
||||
## Scope constraint found in Phase A — Tier 3 cannot be isolated, so it will not run
|
||||
|
||||
The brief's §2 requires the campaign's PBS to be *a dedicated datastore and namespace on DooPlex*. That
|
||||
is not reachable, for two reasons that are each **already-recorded deliberate positions**, not new defects:
|
||||
|
||||
1. **Offsite hard-requires the DR tier.** `hub/internal/web/configs.go:1300` refuses offsite without it:
|
||||
*"Offsite backup requires the DR tier — enable it first (the escrow ceremony depends on the PBS key)"*.
|
||||
Closed by policy 2026-07-12 (drill F-6, DR-tier-by-default decisions 3/4).
|
||||
2. **The DR tier only ever provisions on ep0.** Peer allocation and endpoint sync use the lowest
|
||||
`endpoint_id` only; per-endpoint allocation is an explicitly deferred arc (`hub/README.md:260`).
|
||||
|
||||
So the only two configurations are: *DR tier on* → the campaign's PBS traffic lands on **ep0**, which is
|
||||
Tier 2, the offsite of last resort, RAM-constrained (3.8 GB, R-90) and fenced by §3 — or *DR tier off* →
|
||||
no Tier 3 at all. **Chosen: DR tier OFF, offsite OFF**, which is the only option §3 permits.
|
||||
|
||||
Consequence, stated plainly rather than discovered later: the campaign touches **neither ep0 nor the
|
||||
Storage Box at any point** — a stronger isolation than the brief asked for, obtained by not running the
|
||||
tier. What is lost: all Tier-3 operations in B1, **I8** entirely, and the Tier-3 rows of the RTO/RPO
|
||||
table. Everything else — the two-drive core (R-112/R-113/R-114/R-116/R-117), D5, I1–I7 and I9–I11 — is
|
||||
unaffected, because those live on the drives and the local/whole-guest tiers.
|
||||
|
||||
**No `R-n` minted**: per the fences, the register was grepped first and both halves are already recorded
|
||||
(F-6 policy close; the `hub/README.md:260` deferral). This is a campaign constraint, not a finding.
|
||||
|
||||
## Isolation gate — §2
|
||||
|
||||
See `../isolation/pbs-denial.txt` and `../isolation/restic-denial.txt`. Summary in the main audit.
|
||||
Reference in New Issue
Block a user