sage: row 176 brief adds the credentials-body and project-root tests (mos-0878 Q1); row 178 brief rewritten for mos-0878 Q2: partial fix after the window rows, console-proof.token slot, ops-01 SealedSecret and middleware under R27, console-allowed-uid recorded as decision 101's uid and left alone
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Brief: the Console checks that a signed-in request came through Traefik
|
||||
|
||||
Vikunja #385, Gitea #1676. Owner and reviewer are set at briefing. Gate owner Sage. Not on the window path (mos-0874).
|
||||
Vikunja #385, Gitea #1676. Owner Rocko or whichever seat is free, set at briefing. Gate owner Sage. Not on the window
|
||||
path (mos-0874). Built after the window rows 169, 175 and 176, as a partial fix with the limit recorded (mos-0878 Q2).
|
||||
Line numbers are at origin/refactor 835941b7c.
|
||||
|
||||
## The hole
|
||||
@@ -42,32 +43,48 @@ signed-in request without the right value.
|
||||
|
||||
### What this stops and what it doesn't
|
||||
|
||||
The proof stops any process that can't read the proof file: another uid on the host, a hostNetwork pod on the
|
||||
k8s-woltje node (Cilium `AllowLocalhost=always`), and a sandboxed worker, since `run-task.sh` workers get no host
|
||||
home.
|
||||
The proof stops any process that can't read the proof file: another uid on the host, and a hostNetwork pod on the
|
||||
k8s-woltje node (Cilium `AllowLocalhost=always`). It also stops a `run-task.sh` worker if its sandbox hides the host's
|
||||
`~/.config`. The build checks that and records it in the packet, without assuming it.
|
||||
|
||||
It doesn't stop a process running as the Console's own uid. The Console runs as a `systemctl --user` unit under
|
||||
jwoltje, and every T3 seat runs as jwoltje too. A seat can read any file the Console can read, and `ptrace_scope` 1
|
||||
doesn't stop that. Closing that part means the Console runs under a uid the seats don't have, which is a host and
|
||||
service change, so it's Jason's (Q173: infra). This row doesn't make that change. The brief records it, and the issue
|
||||
stays open until Jason rules on it.
|
||||
service change, so it's Jason's (Q173: infra). This row doesn't make that change. Agents acting as Jason only matter
|
||||
once the stack is in use, so uid separation is a question for Jason before he says "in use". It doesn't hold up the
|
||||
window. #385 stays open for his ruling (mos-0878 Q2).
|
||||
|
||||
### Jason's steps, not this row's
|
||||
### `console-allowed-uid` isn't a proof
|
||||
|
||||
- The secret's home. A file outside git, mode 0600, outside every repo and copy, written by Jason. Its path goes in
|
||||
the unit through `--proof-file`. No agent writes or reads it, and it never goes in a message.
|
||||
- The Traefik middleware that sets the header on the Console's route (`headers.customRequestHeaders`, which replaces
|
||||
a client copy). That's k3s, so it's Jason's. The value has to match the file.
|
||||
- Turning it on in the live unit. That changes who can reach the Console from outside (it narrows it), so it waits
|
||||
for Jason under Q173. The order is middleware first, then unit flags, then a signed-in check. Rollback drops the
|
||||
unit flags.
|
||||
`~/.config/mosaic-dev/secrets/mosaic-stack/console-allowed-uid` (0600, 65 bytes, 2026-10-12, contents not read) is the
|
||||
Authentik uid for lead decision 101's switch from `--auth-header X-authentik-username --allow-user jason.woltje` to
|
||||
`--auth-header X-authentik-uid --allow-user UID`. No code in the repo reads it. It's an operator input for that
|
||||
reinstall (`scripts/webui-service.sh:145-146`, `docs/TOOLS.md:516`), and the live unit still signs in by username.
|
||||
It's an identity, not a secret the proxy proves it holds. Once installed it sits in ExecStart, which any local process
|
||||
can read. This row neither reuses it nor retires it, and the uid switch stays a separate operator step.
|
||||
|
||||
### Activation steps, not this row's
|
||||
|
||||
1. The secret. A slot file, `~/.config/mosaic-dev/secrets/mosaic-stack/console-proof.token`, mode 0600, 43 or more
|
||||
characters from `[A-Za-z0-9_-]` (for example 32 random bytes in base64url). Under Q173 an agent may create it,
|
||||
as long as the value is never printed, logged, put on argv or sent in a message (mos-0878 Q2). Create it with
|
||||
`umask 077` and write it in one step, then check only `stat -c '%a %U %s'`.
|
||||
2. Traefik. A SealedSecret holding the same value, and a middleware on the Console's route in the mosaic-preview Argo
|
||||
app that sets the header (`headers.customRequestHeaders`, which replaces a client copy). That's ops-01's under R27.
|
||||
It narrows who gets in and opens nothing new from outside (mos-0878). Sealing the value means handing it to ops-01
|
||||
without printing it. The activation plan names that path, and Mos checks it before anything runs.
|
||||
3. The unit. After a store backup, add `--proof-header NAME --proof-file <slot path>` to the Console unit with
|
||||
`webui-service.sh install` and a restart, in a window Mos plan-checks. The order is middleware first, then the
|
||||
unit flags, then a signed-in check through the public name and a forged loopback request that has to get 401.
|
||||
Rollback drops the unit flags first, then the middleware. A STATE report follows. This step narrows who can reach
|
||||
the Console from outside, and Q173 keeps any change to that for Jason. Mos rules at the plan check whether
|
||||
narrowing counts. Until then it waits for Jason.
|
||||
|
||||
## Tests
|
||||
|
||||
1. A named request with the right proof and auth header passes, and the reads and the send path still work as before.
|
||||
2. A named request with no proof, a wrong proof, a proof one byte short, a repeated proof header, or the proof in
|
||||
another case of header name gets 401 with no bus call and no board call. Assert that the body never contains the
|
||||
value.
|
||||
2. A named request with no proof, a wrong proof, a proof one byte short, or a repeated proof header gets 401 with no
|
||||
bus call and no board call. Assert that the body never contains the value.
|
||||
3. NAME with a loopback Host or any other Host refuses 403. NAME on the row 176 tester listener refuses 403.
|
||||
4. Start-up refusals: one flag without the other; the flags without preview mode; a missing file, a directory, a
|
||||
symlink, mode 0644, another owner, a short value, a value with a newline in the middle or a bad character. Each
|
||||
@@ -90,5 +107,5 @@ Mutants, each killed by some gate test (mos-0862):
|
||||
## Gate
|
||||
|
||||
The reviewer approves the candidate manifest. Webui and cli suites and every `scripts/test-*.sh` green on Sage's
|
||||
gate. Landing changes nothing live. The live unit gets the flags only in a window Jason runs or approves, after the
|
||||
gate. Landing changes nothing live. The live unit gets the flags only through the activation steps above, after the
|
||||
middleware.
|
||||
|
||||
@@ -104,9 +104,18 @@ move, stop and tell Sage. Before in-review, grep the tests for each changed path
|
||||
403 and no bus call.
|
||||
7. With `bus.tester` off, the tester listener's reads answer 403 `tester-off`.
|
||||
8. A tester reads `credentials`, `routes`, `project.files` and `project.file`, and still refuses `project.file.save`.
|
||||
9. `webui-service.sh render --tester-port 7333` puts it in ExecStart, and refuses 7332, the main port and bad values
|
||||
9. The tester's `credentials` read never contains a token, a file path or a variable name (mos-0878 Q1). Give the
|
||||
fixture's credential a known token value, a known file path and a known variable name. Assert that each string is
|
||||
absent from the whole response body, through the broker and through the tester listener. Checking that the fields
|
||||
are left out isn't enough.
|
||||
10. A tester's `project.file` read stays inside the project root (mos-0878 Q1). Reads go through `git ls-tree` at a
|
||||
commit, `documentPath` limits the path (`packages/project/src/paths.mjs:14`, `read.mjs:70`), and `blob()` takes
|
||||
regular blobs only (`read.mjs:21-22`). Test through the tester path that `docs/../../x.md`, an absolute path and
|
||||
`docs/%2e%2e/x.md` refuse. Test that a committed `docs/out.md` symlink pointing outside the repo refuses
|
||||
`project-read-content`, and returns none of its target's bytes.
|
||||
11. `webui-service.sh render --tester-port 7333` puts it in ExecStart, and refuses 7332, the main port and bad values
|
||||
(exit 4). `stack-release.sh` keeps `--tester-port 7333` across a re-pin and a rollback.
|
||||
10. A browser test (the webui browser harness) opens the tester listener, sees the tester banner, changes an
|
||||
12. A browser test (the webui browser harness) opens the tester listener, sees the tester banner, changes an
|
||||
instance's route, and the change lands as tester.
|
||||
|
||||
Mutants, each killed by some gate test (mos-0862):
|
||||
@@ -117,6 +126,8 @@ Mutants, each killed by some gate test (mos-0862):
|
||||
- `--tester-port 7332` accepted
|
||||
- the tester listener accepts the main listener's auth header
|
||||
- `credentials` dropped from the tester reads, or `project.file.save` let through for a tester
|
||||
- the credentials result passes `#secretCheck` but carries the fixture's file path
|
||||
- `blob()` accepts mode 120000
|
||||
|
||||
## Gate
|
||||
|
||||
|
||||
Reference in New Issue
Block a user