---ci--- project: orca phase: 0 milestone: v0.12 status: specify ---/ci--- Threat-model review of entire surface incl OS (25 findings F1..F25). Adopts R-021 (no Orca credentials: human=OIDC, machine=mTLS/SPIFFE). Bundled Dex + WebAuthn (passkeys) as default password-free authenticator. Master key seal-to-OIDC + Shamir 3-of-5 recovery. 30 net-new requirements (REQ-119..REQ-148). 29 phases. Binding conditions C-29..C-38.
9.3 KiB
Grill: v0.12 Security Hardening (Zero-Trust Identity) — Phase 0 Adversarial Review
Status: PROCEED-WITH-CONDITIONS. The v0.12 plan is sound; 10 binding conditions (C-29..C-38) gate specific phases. The plan adopts R-021 (no Orca credentials) and D-238..D-247 from the threat-model review + operator decisions. The grill reviewed the plan adversarially across the same 9 axes as GRILL_v0.9/v0.11 (vision, feasibility, scope, risk, security, operational, cost, competitive, exit).
Forcing questions + verdicts
FQ1 — R-021 is the largest behavioral change in project history
Question: R-021 ("no Orca credentials") removes all password and
token surfaces. P07 is explicitly breaking. Is the migration path
(C-34 --accept-identity-migration) sufficient, or does the breakage
extend beyond what's documented?
Verdict: BREAKAGE IS CONTAINED BUT UNDERESTIMATED. The plan
documents the Proxmox --password and step-ca --password-file
removal. But KindToken removal (P06) also breaks any existing
acl.json that uses token identities. The migration must rewrite
acl.json entries, not just refuse them.
Binding condition C-29 (refined): P22 (orca upgrade) MUST
detect v0.11 acl.json entries with KindToken and either (a)
refuse without --accept-identity-migration + a documented
re-mapping, or (b) auto-stub them as KindOidc with a placeholder
sub requiring operator confirmation. No silent data loss.
Confidence: 0.90
FQ2 — P08 master key seal is the riskiest phase
Question: A bug in seal/unseal corrupts all secrets at rest. Is the recovery path (Shamir 3-of-5) actually testable, and does it handle the "IdP lost AND shards partially lost" case?
Verdict: RECOVERY IS TESTABLE BUT THE EDGE CASES ARE UNDERTESTED. The plan covers the happy path (3-of-5) and the failure case (< 3 shards -> unrecoverable). But the "IdP lost, 3 shards available, but the OIDC-derived salt was also lost" case (the salt is in the sealed blob, so this shouldn't happen -- but verify) needs an explicit test.
Binding condition C-30 (refined): P08 MUST include a test that recovers with 3-of-5 shards AFTER the IdP is simulated-down (seal key reconstruction from shards, NOT from OIDC token). The sealed blob must contain the salt (so recovery doesn't need the IdP). Document that the salt is stored in the sealed blob, not derived from the token at recovery time.
Confidence: 0.88
FQ3 — P05 WebAuthn connector feasibility
Question: The custom Dex connector (~300 LoC) is new ground. Is
the go-webauthn library mature enough, and does the RP ID / secure
context requirement create a chicken-and-egg problem (Dex needs
Traefik, Traefik needs the cert, the cert needs step-ca, step-ca
needs the operator authenticated -- by Dex)?
Verdict: NO CHICKEN-AND-EGG, but the bootstrap sequence must be explicit. The cert comes from step-ca's OIDC provisioner (P07), but the FIRST operator must authenticate to step-ca. Resolution: the first operator uses the mTLS-only path (cluster CA cert, held offline) to mint the first Traefik cert. Dex then comes up. The first WebAuthn registration happens via that first cert. The chicken- and-egg is resolved by the mTLS-only bootstrap path.
Binding condition C-31 (new): P04/P05 MUST document the bootstrap
sequence: (1) orca init bootstraps the cluster CA (step-ca, mTLS-
only), (2) orca auth init-idp deploys Dex behind Traefik using the
step-ca cert, (3) the first operator registers a passkey via the
mTLS-authenticated session, (4) subsequent operators use WebAuthn.
The mTLS-only path is the bootstrap escape hatch.
Confidence: 0.87
FQ4 — P21 SQLite encryption CGO risk
Question: SQLCipher needs CGO (breaks D-008 cross-compile). The C-31 fallback is "file-mode 0600 + documented threat." Is that acceptable for a security-hardening milestone?
Verdict: FALLBACK IS ACCEPTABLE BUT MUST BE EXPLICIT. The threat-model finding (F8) is "DBs unencrypted with no explicit file mode." The minimum fix (0600 file mode) closes the "no explicit mode" half. The "unencrypted" half is a documented residual risk if CGO is infeasible. This is consistent with the project's "no CGO" invariant (D-008) which is load-bearing for cross-compile.
Binding condition C-32 (refined): P21 MUST evaluate at least one CGO-free encryption option (e.g., application-level AES-GCM envelope around the SQLite file, or a FUSE encryption layer). If all are infeasible or too complex for v0.12, document the decision + residual risk. The fallback is file-mode 0600 only. No CGO.
Confidence: 0.85
FQ5 — Phase count (29) vs. sizing
Question: 29 phases is the largest milestone in project history (v0.11 was 24, v0.9 was 14). Is any single phase too large to ship atomically?
Verdict: TWO PHASES ARE LARGE. P04 (OIDC+Dex) and P08 (master key seal) are each ~500-700 LoC + tests. They're within the v0.11 P10a/P10b sizing that the grill previously accepted, but the grill split P10. If P04 or P08 grows during execution, the EXECUTE workflow may split them (P04a/P04b, P08a/P08b).
Binding condition C-33 (new): P04 and P08 are SPLIT CANDIDATES. If either exceeds ~700 LoC + tests during EXECUTE, split: P04a (OIDC client) / P04b (bundled Dex deploy); P08a (seal/unseal + Shamir) / P08b (CLI + mTLS-only path). The planner monitors LoC during execution.
Confidence: 0.82
FQ6 — C-32 human gate (leaked GITEA_TOKEN) could stall the final ship
Question: If the operator doesn't rotate the token, P28 can't ship. Is there an escalation path that doesn't block the milestone?
Verdict: ESCALATION PATH EXISTS. Ship as v0.11.28-rc1 (release
candidate) if the token is not rotated by P28. The v0.11.28 final
tag (milestone release) waits for confirmation. The milestone is
"complete" (all phases shipped); only the final tag is gated.
Binding condition C-34 (refined): C-32 human-gate: if the
GITEA_TOKEN is not rotated by P28, ship v0.11.28-rc1 (all phases
complete, release notes flag the pending rotation). The v0.11.28
final tag is cut when the operator confirms. The ---ci--- block
records escalation: type=release_pending resolution=auto -- does
not halt the pipeline.
Confidence: 0.90
Adopted binding conditions (C-29..C-38)
| ID | Condition | Phase | Confidence |
|---|---|---|---|
| C-29 | P23 (dual-write closure) gated on P06/P08/P09/P11 all shipped. P22 must detect v0.11 acl.json KindToken entries and refuse/remap without --accept-identity-migration. |
P22/P23 | 0.90 |
| C-30 | P14 (master key rotation) reversible; --dry-run mandatory; auto-rollback to old sealed key on any ns failure. |
P14 | 0.88 |
| C-31 | P21 (SQLite encryption): evaluate at least one CGO-free option (app-level AES-GCM envelope, FUSE layer). If infeasible, file-mode 0600 + documented residual risk. No CGO. | P21 | 0.85 |
| C-32 | Human-gate: leaked GITEA_TOKEN (F17) rotated + .env re-seeded before v0.11.28 final tag. If not rotated by P28, ship v0.11.28-rc1. History-scrub best-effort, non-blocking. Escalation hook in ---ci---. |
P28 | 0.90 |
| C-33 | P26 (security integration tests) in .coreci.yml validate, gates merges -- not opt-in. |
P26 | 0.95 |
| C-34 | P07 (password/token removal) breaking. orca upgrade (P22) refuses v0.11 clusters using --password/bare-tokens/KindToken without --accept-identity-migration. No silent breakage. |
P07/P22 | 0.90 |
| C-35 | P08 (Shamir recovery): 3-of-5 shards printed at seal time, operator stores offline. Sealed blob contains the salt (recovery doesn't need the IdP). If IdP lost AND < 3 shards -> unrecoverable by design (documented residual risk). No backdoor. Test recovery with IdP-down. | P08 | 0.88 |
| C-36 | OIDC client secret (confidential clients) at ClusterDir()/oidc-client-secret (0600), rotatable via orca auth rotate-client-secret, never committed. Public PKCE clients avoid even this. |
P04 | 0.92 |
| C-37 | P04/P05 (bundled Dex + WebAuthn): document the bootstrap sequence (mTLS-only first cert -> Dex -> first passkey). The mTLS-only path is the bootstrap escape hatch. If WebAuthn proves infeasible, bundled Dex ships mTLS-client-cert-only (C-37 fallback). The "no Orca credentials" invariant holds regardless. | P04/P05 | 0.87 |
| C-38 | P05 (WebAuthn): RP ID must match the cluster's Traefik-served domain; orca auth init-idp configures it. HTTPS secure context via Traefik (step-ca cert). P26 integration tests use the WebAuthn virtual-authenticator API -- no hardware key required in CI. |
P05 | 0.90 |
Verdict: PROCEED-WITH-CONDITIONS
The v0.12 plan is sound. The 10 binding conditions gate the risky phases. The 29-phase count is within the operator's "more than 20 if warranted" guidance. The plan adopts R-021 (no Orca credentials) and D-238..D-247. The grill does NOT recommend REPLAN.
Phase challenges (PC-01..PC-05)
| ID | Challenge | Phase |
|---|---|---|
| PC-01 | P04/P08 are split candidates if LoC exceeds ~700 (C-33) | P04/P08 |
| PC-02 | P07 breaking change -- migration must handle KindToken acl.json entries, not just passwords (C-29/C-34) |
P07/P22 |
| PC-03 | P05 WebAuthn bootstrap sequence must be explicit (mTLS-only first cert) | P04/P05 |
| PC-04 | P21 SQLite encryption CGO evaluation -- document the decision + residual risk if fallback | P21 |
| PC-05 | C-32 human gate -- v0.11.28-rc1 escalation if token not rotated |
P28 |