cf0df0f157
---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.
396 lines
22 KiB
Markdown
396 lines
22 KiB
Markdown
# Plan v0.12: Security Hardening (Zero-Trust Identity)
|
|
|
|
**Status**: Phase 0 plan. 29 phases (P0 + P01..P27 + P28 final). Wave
|
|
ordering, persona assignments, and binding conditions. GRILL will
|
|
pressure-test and may split/merge.
|
|
|
|
## Milestone identity
|
|
|
|
- **Label**: `v0.12-security-hardening`
|
|
- **Type**: feature (P04, P05 ship `feat`)
|
|
- **Tag line**: v0.11.x patches (`v0.11.0`..`v0.11.28`)
|
|
- **Final phase patch** = milestone release = `v0.11.28` (no separate `v0.12.0`)
|
|
- **Branch**: `milestone/v0.12-security-hardening`
|
|
- **v1.0.0**: deferred for post-v0.12 UAT (per v0.11 PRD)
|
|
|
|
## Wave ordering
|
|
|
|
Waves are dependency-ordered. Within a wave, phases run in sequence
|
|
(parallelization disabled per config.json `parallelization.enabled=false`).
|
|
|
|
### Phase 0 — Pre-execution (all personas, lead-developer coordinates)
|
|
|
|
Stages: SPECIFY -> CLARIFY -> RESEARCH -> IDEATE -> PLAN -> GRILL -> SHIP.
|
|
|
|
- SPECIFY: v0.12 in config.json + PROJECT.md (done).
|
|
- CLARIFY: D-238..D-247 (done, CLARIFY_v0.12.md).
|
|
- RESEARCH: threat model F1..F25 + zero-trust identity model (done,
|
|
RESEARCH_v0.12.md). Resolves RQ-1 (WebAuthn as password-free upstream).
|
|
- IDEATE: 30 ideas accepted -> REQ-119..REQ-148 (done, IDEATION_v0.12.md).
|
|
- PLAN: this document.
|
|
- GRILL: ratify C-29..C-38, split/merge as needed.
|
|
- SHIP: tag `v0.11.0`.
|
|
|
|
**Commit**: `docs(P00): v0.12 security-hardening phase 0 (specify/clarify/research/ideate/plan/grill)`
|
|
|
|
### Wave A — Critical injection & traversal (backend-engineer)
|
|
|
|
Vertical slice: stop the bleeding first. Three independent fixes, no
|
|
inter-dependencies.
|
|
|
|
#### P01 — Command injection (podman/wasm) — REQ-119, F3
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/runtime/`)
|
|
- **Tasks**:
|
|
1. Add `shellQuote` helper (or use `golang.org/x/crypto/ssh`-safe quoting) to `internal/runtime/`.
|
|
2. Fix `podman.go:57`: `fmt.Sprintf("podman run -d --name %s %q %s", name, image, shellQuote(cmdStr))`.
|
|
3. Fix `wasm.go:39`: same pattern for `wasmtime run`.
|
|
4. Add Go regression tests: `;`, `|`, `$()`, backticks, newline, `$IFS`, `<>()` injection attempts.
|
|
5. Add bats test: a jobspec with a malicious command runs the literal command, not the injected shell.
|
|
- **Must-haves**: all injection tests pass; existing podman/wasm tests still pass.
|
|
- **Commit**: `fix(P01): command injection in podman/wasm runtimes (REQ-119, F3)`
|
|
- **Tag**: `v0.11.1`
|
|
|
|
#### P02 — Namespace path traversal — REQ-120, F4
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/ns/`, `internal/cli/ns.go`)
|
|
- **Tasks**:
|
|
1. Add `validateNamespaceName(name)` to `internal/ns/`: reject `..`, `/`, leading `-`, null bytes, control chars, empty, length > 128.
|
|
2. Wire into `ns create`, `ns inherit`, `ns set-constraint`, and any path-accepting ns command.
|
|
3. Add Go fuzz test (`FuzzValidateNamespaceName`).
|
|
4. Add regression test: `orca ns create "../../etc"` fails with a clear error.
|
|
- **Must-haves**: fuzz test passes 10k iterations; `..`/`/`/null rejected.
|
|
- **Commit**: `fix(P02): namespace path traversal (REQ-120, F4)`
|
|
- **Tag**: `v0.11.2`
|
|
|
|
#### P03 — Txn apply path allowlist — REQ-121, F5
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/txn/`)
|
|
- **Tasks**:
|
|
1. In `txn.go:renderApplyScript`, add path validation to the python heredoc: every `path` in `desired-state.json` must match a prefix in the allowlist (`/etc/orca/`, `/etc/traefik/orca*`, `/etc/systemd/system/orca-*`, `/etc/nftables.d/orca*`, `/etc/syncthing/orca*`).
|
|
2. Reject with a clear error + exit code on mismatch.
|
|
3. Add Go test: a desired-state with `"path": "/etc/shadow"` is rejected.
|
|
4. Add bats test: `orca-pull.sh` with a crafted manifest refuses.
|
|
- **Must-haves**: arbitrary-path writes rejected; legitimate paths still apply.
|
|
- **Commit**: `fix(P03): txn apply path allowlist (REQ-121, F5)`
|
|
- **Tag**: `v0.11.3`
|
|
|
|
### Wave B — Zero-trust identity (backend-engineer + lead-developer)
|
|
|
|
The architectural foundation. P04/P05 are `feat` phases; P06/P07/P08
|
|
are `fix`/`refactor` that depend on them.
|
|
|
|
#### P04 — OIDC client + bundled Dex — REQ-144
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/cli/`, `internal/identity/`)
|
|
- **Tasks**:
|
|
1. Add `github.com/coreos/go-oidc/v3` dependency.
|
|
2. `internal/identity/oidc.go`: OIDC client (provider discovery, JWKS cache + refresh, ID token verification, token storage at `~/.orca/credentials.json` 0600).
|
|
3. `orca auth login`/`logout`/`status` CLI: browser auth-code + PKCE + local loopback redirect (`127.0.0.1:<port>/callback`); headless device-code fallback.
|
|
4. `orca auth init-idp`: bootstrap bundled Dex (systemd unit + config template + Traefik route) on the lead; `--rp-id <domain>` config.
|
|
5. OIDC config block in `internal/config/`: `oidc.issuer`, `client_id`, `client_secret`, `scopes`.
|
|
6. BYO external IdP override: `oidc.issuer` repoint bypasses bundled Dex.
|
|
7. Go tests: mock OIDC provider, JWKS rotation, token refresh, login/logout flow.
|
|
- **Must-haves**: `orca auth login` produces a valid ID token; `orca auth status` shows it; `--oidc` flag gated; offline Dex quickstart doc'd.
|
|
- **Commit**: `feat(P04): OIDC client + bundled Dex (REQ-144, D-239, D-242)`
|
|
- **Tag**: `v0.11.4`
|
|
|
|
#### P05 — WebAuthn connector for Dex — REQ-148
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/identity/`, new `internal/webauthn/`)
|
|
- **Tasks**:
|
|
1. Add `github.com/go-webauthn/webauthn` dependency.
|
|
2. `internal/webauthn/connector.go`: Dex connector (~300 LoC) -- registration + login ceremonies at `/orca/webauthn/{register,login}`.
|
|
3. `internal/webauthn/store.go`: passkey storage SQLite at `ClusterDir()/webauthn-credentials.db` (0600); schema: `credentials(user_id, credential_id, public_key, sign_count, aaguid, created_at)`.
|
|
4. `orca auth register` CLI: browser flow to register a new passkey.
|
|
5. RP ID = cluster Traefik domain (from `orca auth init-idp --rp-id`); secure context via step-ca cert (R-017).
|
|
6. Go tests using `go-webauthn` virtual-authenticator test helpers (no hardware key).
|
|
- **Must-haves**: register + login flow works end-to-end against the bundled Dex; public keys only stored; virtual-authenticator tests pass.
|
|
- **Commit**: `feat(P05): WebAuthn connector for Dex (REQ-148, D-240, C-38)`
|
|
- **Tag**: `v0.11.5`
|
|
|
|
#### P06 — ACL rewrite to OIDC claims + enforcement — REQ-145, REQ-122, F1
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/acl/`, `internal/daemon/`, `internal/sshpush/`)
|
|
- **Tasks**:
|
|
1. Remove `KindToken` from `internal/acl/acl.go` entirely.
|
|
2. Add `KindOidc`: maps `sub` + `groups` -> namespace permissions.
|
|
3. `acl.Check` takes an OIDC claims struct (or SPIFFE SVID for machine identity).
|
|
4. Wire `acl.Check` into daemon handlers (read/write/admin by route).
|
|
5. Wire `acl.Check` into SSH-push applier: validate `ORCA_OIDC_TOKEN` env var against JWKS before applying any txn.
|
|
6. `acl.json` file mode tightened to 0600.
|
|
7. Deny-by-default enforced; actor recorded in audit log.
|
|
8. Go tests: ACL-negative (unauthorized sub denied), ACL-positive, machine identity (SVID) still works.
|
|
- **Must-haves**: no request applies without a valid OIDC token or SVID; `KindToken` removed; deny-by-default enforced.
|
|
- **Commit**: `fix(P06): ACL rewrite to OIDC claims + enforcement (REQ-145, REQ-122, F1)`
|
|
- **Tag**: `v0.11.6`
|
|
|
|
#### P07 — Remove all password/token paths — REQ-146, R-021, C-34
|
|
|
|
- **Persona**: lead-developer (territory: `internal/proxmox/`, `internal/stepca/`, `internal/cli/`)
|
|
- **Tasks**:
|
|
1. Remove `--password`/`$ORCA_PROXMOX_PASSWORD` from Proxmox join (`proxmox/bootstrap.go:29`); replace with pre-staged-key-only or `step ssh` OIDC cert exchange.
|
|
2. Remove step-ca `--password-file` provisioner; migrate to OIDC provisioner (step-ca natively supports OIDC).
|
|
3. Remove any bare-token CLI paths (already removed in P06, but sweep for stragglers).
|
|
4. Add deprecation/migration docs: `--accept-identity-migration` flag on `orca upgrade` (P22 enforces).
|
|
5. Go tests: `--password` flag is rejected with a clear error pointing to the migration guide.
|
|
- **Must-haves**: no password accepted anywhere; `--password` rejected; step-ca OIDC provisioner works.
|
|
- **Commit**: `fix(P07): remove all password/token paths (REQ-146, R-021, C-34) -- BREAKING`
|
|
- **Tag**: `v0.11.7`
|
|
|
|
#### P08 — Master key seal-to-OIDC + Shamir — REQ-147, D-241, C-35
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/secrets/`, new `internal/seal/`)
|
|
- **Tasks**:
|
|
1. `internal/seal/seal.go`: seal/unseal using HKDF-SHA256 of OIDC ID token `sub` + fresh 32-byte salt; sealed blob at `ClusterDir()/master.key.sealed` (0600).
|
|
2. Shamir 3-of-5: `internal/seal/shamir.go` (using `golang.org/x/crypto/...` or a vendored Shamir impl); print 5 shards at seal time.
|
|
3. `orca cluster unseal`/`seal` CLI: unseal via OIDC auth; `--recovery` + 3 shards for IdP-lost case.
|
|
4. mTLS-only offline path: seal key derived from cluster CA.
|
|
5. Master key zeroed on shutdown (use `memguard` or manual `crypto/rand` overwrite).
|
|
6. Go tests: seal -> unseal round-trip; recovery with 3 shards; 2 shards fails; raw key never on disk (assert no `master.key` file, only `master.key.sealed`).
|
|
- **Must-haves**: raw master key never touches disk; unseal works via OIDC; recovery works with 3-of-5 shards.
|
|
- **Commit**: `feat(P08): master key seal-to-OIDC + Shamir 3-of-5 (REQ-147, D-241, C-35)`
|
|
- **Tag**: `v0.11.8`
|
|
|
|
### Wave C — Auth & integrity (backend-engineer + data-engineer)
|
|
|
|
#### P09 — Daemon auth hardening — REQ-123, REQ-124, F6, F24
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/daemon/`)
|
|
- **Tasks**:
|
|
1. Remove plaintext mode entirely (mandatory mTLS).
|
|
2. Accept OIDC bearer as second factor on human-facing endpoints.
|
|
3. `http.MaxBytesReader` on all JSON-decoding handlers; `MaxHeaderBytes` set.
|
|
4. pprof loopback-only by default; `--pprof-allow-public` requires confirmation.
|
|
5. Go tests: plaintext mode rejected; oversized body rejected; pprof non-loopback rejected.
|
|
- **Commit**: `fix(P09): daemon auth hardening (REQ-123, REQ-124, F6, F24)`
|
|
- **Tag**: `v0.11.9`
|
|
|
|
#### P10 — Audit log tamper-evidence — REQ-125, F2
|
|
|
|
- **Persona**: data-engineer (territory: `internal/audit/`, `internal/store/`)
|
|
- **Tasks**:
|
|
1. Add `prev_hash` + `entry_hash` columns to `audit_log` table (migration 0008).
|
|
2. `AuditRepo.Append` computes `entry_hash = sha256(prev_hash || payload)`, stores it; HMAC-SHA256 under master key on the chain head (stored separately).
|
|
3. SQLite trigger blocks UPDATE/DELETE on `audit_log`.
|
|
4. `orca doctor audit` verifies the chain (recomputes hashes, checks HMAC).
|
|
5. Actor field carries OIDC `sub` or SPIFFE SVID.
|
|
6. Go tests: tamper detection (modify a row -> doctor fails); append-only enforcement (DELETE fails).
|
|
- **Commit**: `fix(P10): audit log tamper-evidence (REQ-125, F2)`
|
|
- **Tag**: `v0.11.10`
|
|
|
|
#### P11 — SVID chain validation — REQ-126, F9
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/identity/`)
|
|
- **Tasks**:
|
|
1. `VerifySVID` loads the CA pool (from `ClusterDir()/ca.crt` or step-ca root) and validates the full cert chain.
|
|
2. Reject certs signed by unknown CAs even with correct URI SAN.
|
|
3. Go tests: cert from wrong CA rejected; cert from correct CA + correct URI accepted; expired cert rejected.
|
|
- **Commit**: `fix(P11): SVID chain validation (REQ-126, F9)`
|
|
- **Tag**: `v0.11.11`
|
|
|
|
#### P12 — Backup symlink validation — REQ-127, F7
|
|
|
|
- **Persona**: data-engineer (territory: `internal/backup/`)
|
|
- **Tasks**:
|
|
1. `Restore` rejects `Linkname` that's absolute, contains `..`, or points outside `ORCA_HOME`.
|
|
2. Regression test with crafted tarball containing a symlink to `/etc/shadow`.
|
|
- **Commit**: `fix(P12): backup symlink validation (REQ-127, F7)`
|
|
- **Tag**: `v0.11.12`
|
|
|
|
### Wave D — Crypto & secrets (backend-engineer)
|
|
|
|
#### P13 — step-ca /tmp hardening — REQ-128, F10
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/stepca/`, `internal/identity/`)
|
|
- **Tasks**:
|
|
1. `step ca certificate` writes to `0600` temp under `ClusterDir()/step-tmp/` (or `TMPDIR` override).
|
|
2. Cleanup in `defer`; `mkdir -p` with 0700 on the temp dir.
|
|
3. Go test: assert temp file mode is 0600; assert cleanup on success + failure.
|
|
- **Commit**: `fix(P13): step-ca /tmp hardening (REQ-128, F10)`
|
|
- **Tag**: `v0.11.13`
|
|
|
|
#### P14 — Master key rotation — REQ-129, F12, C-30
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/secrets/`, `internal/seal/`)
|
|
- **Tasks**:
|
|
1. `orca secrets rotate-master`: generate new master key, re-encrypt all namespace secrets, re-seal to OIDC.
|
|
2. `--dry-run` reports affected namespaces without writing.
|
|
3. Atomic per-namespace re-encryption; auto-rollback to old sealed key on any ns failure.
|
|
4. Go tests: rotation succeeds; partial failure rolls back; dry-run doesn't write.
|
|
- **Commit**: `fix(P14): master key rotation (REQ-129, F12, C-30)`
|
|
- **Tag**: `v0.11.14`
|
|
|
|
#### P15 — File-mode audit expansion — REQ-130, F13
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/security/`, `internal/cli/doctor.go`)
|
|
- **Tasks**:
|
|
1. `EnforceFileModes` extended to SSH key, master key (sealed blob), server cert/key, known_hosts.
|
|
2. `orca doctor modes` checks all.
|
|
3. Startup refuses to run on violation.
|
|
4. Go tests: looser mode -> doctor fails + startup refuses.
|
|
- **Commit**: `fix(P15): file-mode audit expansion (REQ-130, F13)`
|
|
- **Tag**: `v0.11.15`
|
|
|
|
### Wave E — OS scripts & emitters (backend-engineer + lead-developer)
|
|
|
|
#### P16 — aggregate.sh JSON injection + drift-gate fix — REQ-131, F11, F18
|
|
|
|
- **Persona**: lead-developer (territory: `scripts/`)
|
|
- **Tasks**:
|
|
1. Replace `printf` interpolation in `orca-aggregate.sh:64` with `jq`-based JSON construction (or a Go-side aggregator emitting JSON).
|
|
2. Fix `orca-pull.sh` R-020 parsing to use `jq` instead of grep.
|
|
3. Bats tests: malicious peer output doesn't corrupt `cluster.json`; drift gate correctly excludes acknowledged drift.
|
|
- **Commit**: `fix(P16): aggregate.sh JSON injection + drift-gate fix (REQ-131, F11, F18)`
|
|
- **Tag**: `v0.11.16`
|
|
|
|
#### P17 — install.sh checksum+GPG verification — REQ-132, F14
|
|
|
|
- **Persona**: lead-developer (territory: `scripts/install.sh`, `scripts/release.sh`)
|
|
- **Tasks**:
|
|
1. `release.sh` publishes `SHA256SUMS` + `SHA256SUMS.asc` (GPG-signed) alongside the tarball.
|
|
2. `install.sh` verifies SHA256 + GPG signature before `tar -xzf`; fail closed on mismatch.
|
|
3. `--no-verify` escape hatch (documented, warns).
|
|
4. Bats tests: tampered tarball rejected; valid tarball accepted.
|
|
- **Commit**: `fix(P17): install.sh checksum+GPG verification (REQ-132, F14)`
|
|
- **Tag**: `v0.11.17`
|
|
|
|
#### P18 — nftables ruleset hardening — REQ-133, F21
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/emitter/nft.go`)
|
|
- **Tasks**:
|
|
1. Add conntrack bounds (`ct state established,related accept`).
|
|
2. Input default-deny on the orca chain.
|
|
3. Drop invalid packets (`ct state invalid drop`).
|
|
4. `orca doctor nft` audits live ruleset against emitted one.
|
|
5. Go tests: emitted ruleset contains the new rules; doctor detects drift.
|
|
- **Commit**: `fix(P18): nftables ruleset hardening (REQ-133, F21)`
|
|
- **Tag**: `v0.11.18`
|
|
|
|
#### P19 — sudoers hardening — REQ-134, F22
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/proxmox/bootstrap.go`)
|
|
- **Tasks**:
|
|
1. Add NOEXEC to `apt-get`/`dpkg` in the OrcaOperator sudoers (or remove if unused).
|
|
2. `orca doctor proxmox` audits the sudoers file against the expected allowlist.
|
|
3. Go tests: emitted sudoers has NOEXEC; doctor detects drift.
|
|
- **Commit**: `fix(P19): sudoers hardening (REQ-134, F22)`
|
|
- **Tag**: `v0.11.19`
|
|
|
|
#### P20 — System user consistency — REQ-135, F23
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/proxmox/bootstrap.go`, `internal/cli/peer_setup.go`)
|
|
- **Tasks**:
|
|
1. Proxmox bootstrap creates `nologin` system user (`-r -s /usr/sbin/nologin`), matching peer-setup.
|
|
2. `orca doctor` flags inconsistency on existing peers.
|
|
3. `orca upgrade` migrates existing `-m -s /bin/bash` users to `-r -s /usr/sbin/nologin`.
|
|
4. Go tests: emitted useradd matches; doctor detects the old style.
|
|
- **Commit**: `fix(P20): system user consistency (REQ-135, F23)`
|
|
- **Tag**: `v0.11.20`
|
|
|
|
### Wave F — State storage & migration (data-engineer)
|
|
|
|
#### P21 — SQLite file-mode + at-rest encryption — REQ-136, F8, C-31
|
|
|
|
- **Persona**: data-engineer (territory: `internal/store/`)
|
|
- **Tasks**:
|
|
1. `store.Open` sets DB file mode 0600 (via `os.Chmod` after open, since SQLite creates with umask).
|
|
2. Evaluate SQLCipher envelope (CGO-free check). If infeasible without CGO (breaks D-008), fall back to file-mode 0600 + documented threat per C-31.
|
|
3. Document the decision in RESEARCH/PROJECT.
|
|
4. Go tests: DB file mode is 0600 after open.
|
|
- **Commit**: `fix(P21): SQLite file-mode + at-rest encryption (REQ-136, F8, C-31)`
|
|
- **Tag**: `v0.11.21`
|
|
|
|
#### P22 — Migration safety + identity migration — REQ-137, F19, C-34
|
|
|
|
- **Persona**: data-engineer (territory: `internal/migration/`, `internal/cli/upgrade.go`)
|
|
- **Tasks**:
|
|
1. `copyFile` -> atomic temp+rename.
|
|
2. `migrateDBSchema` runs in a transaction with `foreign_keys(ON)`.
|
|
3. Pre-migration backup step (uses `internal/backup`).
|
|
4. Document manual rollback (restore from backup).
|
|
5. `orca upgrade` refuses v0.11 clusters using `--password`/bare-tokens without `--accept-identity-migration` (C-34).
|
|
6. Go tests: migration is atomic; partial failure rolls back; `--accept-identity-migration` gate works.
|
|
- **Commit**: `fix(P22): migration safety + identity migration (REQ-137, F19, C-34)`
|
|
- **Tag**: `v0.11.22`
|
|
|
|
### Wave G — Dual-write closure (lead-developer, gated by C-29)
|
|
|
|
#### P23 — Legacy CA/mTLS/daemon + step-ca password-provisioner deletion — REQ-138, F16
|
|
|
|
- **Persona**: lead-developer (territory: `internal/security/ca.go`, `internal/transport/mtls.go`, `internal/daemon/`, `internal/stepca/`, `internal/certpaths/`)
|
|
- **Pre-gate (C-29)**: P06, P08, P09, P11 must all be shipped.
|
|
- **Tasks**:
|
|
1. Remove `internal/security/ca.go` legacy CA; migrate `orca init` and `orca cert *` to step-ca exclusively.
|
|
2. Remove `internal/transport/mtls.go` deprecated path.
|
|
3. Remove daemon plaintext mode (already killed in P09, but delete the code path).
|
|
4. Remove `internal/certpaths/` (v0.8 flat layout); `internal/paths/` is the only layout.
|
|
5. Delete step-ca `--password-file` provisioner (already replaced by OIDC provisioner in P07).
|
|
6. Full test suite must pass after deletion.
|
|
- **Must-haves**: `orca init` + `orca cert *` work via step-ca only; no legacy code compiled.
|
|
- **Commit**: `refactor(P23): delete legacy CA/mTLS/daemon + step-ca password-provisioner (REQ-138, F16, C-29)`
|
|
- **Tag**: `v0.11.23`
|
|
|
|
### Wave H — Defense-in-depth (backend-engineer)
|
|
|
|
#### P24 — known_hosts tightening + transport hardening — REQ-139, F15, F25
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/security/flock.go`, `internal/sshpush/`)
|
|
- **Tasks**:
|
|
1. `Flock` tightens pre-existing looser perms to 0600 (chmod after open if looser).
|
|
2. `classifyDialErr` switched from substring to typed errors (use `*ssh.ExitError`, `net.Error` type assertions).
|
|
3. Add SSH-exec rate limiting (token bucket per peer, default 10 req/s).
|
|
4. Go tests: looser perms tightened; typed errors classified correctly; rate limit enforced.
|
|
- **Commit**: `fix(P24): known_hosts tightening + transport hardening (REQ-139, F15, F25)`
|
|
- **Tag**: `v0.11.24`
|
|
|
|
#### P25 — Drift event authentication — REQ-140, F18
|
|
|
|
- **Persona**: backend-engineer (territory: `internal/drift/`, `scripts/orca-drift-notify.sh`)
|
|
- **Tasks**:
|
|
1. Per-peer HMAC key (derived from master key via HKDF); deployed to peers at `0600` owned by `orca`.
|
|
2. `orca-drift-notify.sh` signs each event with the HMAC; aggregator rejects unsigned/forged events.
|
|
3. Go tests: forged event rejected; valid event accepted.
|
|
- **Commit**: `fix(P25): drift event authentication (REQ-140, F18)`
|
|
- **Tag**: `v0.11.25`
|
|
|
|
#### P26 — Security integration test suite — REQ-141, C-33
|
|
|
|
- **Persona**: backend-engineer (territory: `tests/`)
|
|
- **Tasks**:
|
|
1. Hermetic harness exercising: injection (P01), traversal (P02), symlink (P12), drift-forgery (P25), audit-tamper (P10), daemon-auth-negative (P09), OIDC mock-IdP flow (P04), ACL-with-OIDC-claims negative (P06), unseal/seal (P08), WebAuthn virtual-authenticator ceremony (P05), password-removal regression (P07 -- assert `--password` rejected).
|
|
2. Gates in `.coreci.yml` `validate` pipeline (C-33).
|
|
3. Bats + Go test runner.
|
|
- **Commit**: `test(P26): security integration test suite (REQ-141, C-33)`
|
|
- **Tag**: `v0.11.26`
|
|
|
|
### Wave I — Documentation & release (lead-developer)
|
|
|
|
#### P27 — Zero-trust + OIDC + WebAuthn + threat-model docs — REQ-142
|
|
|
|
- **Persona**: lead-developer (territory: `docs/`, `README.md`)
|
|
- **Tasks**:
|
|
1. `docs/threat-model.md`: STRIDE per component, zero-trust model, OIDC data-flow diagram, OS surface diagram, residual risk register.
|
|
2. `docs/oidc.md`: configure your IdP, bundled Dex offline quickstart, claim-to-namespace mapping, BYO-IdP override.
|
|
3. `docs/webauthn.md`: passkey registration, RP ID, secure context, recovery flow.
|
|
4. `docs/security-runbook.md`: unseal/seal, master key rotation, incident response, sudoers audit, nft audit, Shamir recovery.
|
|
5. README security section names "no orca credentials" as an invariant (R-021).
|
|
- **Commit**: `docs(P27): zero-trust + OIDC + WebAuthn + threat-model docs (REQ-142)`
|
|
- **Tag**: `v0.11.27`
|
|
|
|
#### P28 — Final review + ship + audit — REQ-143
|
|
|
|
- **Persona**: lead-developer (coordinates)
|
|
- **Tasks**:
|
|
1. `ciagent-review` multi-persona review across all phases.
|
|
2. `ciagent-audit` reconstruction test (git log matches `.ciagent/` files).
|
|
3. C-32 human-gate: confirm GITEA_TOKEN rotated + `.env` re-seeded (escalation hook if pending).
|
|
4. Merge `phase/28` -> `milestone/v0.12-security-hardening`.
|
|
5. Merge `milestone/v0.12-security-hardening` -> `main` (rebase-then-fast-forward).
|
|
6. Tag `v0.11.28` (= v0.12 milestone release per feature-milestone rule).
|
|
7. Create Gitea release with full milestone summary.
|
|
8. Delete milestone + phase branches (tags preserve history).
|
|
9. Update REQUIREMENTS.md (mark all v0.12 REQs complete) + ROADMAP.md (mark v0.12 complete).
|
|
- **Commit**: `docs(milestone): complete v0.12 -- Security Hardening (Zero-Trust Identity) (29 phases shipped)`
|
|
- **Tag**: `v0.11.28`
|