docs(P11): doc drift round 2 — README, cli.md, CHANGELOG, verify-reqs (REQ-160)
- README: status banner v0.12+v0.13, latest tag v0.12.10, subcommand table expanded (auth/nft/peer-setup/secrets rotate-master), "mTLS by default" corrected to "SSH-push canonical", docs table updated - docs/cli.md: complete rewrite (521->1465 lines), all ~40 subcommands - CHANGELOG: regenerated from git log (v0.11.29..HEAD) - help text: job run HCL->markdown, job stop daemon->SSH-push - docs/security-runbook.md: expanded to match P05 reality (seal/unseal, doctor audit/modes/oidc, incident response) - docs/webauthn.md: added auth register (P06) - docs/namespace.md: added inherit + set-constraint - internal/proxmox/bootstrap.go: comments password->key auth - internal/cli/status.go: deprecation warning - scripts/verify-docs.sh + make verify-docs: cli.md <-> orca --help - cmd/verify-reqs/main.go: fix bold-format regex (was bypassing v0.12) + case-insensitive status matching - .ciagent/REQUIREMENTS.md: v0.12 REQs marked complete - .ciagent/ROADMAP.md: v0.12 bolded COMPLETE ---ci--- project: orca phase: 11 milestone: v0.13 status: complete requirements: covered: [160] ---/ci---
This commit is contained in:
+31
-31
@@ -254,7 +254,7 @@ operator decision Q2=C.
|
||||
|
||||
## v0.12 Milestone Summary — Security Hardening (Zero-Trust Identity)
|
||||
|
||||
**Status**: in progress (Phase 0). 30 net-new requirements (REQ-119..REQ-148)
|
||||
**Status**: complete (shipped as v0.11.x tags; milestone release v0.11.28). 30 net-new requirements (REQ-119..REQ-148)
|
||||
derived from the v0.12 threat-model review (25 findings F1..F25) and the
|
||||
zero-trust identity model (R-021). See ROADMAP.md for the 29-phase plan
|
||||
(P0 + P01..P27 + P28 final) and RESEARCH_v0.12.md for the full threat model.
|
||||
@@ -263,41 +263,41 @@ zero-trust identity model (R-021). See ROADMAP.md for the 29-phase plan
|
||||
|
||||
| ID | Requirement | Priority | Phase | Status |
|
||||
|----|-------------|----------|-------|--------|
|
||||
| REQ-119 | Command injection fix in `internal/runtime/podman.go` & `wasm.go`: shell-quote `cmdStr` via `shellQuote` in SSH exec interpolation (`podman.go:57`, `wasm.go:39`); add injection regression tests (bats + Go) covering `;`, `\|`, `$()`, backticks, newline injection (F3) | High | **v0.12 P01** | pending |
|
||||
| REQ-120 | Namespace path traversal fix: `validateNamespaceName` in `internal/ns/` rejects `..`, `/`, leading `-`, null bytes, control chars in `ns create`/`ns inherit`/`ns set-constraint`; add fuzz test (F4) | High | **v0.12 P02** | pending |
|
||||
| REQ-121 | Txn apply path allowlist: `apply.sh` python heredoc validates every `path` in `desired-state.json` against a prefix allowlist (`/etc/orca/`, `/etc/traefik/orca*`, `/etc/systemd/system/orca-*`, `/etc/nftables.d/orca*`, `/etc/syncthing/orca*`); rejects otherwise; HMAC-signed manifest unchanged (F5) | High | **v0.12 P03** | pending |
|
||||
| REQ-119 | Command injection fix in `internal/runtime/podman.go` & `wasm.go`: shell-quote `cmdStr` via `shellQuote` in SSH exec interpolation (`podman.go:57`, `wasm.go:39`); add injection regression tests (bats + Go) covering `;`, `\|`, `$()`, backticks, newline injection (F3) | High | **v0.12 P01** | complete |
|
||||
| REQ-120 | Namespace path traversal fix: `validateNamespaceName` in `internal/ns/` rejects `..`, `/`, leading `-`, null bytes, control chars in `ns create`/`ns inherit`/`ns set-constraint`; add fuzz test (F4) | High | **v0.12 P02** | complete |
|
||||
| REQ-121 | Txn apply path allowlist: `apply.sh` python heredoc validates every `path` in `desired-state.json` against a prefix allowlist (`/etc/orca/`, `/etc/traefik/orca*`, `/etc/systemd/system/orca-*`, `/etc/nftables.d/orca*`, `/etc/syncthing/orca*`); rejects otherwise; HMAC-signed manifest unchanged (F5) | High | **v0.12 P03** | complete |
|
||||
|
||||
### Wave B — Zero-trust identity
|
||||
|
||||
| ID | Requirement | Priority | Phase | Status |
|
||||
|----|-------------|----------|-------|--------|
|
||||
| REQ-122 | ACL enforcement wiring: `acl.Check` invoked in daemon handlers (read/write/admin by route) and SSH-push applier (validates `ORCA_OIDC_TOKEN` env var against JWKS before applying any txn); deny-by-default enforced; actor recorded in audit (F1, foundational for REQ-145) | High | **v0.12 P06** | pending |
|
||||
| REQ-123 | Daemon auth hardening: mandatory mTLS (remove plaintext mode entirely); OIDC bearer accepted as second factor on human-facing endpoints; `MaxBytesReader` body limits; pprof loopback-only by default, refuse non-loopback without `--pprof-allow-public` confirmation (F6, F24) | High | **v0.12 P09** | pending |
|
||||
| REQ-124 | HTTP request body size limits: `http.MaxBytesReader` on all JSON-decoding handlers; `MaxHeaderBytes` set; rejects oversized bodies (F24) | Medium | **v0.12 P09** | pending |
|
||||
| REQ-125 | Audit log tamper-evidence: hash-chained entries (`prev_hash = sha256(prev_row \|\| payload)`), HMAC-SHA256 under master key on the chain head; `orca doctor audit` verifies the chain; append-only enforcement via SQLite trigger blocking UPDATE/DELETE; actor field carries OIDC `sub` or SPIFFE SVID (F2) | High | **v0.12 P10** | pending |
|
||||
| REQ-126 | SVID chain validation: `VerifySVID` validates the full cert chain against the CA pool, not just the URI SAN; reject certs signed by unknown CAs even with correct URI (F9) | High | **v0.12 P11** | pending |
|
||||
| REQ-127 | Backup symlink validation: `Restore` rejects `Linkname` that's absolute, contains `..`, or points outside `ORCA_HOME`; add regression test with crafted tarball (F7) | High | **v0.12 P12** | pending |
|
||||
| REQ-128 | step-ca /tmp hardening: `step ca certificate` writes to 0600 temp under `ClusterDir()/step-tmp/` (or `TMPDIR` override), not world-readable `/tmp`; cleanup in `defer` (F10) | High | **v0.12 P13** | pending |
|
||||
| REQ-129 | Master key rotation: `orca secrets rotate-master` re-encrypts all namespace secrets under a new master key; new master key re-sealed to OIDC as part of the same operation; `--dry-run` + atomic + automatic rollback to old sealed key on any ns failure; no passphrase (R-021) (F12) | High | **v0.12 P14** | pending |
|
||||
| REQ-130 | File-mode audit expansion: `EnforceFileModes` extended to SSH key, master key (sealed blob), server cert/key, known_hosts; `orca doctor modes` checks all; startup refuses to run on violation (F13) | Medium | **v0.12 P15** | pending |
|
||||
| REQ-131 | aggregate.sh JSON injection fix + drift-gate parse fix: replace `printf` interpolation with `jq`-based JSON construction (or Go-side aggregator emitting JSON); fix `orca-pull.sh` R-020 parsing to use `jq` instead of grep (F11, F18) | High | **v0.12 P16** | pending |
|
||||
| REQ-132 | install.sh checksum+GPG verification: release.sh publishes `SHA256SUMS` + `SHA256SUMS.asc` (GPG-signed) alongside tarball; install.sh verifies before `tar -xzf`; fail closed on mismatch (F14) | High | **v0.12 P17** | pending |
|
||||
| REQ-133 | nftables ruleset hardening: add conntrack bounds (`ct state established,related accept`), input default-deny on orca chain, drop invalid packets; `orca doctor nft` audits live ruleset against emitted one (F21) | Medium | **v0.12 P18** | pending |
|
||||
| REQ-134 | sudoers hardening: add NOEXEC to `apt-get`/`dpkg` (or remove if unused); `orca doctor proxmox` audits sudoers file against expected allowlist (F22) | Medium | **v0.12 P19** | pending |
|
||||
| REQ-135 | System user consistency: Proxmox bootstrap creates `nologin` system user (`-r -s /usr/sbin/nologin`), matching peer-setup; `orca doctor` flags inconsistency on existing peers; `orca upgrade` migrates (F23) | Medium | **v0.12 P20** | pending |
|
||||
| REQ-136 | SQLite file-mode + at-rest encryption: `store.Open` sets DB file mode 0600; optional `--encrypt-db` (CGO-free fallback per C-31: file-mode 0600 + documented threat if SQLCipher needs CGO); no CGO (F8) | High | **v0.12 P21** | pending |
|
||||
| REQ-137 | Migration safety: `copyFile` -> atomic temp+rename; `migrateDBSchema` runs in transaction with `foreign_keys(ON)`; pre-migration backup step (uses `internal/backup`); document manual rollback; v0.11->v0.12 identity migration: `orca upgrade` refuses clusters using `--password`/bare-tokens without `--accept-identity-migration` (F19, C-34) | High | **v0.12 P22** | pending |
|
||||
| REQ-138 | Legacy CA/mTLS/daemon + step-ca password-provisioner deletion: remove `internal/security/ca.go` legacy CA, `internal/transport/mtls.go` deprecated path, daemon plaintext mode; migrate `orca init`/`orca cert *` to step-ca exclusively; `certpaths` (v0.8 layout) removed; delete step-ca `--password-file` provisioner (replaced by OIDC provisioner); **gate: P06/P08/P09/P11 all shipped** (F16) | High | **v0.12 P23** | pending |
|
||||
| REQ-139 | known_hosts tightening + transport hardening: `Flock` tightens pre-existing looser perms to 0600; `classifyDialErr` switched from substring to typed errors; add SSH-exec rate limiting (token bucket per peer) (F15, F25) | Medium | **v0.12 P24** | pending |
|
||||
| REQ-140 | Drift event authentication: drift events signed with per-peer HMAC key (derived from master key); aggregator rejects unsigned/forged events; `orca-drift-notify.sh` reads key from 0600 file owned by `orca` (F18) | Medium | **v0.12 P25** | pending |
|
||||
| REQ-141 | Security integration test suite: hermetic harness exercising injection, traversal, symlink, drift-forgery, audit-tamper, daemon-auth-negative, OIDC mock-IdP flow, ACL-with-OIDC-claims negative tests, unseal/seal, WebAuthn virtual-authenticator ceremony, password-removal regression (assert `--password` is rejected); gates in `.coreci.yml` `validate` (C-33) | High | **v0.12 P26** | pending |
|
||||
| REQ-142 | Zero-trust + OIDC + WebAuthn + threat-model docs: `docs/threat-model.md` (STRIDE + zero-trust model + OIDC data-flow), `docs/oidc.md` (configure your IdP, Dex offline quickstart, claim-to-namespace mapping), `docs/webauthn.md` (passkey registration, RP ID, secure context), `docs/security-runbook.md` (unseal/seal, master key rotation, incident response, sudoers audit, nft audit); README security section names "no orca credentials" as an invariant | Medium | **v0.12 P27** | pending |
|
||||
| REQ-143 | Final review + ship + audit: multi-persona review across all phases, `ciagent-audit` reconstruction test, milestone merge to main, tag `v0.11.29` (= v0.12 milestone release per feature-milestone rule) | High | **v0.12 P28** | pending |
|
||||
| REQ-144 | OIDC client + bundled Dex: `orca auth login`/`logout`/`status`/`init-idp`; OIDC config block (`oidc.issuer`, `client_id`, `client_secret`, `scopes`); bundled Dex systemd unit + Traefik route on the lead; BYO external IdP override via `oidc.issuer` repoint; JWKS caching + refresh; token storage at `~/.orca/credentials.json` (0600); `--oidc` flag on commands requiring identity; browser auth-code + PKCE + local loopback redirect; headless device-code fallback (D-238..D-247) | High | **v0.12 P04** | pending |
|
||||
| REQ-145 | ACL rewrite to OIDC claims: remove `KindToken` entirely; `KindSpiffe` stays for machine identity; new `KindOidc` maps `sub`+`groups` -> namespace permissions; `acl.Check` takes OIDC claims struct; deny-by-default enforced in daemon + SSH-push applier; `acl.json` mode tightened to 0600 (F1) | High | **v0.12 P06** | pending |
|
||||
| REQ-146 | Remove all password/token paths (breaking): delete `--password`/`$ORCA_PROXMOX_PASSWORD` from Proxmox join (replace with pre-staged-key-only or `step ssh` OIDC cert exchange); delete step-ca `--password-file` provisioner (migrate to OIDC provisioner); delete any bare-token CLI paths; documented in migration guide (R-021, C-34) | High | **v0.12 P07** | pending |
|
||||
| REQ-147 | Master key seal-to-OIDC + Shamir recovery: master key encrypted with key derived from OIDC token exchange at unseal; `orca cluster unseal`/`seal`; sealed blob at `ClusterDir()/master.key.sealed` (0600); raw key never on disk; Shamir 3-of-5 shards printed at seal time; recovery via `--recovery` + 3 shards; mTLS-only offline path derives seal key from cluster CA (D-241, C-35) | High | **v0.12 P08** | pending |
|
||||
| REQ-148 | WebAuthn connector for Dex (passkeys): `orca-webauthn-connector` (~300 LoC Go, `go-webauthn`); register/login ceremonies at `/orca/webauthn/{register,login}` behind Traefik; `orca auth register` browser flow; passkey storage SQLite `ClusterDir()/webauthn-credentials.db` (0600, public keys only); RP ID = cluster Traefik domain; secure context via step-ca cert; headless device-code fallback; virtual-authenticator integration tests (D-240, D-243, D-244, C-38) | High | **v0.12 P05** | pending |
|
||||
| REQ-122 | ACL enforcement wiring: `acl.Check` invoked in daemon handlers (read/write/admin by route) and SSH-push applier (validates `ORCA_OIDC_TOKEN` env var against JWKS before applying any txn); deny-by-default enforced; actor recorded in audit (F1, foundational for REQ-145) | High | **v0.12 P06** | complete |
|
||||
| REQ-123 | Daemon auth hardening: mandatory mTLS (remove plaintext mode entirely); OIDC bearer accepted as second factor on human-facing endpoints; `MaxBytesReader` body limits; pprof loopback-only by default, refuse non-loopback without `--pprof-allow-public` confirmation (F6, F24) | High | **v0.12 P09** | complete |
|
||||
| REQ-124 | HTTP request body size limits: `http.MaxBytesReader` on all JSON-decoding handlers; `MaxHeaderBytes` set; rejects oversized bodies (F24) | Medium | **v0.12 P09** | complete |
|
||||
| REQ-125 | Audit log tamper-evidence: hash-chained entries (`prev_hash = sha256(prev_row \|\| payload)`), HMAC-SHA256 under master key on the chain head; `orca doctor audit` verifies the chain; append-only enforcement via SQLite trigger blocking UPDATE/DELETE; actor field carries OIDC `sub` or SPIFFE SVID (F2) | High | **v0.12 P10** | complete |
|
||||
| REQ-126 | SVID chain validation: `VerifySVID` validates the full cert chain against the CA pool, not just the URI SAN; reject certs signed by unknown CAs even with correct URI (F9) | High | **v0.12 P11** | complete |
|
||||
| REQ-127 | Backup symlink validation: `Restore` rejects `Linkname` that's absolute, contains `..`, or points outside `ORCA_HOME`; add regression test with crafted tarball (F7) | High | **v0.12 P12** | complete |
|
||||
| REQ-128 | step-ca /tmp hardening: `step ca certificate` writes to 0600 temp under `ClusterDir()/step-tmp/` (or `TMPDIR` override), not world-readable `/tmp`; cleanup in `defer` (F10) | High | **v0.12 P13** | complete |
|
||||
| REQ-129 | Master key rotation: `orca secrets rotate-master` re-encrypts all namespace secrets under a new master key; new master key re-sealed to OIDC as part of the same operation; `--dry-run` + atomic + automatic rollback to old sealed key on any ns failure; no passphrase (R-021) (F12) | High | **v0.12 P14** | complete |
|
||||
| REQ-130 | File-mode audit expansion: `EnforceFileModes` extended to SSH key, master key (sealed blob), server cert/key, known_hosts; `orca doctor modes` checks all; startup refuses to run on violation (F13) | Medium | **v0.12 P15** | complete |
|
||||
| REQ-131 | aggregate.sh JSON injection fix + drift-gate parse fix: replace `printf` interpolation with `jq`-based JSON construction (or Go-side aggregator emitting JSON); fix `orca-pull.sh` R-020 parsing to use `jq` instead of grep (F11, F18) | High | **v0.12 P16** | complete |
|
||||
| REQ-132 | install.sh checksum+GPG verification: release.sh publishes `SHA256SUMS` + `SHA256SUMS.asc` (GPG-signed) alongside tarball; install.sh verifies before `tar -xzf`; fail closed on mismatch (F14) | High | **v0.12 P17** | complete |
|
||||
| REQ-133 | nftables ruleset hardening: add conntrack bounds (`ct state established,related accept`), input default-deny on orca chain, drop invalid packets; `orca doctor nft` audits live ruleset against emitted one (F21) | Medium | **v0.12 P18** | complete |
|
||||
| REQ-134 | sudoers hardening: add NOEXEC to `apt-get`/`dpkg` (or remove if unused); `orca doctor proxmox` audits sudoers file against expected allowlist (F22) | Medium | **v0.12 P19** | complete |
|
||||
| REQ-135 | System user consistency: Proxmox bootstrap creates `nologin` system user (`-r -s /usr/sbin/nologin`), matching peer-setup; `orca doctor` flags inconsistency on existing peers; `orca upgrade` migrates (F23) | Medium | **v0.12 P20** | complete |
|
||||
| REQ-136 | SQLite file-mode + at-rest encryption: `store.Open` sets DB file mode 0600; optional `--encrypt-db` (CGO-free fallback per C-31: file-mode 0600 + documented threat if SQLCipher needs CGO); no CGO (F8) | High | **v0.12 P21** | complete |
|
||||
| REQ-137 | Migration safety: `copyFile` -> atomic temp+rename; `migrateDBSchema` runs in transaction with `foreign_keys(ON)`; pre-migration backup step (uses `internal/backup`); document manual rollback; v0.11->v0.12 identity migration: `orca upgrade` refuses clusters using `--password`/bare-tokens without `--accept-identity-migration` (F19, C-34) | High | **v0.12 P22** | complete |
|
||||
| REQ-138 | Legacy CA/mTLS/daemon + step-ca password-provisioner deletion: remove `internal/security/ca.go` legacy CA, `internal/transport/mtls.go` deprecated path, daemon plaintext mode; migrate `orca init`/`orca cert *` to step-ca exclusively; `certpaths` (v0.8 layout) removed; delete step-ca `--password-file` provisioner (replaced by OIDC provisioner); **gate: P06/P08/P09/P11 all shipped** (F16) | High | **v0.12 P23** | complete |
|
||||
| REQ-139 | known_hosts tightening + transport hardening: `Flock` tightens pre-existing looser perms to 0600; `classifyDialErr` switched from substring to typed errors; add SSH-exec rate limiting (token bucket per peer) (F15, F25) | Medium | **v0.12 P24** | complete |
|
||||
| REQ-140 | Drift event authentication: drift events signed with per-peer HMAC key (derived from master key); aggregator rejects unsigned/forged events; `orca-drift-notify.sh` reads key from 0600 file owned by `orca` (F18) | Medium | **v0.12 P25** | complete |
|
||||
| REQ-141 | Security integration test suite: hermetic harness exercising injection, traversal, symlink, drift-forgery, audit-tamper, daemon-auth-negative, OIDC mock-IdP flow, ACL-with-OIDC-claims negative tests, unseal/seal, WebAuthn virtual-authenticator ceremony, password-removal regression (assert `--password` is rejected); gates in `.coreci.yml` `validate` (C-33) | High | **v0.12 P26** | complete |
|
||||
| REQ-142 | Zero-trust + OIDC + WebAuthn + threat-model docs: `docs/threat-model.md` (STRIDE + zero-trust model + OIDC data-flow), `docs/oidc.md` (configure your IdP, Dex offline quickstart, claim-to-namespace mapping), `docs/webauthn.md` (passkey registration, RP ID, secure context), `docs/security-runbook.md` (unseal/seal, master key rotation, incident response, sudoers audit, nft audit); README security section names "no orca credentials" as an invariant | Medium | **v0.12 P27** | complete |
|
||||
| REQ-143 | Final review + ship + audit: multi-persona review across all phases, `ciagent-audit` reconstruction test, milestone merge to main, tag `v0.11.29` (= v0.12 milestone release per feature-milestone rule) | High | **v0.12 P28** | complete |
|
||||
| REQ-144 | OIDC client + bundled Dex: `orca auth login`/`logout`/`status`/`init-idp`; OIDC config block (`oidc.issuer`, `client_id`, `client_secret`, `scopes`); bundled Dex systemd unit + Traefik route on the lead; BYO external IdP override via `oidc.issuer` repoint; JWKS caching + refresh; token storage at `~/.orca/credentials.json` (0600); `--oidc` flag on commands requiring identity; browser auth-code + PKCE + local loopback redirect; headless device-code fallback (D-238..D-247) | High | **v0.12 P04** | complete |
|
||||
| REQ-145 | ACL rewrite to OIDC claims: remove `KindToken` entirely; `KindSpiffe` stays for machine identity; new `KindOidc` maps `sub`+`groups` -> namespace permissions; `acl.Check` takes OIDC claims struct; deny-by-default enforced in daemon + SSH-push applier; `acl.json` mode tightened to 0600 (F1) | High | **v0.12 P06** | complete |
|
||||
| REQ-146 | Remove all password/token paths (breaking): delete `--password`/`$ORCA_PROXMOX_PASSWORD` from Proxmox join (replace with pre-staged-key-only or `step ssh` OIDC cert exchange); delete step-ca `--password-file` provisioner (migrate to OIDC provisioner); delete any bare-token CLI paths; documented in migration guide (R-021, C-34) | High | **v0.12 P07** | complete |
|
||||
| REQ-147 | Master key seal-to-OIDC + Shamir recovery: master key encrypted with key derived from OIDC token exchange at unseal; `orca cluster unseal`/`seal`; sealed blob at `ClusterDir()/master.key.sealed` (0600); raw key never on disk; Shamir 3-of-5 shards printed at seal time; recovery via `--recovery` + 3 shards; mTLS-only offline path derives seal key from cluster CA (D-241, C-35) | High | **v0.12 P08** | complete |
|
||||
| REQ-148 | WebAuthn connector for Dex (passkeys): `orca-webauthn-connector` (~300 LoC Go, `go-webauthn`); register/login ceremonies at `/orca/webauthn/{register,login}` behind Traefik; `orca auth register` browser flow; passkey storage SQLite `ClusterDir()/webauthn-credentials.db` (0600, public keys only); RP ID = cluster Traefik domain; secure context via step-ca cert; headless device-code fallback; virtual-authenticator integration tests (D-240, D-243, D-244, C-38) | High | **v0.12 P05** | complete |
|
||||
|
||||
### Scope notes (v0.12)
|
||||
|
||||
|
||||
+1
-1
@@ -400,7 +400,7 @@ tags: `v0.10.0`…`v0.10.21`.
|
||||
- External CA / Let's Encrypt / cert transparency
|
||||
- Online-only features (HSTS, OCSP stapling, telemetry)
|
||||
|
||||
## Milestone v0.12: Security Hardening (Zero-Trust Identity) — COMPLETE
|
||||
## Milestone v0.12: Security Hardening (Zero-Trust Identity) — **COMPLETE**
|
||||
|
||||
**Scope**: comprehensive security hardening across the entire attack
|
||||
surface, **including the operating system itself**, plus adoption of a
|
||||
|
||||
+132
-26
@@ -5,31 +5,137 @@ All notable changes to orca are documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
- `e1b538575c57158c7a6661d5919b103f6c7932fc` — feat(P06): CoreCI release flow with .coreci.yml and tea integration
|
||||
- `07b8ad2ceaba7ca303dfe91876930d33b76c633e` — ship(P05): health checks merged into milestone
|
||||
- `b06458d31370750417a3b239dac61c6e2fdf5329` — docs(P05): verification - 4 layers pass
|
||||
- `708d9834296271094667700e88e80bfa27db7bdd` — feat(P05): health check daemon with /healthz, /readyz, /v1/* handlers
|
||||
- `30c523c0c7a8e75a2e97b42f1c8a39802febcbdc` — ship(P04): state persistence merged into milestone
|
||||
- `759b1b519d7fadf3d91d3070952d9ad2051a0eba` — docs(P04): verification - 4 layers pass
|
||||
- `b25e074e1d3518f175478184ff8d002ec0d8412c` — feat(P04): audit log + persistence hardening
|
||||
- `bb6b5b3e8342c16601a8503223c7186ecdbb00df` — ship(P03): task exec merged into milestone
|
||||
- `857f7563190e7703f97c607a50d6b0a897d250e9` — docs(P03): verification - 4 layers pass
|
||||
- `f9a98733411cfa8657e82636e0c55671086ebe46` — feat(P03): task execution engine with HCL specs, jobs, tasks, WaitDelay
|
||||
- `78334f1f74f0c185c6d38014c796aac4903b8141` — ship(P02): node mgmt merged into milestone
|
||||
- `c7dbcef9587596786a541a7566479d9fb93fcf0a` — docs(P02): verification - 4 layers pass
|
||||
- `9580f347c68e395dccfbe83b27a857d52bf21075` — feat(P02): node management with SQLite-backed registry
|
||||
- `46e929e4c6539bd604539ba27d5ed0c606e87bb9` — chore(P01): source .env in trigger_coreci.sh for GITEA_TOKEN
|
||||
- `503923bf1ee2c60f8375acc7eb9608d346368e1c` — ship(P01): cli skeleton merged into milestone
|
||||
- `e3f6e1df825d39f73933c9996bd2cc4717ff1061` — docs(P01): verification - 4 layers pass
|
||||
- `aa3cccead503a37dfec75873d06d2d396a2876f2` — feat(P01): CLI skeleton with Cobra, subcommand stubs, pre-push hook
|
||||
- `c2038952c74f7c242ba3be65d2f4269b23685f5a` — docs(P00): create 6 phase plans with wave ordering
|
||||
- `65eb2e601b741b36388598b9f8adddd7bd8dd3a8` — docs(P00): research findings - architecture + personas
|
||||
- `6f34f1794b9f526c06a1dc139d4a74371599502e` — docs(P00): ideation - 30 ideas accepted (3 tiers)
|
||||
- `bc7ce1caf672e87774455a6cd6cc0db986cd09b3` — docs(P00): clarify ambiguities (full autonomy, 10 decisions)
|
||||
- `55aae5347ec09bce9ef7697ea0c9c9ee158bc040` — chore(P00): rename orch-engine to orca, configure gitea + coreci (v0.1)
|
||||
- `0cba1aa5feef9564f8b9a2a97ae735dc859a8a84` — chore(P00): set autonomy level to full
|
||||
- `e2e77e79b9cbfb462044662543845476f843161b` — chore(P00): quick task - populate config.json with backlog reference
|
||||
- `8c086def698bf0af31e8e820b6b7a2783af06f43` — chore(config): populate ciagent config with standard settings
|
||||
- `8774008c3e47e4ca4711f4fef164531006d16216` — docs(init): validate specification
|
||||
## v0.13 milestone (in progress) — tag line v0.12.x
|
||||
|
||||
The v0.13 milestone is **Production Hardening Round 2 + UAT Plan**.
|
||||
Three deep codebase sweeps (security, reliability, feature/doc claims)
|
||||
surfaced ~60 gaps beyond v0.12. v0.13 closes all critical/high/medium
|
||||
findings and delivers the UAT plan + signoff script that gates the
|
||||
v1.0.0 cut.
|
||||
|
||||
**Load-bearing architectural changes**:
|
||||
- **R-022** — `orca job run` deploys to remote nodes via the scheduler →
|
||||
emitter → SSH-push pipeline. The local `exec.CommandContext` path is
|
||||
removed (P03).
|
||||
- **R-023** — Zero-trust enforcement is operationally wired: `acl.Check`
|
||||
is invoked on every daemon handler + sshpush + txn apply path;
|
||||
`acl.json` is 0600; audit `actor` carries OIDC sub/SVID; WebAuthn
|
||||
registration requires auth; `cluster seal`/`unseal` + `doctor audit`/
|
||||
`doctor modes` CLI commands exist (P04, P05).
|
||||
|
||||
### v0.13 phase commits (v0.11.29..HEAD)
|
||||
|
||||
- `ed91d68` — feat(P10): observability expansion — metrics + security headers (REQ-159)
|
||||
- `531b369` — fix(P09): migration + operational safety — job stop, retention, logs cap (REQ-158)
|
||||
- `3a3ea74` — fix(P08): transport + SSH safety — typed errors, IPv6, timeouts, signal (REQ-157)
|
||||
- `0358efe` — fix(P07): concurrency safety — SQLite, flock, cache, atomic writes (REQ-156)
|
||||
- `978334a` — feat(P06): auth init-idp real + auth register + doctor oidc (REQ-155)
|
||||
- `9e83238` — feat(P05): seal/audit CLI + chain race fix + key zeroing (REQ-154)
|
||||
- `5232fcb` — fix(P04): wire ACL enforcement + WebAuthn reg auth + audit actor (REQ-153)
|
||||
- `cf3d98e` — feat(P03): wire scheduler into job run + fix jobspec parser (REQ-151, REQ-152)
|
||||
- `4b70e31` — fix(P02): input validation + injection hardening — 11 vectors (REQ-150)
|
||||
- `b0158c9` — fix(P01): bump go toolchain to 1.25.12 + fix pre-existing test bugs (REQ-149)
|
||||
- `7479cd1` — docs(checkpoint): P0 shipped — v0.12.0 tagged
|
||||
- `1a2dd1a` — docs(P00): incorporate grill binding conditions C-44..C-49
|
||||
- `437d9b2` — docs(P00): grill v0.13 — CONDITIONAL PROCEED (6 binding conditions C-44..C-49)
|
||||
- `82bfab1` — docs(P00): create phase plans — 14 phases, 15 REQs, vertical slices
|
||||
- `a2a651e` — docs(P00): ideation results — 15 accepted (REQ-149..REQ-163), 0 skipped
|
||||
- `3f5e5de` — docs(P00): research findings — threat model round 3 (~60 gaps, F26-F101)
|
||||
- `7a60b35` — docs(P00): clarify v0.13 — 7 decisions resolved (D-248..D-254)
|
||||
- `8071793` — docs(init): validate specification — v0.13 Production Hardening Round 2 + UAT Plan
|
||||
|
||||
### v0.13 phase summary
|
||||
|
||||
- **P0** — Pre-execution: specify → clarify → research → ideate → plan → grill (tag `v0.12.0`)
|
||||
- **P01** — Toolchain & dependency vulns: Go 1.25.12 bump, 24 stdlib vulns closed, govulncheck triage (REQ-149)
|
||||
- **P02** — Input validation & injection hardening: 11 vectors closed (`orca logs --job` RCE, tar-slip, sudoers injection, pprof loopback, txn/nft ID validation, drain allocID, cluster_compat, podman image, nft TrustedProbes, sudoers user/role) (REQ-150)
|
||||
- **P03** — Scheduler/deployment wiring + jobspec parser: `orca job run` wires scheduler → emitter → SSH-push; `schedule:`/`timeout:` parsed by markdown jobspec (REQ-151, REQ-152)
|
||||
- **P04** — ACL enforcement + WebAuthn registration auth: `acl.Check` wired into daemon + sshpush + txn apply; WebAuthn registration requires auth; audit actor carries OIDC sub/SVID (REQ-153)
|
||||
- **P05** — Seal/audit CLI + chain race fix + key zeroing: `orca cluster seal`/`unseal`, `orca doctor audit`, `orca doctor modes` CLI commands; audit hash-chain race fix; master key zeroed on exit (REQ-154)
|
||||
- **P06** — auth init-idp real + auth register + doctor oidc: real Dex deployment, `orca auth register` browser flow, `orca doctor oidc` health check (REQ-155)
|
||||
- **P07** — Concurrency safety: SQLite WAL, flock on known_hosts, cache thread-safety, atomic writes (REQ-156)
|
||||
- **P08** — Transport & SSH safety: typed dial errors, IPv6 support, connect timeouts, signal handling (REQ-157)
|
||||
- **P09** — Migration & operational safety: `orca job stop` via SSH, DB retention check, logs cap (REQ-158)
|
||||
- **P10** — Observability & metrics: metrics endpoint expansion, security headers (REQ-159)
|
||||
- **P11** — Doc drift round 2 (this phase, REQ-160)
|
||||
|
||||
## v0.12 milestone — COMPLETE (tag line v0.11.x)
|
||||
|
||||
The v0.12 milestone is **Security Hardening (Zero-Trust Identity)**.
|
||||
Comprehensive security hardening across the entire attack surface
|
||||
including the OS, plus adoption of a zero-trust identity model. 25
|
||||
threat-model findings (F1..F25) closed. R-021 adopted: no Orca-issued
|
||||
credentials — human identity is exclusively external (OIDC), machine
|
||||
identity is exclusively mTLS/SPIFFE.
|
||||
|
||||
**Milestone release**: `v0.11.28` (29 phases, tags `v0.11.0`..`v0.11.28`).
|
||||
|
||||
### v0.12 phase highlights
|
||||
|
||||
- Command injection fix (REQ-119, F3)
|
||||
- Namespace path traversal fix (REQ-120, F4)
|
||||
- Txn apply path allowlist (REQ-121, F5)
|
||||
- OIDC client + bundled Dex (REQ-144; BYO-IdP override)
|
||||
- WebAuthn connector for Dex / passkeys (REQ-148)
|
||||
- ACL rewrite to OIDC claims + enforcement (REQ-145, REQ-122, F1)
|
||||
- Remove all password/token paths (REQ-146, R-021, C-34)
|
||||
- Master key seal-to-OIDC + Shamir 3-of-5 recovery (REQ-147, C-35)
|
||||
- Daemon auth hardening (REQ-123, REQ-124, F6, F24)
|
||||
- Audit log tamper-evidence (REQ-125, F2)
|
||||
- SVID chain validation (REQ-126, F9)
|
||||
- Backup symlink validation (REQ-127, F7)
|
||||
- step-ca /tmp hardening (REQ-128, F10)
|
||||
- Master key rotation (REQ-129, F12, C-30)
|
||||
- File-mode audit expansion (REQ-130, F13)
|
||||
- aggregate.sh JSON injection + drift-gate fix (REQ-131, F11, F18)
|
||||
- install.sh checksum+GPG verification (REQ-132, F14)
|
||||
- nftables ruleset hardening (REQ-133, F21)
|
||||
- sudoers hardening (REQ-134, F22)
|
||||
- System user consistency (REQ-135, F23)
|
||||
- SQLite file-mode + at-rest encryption (REQ-136, F8, C-31)
|
||||
- Migration safety + identity migration (REQ-137, F19, C-34)
|
||||
- Legacy CA/mTLS/daemon + step-ca password-provisioner deletion (REQ-138, F16)
|
||||
- known_hosts tightening + transport hardening (REQ-139, F15, F25)
|
||||
- Drift event authentication (REQ-140, F18)
|
||||
- Security integration test suite (REQ-141, C-33)
|
||||
- Zero-trust + OIDC + WebAuthn + threat-model docs (REQ-142)
|
||||
- Final review + ship + audit (REQ-143)
|
||||
|
||||
## v0.11 milestone — COMPLETE (tag line v0.10.x)
|
||||
|
||||
The v0.11 milestone is **Production Hardening**. See the git log and
|
||||
ROADMAP for the full phase list.
|
||||
|
||||
## v0.1 milestone — COMPLETE
|
||||
|
||||
Initial CLI skeleton, node management, task execution, state
|
||||
persistence, audit log, health checks, and CoreCI release flow.
|
||||
|
||||
- `e1b5385` — feat(P06): CoreCI release flow with .coreci.yml and tea integration
|
||||
- `07b8ad2` — ship(P05): health checks merged into milestone
|
||||
- `b06458d` — docs(P05): verification - 4 layers pass
|
||||
- `708d983` — feat(P05): health check daemon with /healthz, /readyz, /v1/* handlers
|
||||
- `30c523c` — ship(P04): state persistence merged into milestone
|
||||
- `759b1b5` — docs(P04): verification - 4 layers pass
|
||||
- `b25e074` — feat(P04): audit log + persistence hardening
|
||||
- `bb6b5b3` — ship(P03): task exec merged into milestone
|
||||
- `857f756` — docs(P03): verification - 4 layers pass
|
||||
- `f9a9873` — feat(P03): task execution engine with HCL specs, jobs, tasks, WaitDelay
|
||||
- `78334f1` — ship(P02): node mgmt merged into milestone
|
||||
- `c7dbcef` — docs(P02): verification - 4 layers pass
|
||||
- `9580f34` — feat(P02): node management with SQLite-backed registry
|
||||
- `46e929e` — chore(P01): source .env in trigger_coreci.sh for GITEA_TOKEN
|
||||
- `503923b` — ship(P01): cli skeleton merged into milestone
|
||||
- `e3f6e1d` — docs(P01): verification - 4 layers pass
|
||||
- `aa3ccce` — feat(P01): CLI skeleton with Cobra, subcommand stubs, pre-push hook
|
||||
- `c203895` — docs(P00): create 6 phase plans with wave ordering
|
||||
- `65eb2e6` — docs(P00): research findings - architecture + personas
|
||||
- `6f34f17` — docs(P00): ideation - 30 ideas accepted (3 tiers)
|
||||
- `bc7ce1c` — docs(P00): clarify ambiguities (full autonomy, 10 decisions)
|
||||
- `55aae53` — chore(P00): rename orch-engine to orca, configure gitea + coreci (v0.1)
|
||||
- `0cba1aa` — chore(P00): set autonomy level to full
|
||||
- `e2e77e7` — chore(P00): quick task - populate config.json with backlog reference
|
||||
- `8c086de` — chore(config): populate ciagent config with standard settings
|
||||
- `8774008` — docs(init): validate specification
|
||||
|
||||
Generated by make changelog. Do not edit by hand.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: build test test-race lint fmt clean run release version changelog help security-scan verify-reqs
|
||||
.PHONY: build test test-race lint fmt clean run release version changelog help security-scan verify-reqs verify-docs
|
||||
|
||||
BINARY := bin/orca
|
||||
GOFLAGS := -trimpath
|
||||
@@ -31,6 +31,7 @@ help:
|
||||
@echo " release Run scripts/release.sh [VERSION] — build, tar, publish"
|
||||
@echo " security-scan Run gosec+govulncheck+gitleaks (P03, REQ-014/027/039)"
|
||||
@echo " verify-reqs Assert ROADMAP COMPLETE ↔ REQUIREMENTS Complete (REQ-060)"
|
||||
@echo " verify-docs Assert docs/cli.md ↔ orca --help consistency (REQ-160)"
|
||||
|
||||
build:
|
||||
@mkdir -p bin
|
||||
@@ -128,3 +129,9 @@ security-scan:
|
||||
# of scope (P04 audit). Exits 0 on consistency, 1 with a diff on drift.
|
||||
verify-reqs:
|
||||
go run ./cmd/verify-reqs .ciagent/ROADMAP.md .ciagent/REQUIREMENTS.md
|
||||
|
||||
# verify-docs asserts that every top-level subcommand in docs/cli.md
|
||||
# exists in `orca --help` output (and vice versa). Catches doc drift
|
||||
# (REQ-160). Requires the binary to be built first (`make build`).
|
||||
verify-docs: build
|
||||
./scripts/verify-docs.sh ./bin/orca docs/cli.md
|
||||
|
||||
@@ -6,8 +6,8 @@ identity.
|
||||
|
||||
## Status
|
||||
|
||||
**v0.11: Production Hardening — IN PROGRESS** | **v1.0: UAT-gated** (cut
|
||||
separately after v0.11 completion per operator decision)
|
||||
**v0.12: Security Hardening (Zero-Trust Identity) — COMPLETE** | **v0.13: Production Hardening Round 2 + UAT Plan — IN PROGRESS** | **v1.0: UAT-gated** (cut
|
||||
separately after v0.13 completion per operator decision)
|
||||
|
||||
See [.ciagent/ROADMAP.md](.ciagent/ROADMAP.md) for the full roadmap.
|
||||
|
||||
@@ -18,8 +18,8 @@ See [.ciagent/ROADMAP.md](.ciagent/ROADMAP.md) for the full roadmap.
|
||||
- **Offline-first** — no cloud dependencies; the cluster is the OS
|
||||
- **CLI-first** — the command line is the primary interface (humans and
|
||||
AI agents)
|
||||
- **Security before features** — mTLS by default; NFRs ship before new
|
||||
functionality
|
||||
- **Security before features** — SSH-push is the canonical transport
|
||||
(mTLS available for daemon mode); NFRs ship before new functionality
|
||||
- **WASM-first** — workloads target OS primitives (systemd units,
|
||||
journald), not a container runtime shim
|
||||
- **Bug fixes before features** — stability is paramount
|
||||
@@ -35,8 +35,8 @@ curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | b
|
||||
# System-level install (binary at /usr/local/bin/orca, state at /root/.orca)
|
||||
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | sudo bash -s -- --system
|
||||
|
||||
# Pin a specific version (latest tag: v0.10.19)
|
||||
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | bash -s -- --version v0.10.19
|
||||
# Pin a specific version (latest tag: v0.12.10)
|
||||
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | bash -s -- --version v0.12.10
|
||||
|
||||
# Dry-run: check what would be installed without writing
|
||||
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | bash -s -- --check
|
||||
@@ -65,7 +65,7 @@ config, database, and certificates in the namespace dir:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | bash
|
||||
# → "updated orca from v0.8.15 to v0.10.19"
|
||||
# → "updated orca from v0.11.28 to v0.12.10"
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
@@ -73,7 +73,7 @@ curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | b
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `orca init` | Initialize local orca state with full bootstrap |
|
||||
| `orca status` | Show orca daemon status |
|
||||
| `orca status` | **(deprecated v0.1 stub)** Show orca daemon status — use `orca node list` + `orca metrics /healthz` |
|
||||
| `orca version` | Print version information |
|
||||
| `orca daemon` | **(deprecated)** Run the orca daemon (HTTP API + health checks) |
|
||||
| `orca metrics` | Start metrics endpoint (Prometheus text exposition) |
|
||||
@@ -85,15 +85,18 @@ curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/main/scripts/install.sh | b
|
||||
| `orca job` | Manage orca jobs: `run`, `list`, `stop`, `logs`, `lint`, `verify`, `migrate`, `restart` |
|
||||
| `orca ns` | Manage orca namespaces: `list`, `create`, `delete`, `inspect`, `validate`, `inherit`, `set-constraint` |
|
||||
| `orca cert` | **(deprecated)** Manage orca certificates: `ca-init`, `gen`, `show`, `renew`, `fingerprint` |
|
||||
| `orca doctor` | Run self-checks: `cert`, `network`, `db`, `os`, `proxmox`, `no-orca-on-server` |
|
||||
| `orca doctor` | Run self-checks: `cert`, `network`, `db`, `os`, `proxmox`, `no-orca-on-server`, `nft`, `audit`, `modes`, `oidc`, `db-retention` |
|
||||
| `orca audit` | View orca audit log (`list`) |
|
||||
| `orca cache` | CLI cache management: `show`, `invalidate`, `invalidate-all` |
|
||||
| `orca acl` | ACL management: `grant`, `revoke`, `list`, `check` |
|
||||
| `orca secrets` | Secrets management: `set`, `get`, `list`, `rotate`, `delete` |
|
||||
| `orca secrets` | Secrets management: `set`, `get`, `list`, `rotate`, `delete`, `rotate-master` |
|
||||
| `orca drift` | Drift detection: `show`, `watch`, `acknowledge`, `remediate`, `config` |
|
||||
| `orca txn` | Transaction management: `apply`, `list`, `show`, `rollback` |
|
||||
| `orca nft` | nftables ingress management: `show`, `diff`, `doctor`, `country block`, `rate limit` |
|
||||
| `orca collector` | Collector/aggregator management: `start`, `stop`, `status` |
|
||||
| `orca cluster` | Cluster management: `cutover`, `rotate-lead`, `compat-check` |
|
||||
| `orca cluster` | Cluster management: `cutover`, `rotate-lead`, `compat-check`, `seal`, `unseal` |
|
||||
| `orca auth` | OIDC authentication: `login`, `logout`, `status`, `init-idp`, `register` |
|
||||
| `orca peer-setup` | Create the orca system user + drift-events dir on a peer (REQ-111) |
|
||||
|
||||
See [docs/cli.md](docs/cli.md) for the full CLI reference with all flags
|
||||
and examples.
|
||||
@@ -114,7 +117,7 @@ acknowledged rather than papered over.
|
||||
| Auto-scaling | Cluster autoscaler, HPA/VPA, deep integrations | — |
|
||||
| Daemon footprint | — | No daemon on the critical path; the cluster is the OS |
|
||||
| OS-native | — | Workloads are systemd units + journald; no container runtime shim |
|
||||
| mTLS | — | mTLS by default; no opt-in required |
|
||||
| Transport | — | SSH-push is canonical (no daemon needed); mTLS available for daemon mode |
|
||||
| Offline-first | — | No cloud dependencies; fully air-gapped operation |
|
||||
| WASM-first | — | Workloads target OS primitives, not a container runtime |
|
||||
| Proxmox | — | First-class Proxmox node type (`--type proxmox`) via SSH-push |
|
||||
@@ -129,6 +132,10 @@ acknowledged rather than papered over.
|
||||
| [docs/namespace.md](docs/namespace.md) | Namespace and path layout |
|
||||
| [docs/install.md](docs/install.md) | Installation guide |
|
||||
| [docs/security-scanning.md](docs/security-scanning.md) | Security scanning tools |
|
||||
| [docs/security-runbook.md](docs/security-runbook.md) | Security runbook — seal/unseal, rotation, incident response |
|
||||
| [docs/webauthn.md](docs/webauthn.md) | WebAuthn / passkeys registration and login |
|
||||
| [docs/threat-model.md](docs/threat-model.md) | STRIDE threat model + zero-trust architecture |
|
||||
| [docs/oidc.md](docs/oidc.md) | OIDC configuration — Dex quickstart, BYO IdP |
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -144,6 +151,7 @@ make test # Run tests
|
||||
go vet ./... # Vet all packages
|
||||
make lint # Run gofmt + go vet + shellcheck
|
||||
make verify-reqs # Assert ROADMAP ↔ REQUIREMENTS consistency
|
||||
make verify-docs # Assert docs/cli.md ↔ `orca --help` consistency
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
+24
-10
@@ -16,17 +16,20 @@ import (
|
||||
// the Phase + Status match at the END of the line, where those two columns
|
||||
// always live. The status token is optionally wrapped in markdown bold
|
||||
// (real rows use `**Complete**`; synthetic/future rows may use bare
|
||||
// `Pending`), and may carry trailing notes (e.g. "**Complete** (P01
|
||||
// shipped v0.2.1)") matched by [^|]* before the closing pipe.
|
||||
var reqRowRe = regexp.MustCompile(`^\|\s*(REQ-\d+)\s*\|.*\|\s*([^|]*?)\s*\|\s*\*{0,2}(Complete|Pending)\*{0,2}[^|]*\|\s*$`)
|
||||
// `pending` or `complete` in any case), and may carry trailing notes
|
||||
// (e.g. "**Complete** (P01 shipped v0.2.1)") matched by [^|]* before
|
||||
// the closing pipe. The (?i) flag makes the match case-insensitive so
|
||||
// lowercase `pending` (used by v0.12/v0.13 REQ rows) is captured;
|
||||
// normalizeStatus canonicalizes the captured value to title case.
|
||||
var reqRowRe = regexp.MustCompile(`(?i)^\|\s*(REQ-\d+)\s*\|.*\|\s*([^|]*?)\s*\|\s*\*{0,2}(Complete|Pending)\*{0,2}[^|]*\|\s*$`)
|
||||
|
||||
// milestoneCompleteRe matches a ROADMAP.md milestone header that is marked
|
||||
// COMPLETE. The bold span is substring-tolerant (GRILL #4): it matches
|
||||
// `**COMPLETE**`, `**COMPLETE (merged to main via v0.3)**`, and any future
|
||||
// variant where the word COMPLETE appears inside the bold span, possibly
|
||||
// preceded or followed by non-asterisk text. The milestone version (v0.X)
|
||||
// is captured.
|
||||
var milestoneCompleteRe = regexp.MustCompile(`^##\s*Milestone\s+(v0\.\d+):.*—\s*\*\*[^*]*\bCOMPLETE\b[^*]*\*\*`)
|
||||
// COMPLETE. The bold markers are optional (GRILL #4 + REQ-160 T11): it
|
||||
// matches `**COMPLETE**`, `**COMPLETE (merged to main via v0.3)**`, and
|
||||
// bare `COMPLETE` (as used by the v0.12 milestone header). The word
|
||||
// COMPLETE may be preceded or followed by non-asterisk text. The milestone
|
||||
// version (v0.X) is captured.
|
||||
var milestoneCompleteRe = regexp.MustCompile(`^##\s*Milestone\s+(v0\.\d+):.*—\s*\*{0,2}[^*]*\bCOMPLETE\b[^*]*\*{0,2}`)
|
||||
|
||||
// phaseRe extracts the milestone version from a REQUIREMENTS Phase cell such
|
||||
// as `v0.7 P1`, `**v0.2 P01**`, `v0.2 P01–P04`, or bare `v0.7`. The cell may
|
||||
@@ -40,6 +43,17 @@ type reqRow struct {
|
||||
status string // "Complete" or "Pending"
|
||||
}
|
||||
|
||||
// normalizeStatus canonicalizes a captured status token to the title-case
|
||||
// form ("Complete" or "Pending") so that case-insensitive matches like
|
||||
// "pending" or "complete" compare correctly against the drift assertions.
|
||||
func normalizeStatus(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
|
||||
// milestoneVersions returns the distinct v0.X milestones referenced in the
|
||||
// phase cell (e.g. "v0.7 P1" → ["v0.7"]; "v0.2 P01 / v0.3 P02" →
|
||||
// ["v0.2","v0.3"]).
|
||||
@@ -174,7 +188,7 @@ func parseRequirements(path string) ([]reqRow, error) {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, reqRow{id: m[1], phase: strings.TrimSpace(m[2]), status: m[3]})
|
||||
rows = append(rows, reqRow{id: m[1], phase: strings.TrimSpace(m[2]), status: normalizeStatus(m[3])})
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
+1069
-126
File diff suppressed because it is too large
Load Diff
+62
-13
@@ -8,8 +8,8 @@ directory holds cluster-wide artifacts shared across namespaces.
|
||||
|
||||
> **v0.9 layout (canonical)**: This document describes the v0.9
|
||||
> multi-namespace layout. The v0.8 flat layout (`orca.db`, `ca.crt`,
|
||||
> `server.crt` at the root) is deprecated and will be removed in
|
||||
> v0.11. See [v0.8 flat layout](#deprecated-v08-flat-layout) below.
|
||||
> `server.crt` at the root) is deprecated and removed in v0.12
|
||||
> (REQ-138).
|
||||
|
||||
## Namespace root resolution
|
||||
|
||||
@@ -51,12 +51,16 @@ $ORCA_HOME/
|
||||
├── cluster/ # cluster-wide (NOT a workload namespace)
|
||||
│ ├── ca.crt, ca.key # step-ca root (R-006, D-101)
|
||||
│ ├── master.key # AES-256-GCM root (R-011, mode 0600)
|
||||
│ ├── master.key.sealed # sealed master key (REQ-147, mode 0600)
|
||||
│ ├── config.md # Markdown frontmatter config (R-014)
|
||||
│ ├── known_hosts # SSH known_hosts (D-035)
|
||||
│ ├── orca_ssh_key # orca SSH private key (D-037)
|
||||
│ ├── orca_ssh_key.pub # orca SSH public key
|
||||
│ ├── peers/<host>/ # per-peer directory
|
||||
│ ├── txns/ # cluster transaction log (R-016)
|
||||
│ ├── acl.json # ACL state (mode 0600)
|
||||
│ ├── oidc-client-secret # OIDC client secret (mode 0600, C-36)
|
||||
│ ├── webauthn-credentials.db # WebAuthn public keys (mode 0600)
|
||||
│ └── state/ # cluster state
|
||||
├── _defaults/ # implicit root namespace (always exists)
|
||||
│ ├── ns.md # namespace frontmatter (kind: Namespace)
|
||||
@@ -80,14 +84,16 @@ $ORCA_HOME/
|
||||
exists. Every namespace inherits from `_defaults` and cannot opt out
|
||||
(D-185, D-187).
|
||||
- **`cluster/`** is NOT a workload namespace — it holds cluster-wide
|
||||
artifacts (CA, master key, SSH keys, known_hosts, peers, txns).
|
||||
artifacts (CA, master key, SSH keys, known_hosts, peers, txns, ACL,
|
||||
OIDC secrets, WebAuthn credentials).
|
||||
- **Per-namespace DBs**: each namespace has its own
|
||||
`db/orca.db` (R-002). No namespace column in SQLite.
|
||||
- **Namespace inheritance**: child namespaces inherit env and
|
||||
constraints from parents (via `ns.md` frontmatter `parents:` field).
|
||||
`_defaults` is always appended last in the inheritance chain.
|
||||
- **`orca ns` subcommands**: `list`, `create`, `delete`, `inspect`,
|
||||
`validate` — see [docs/cli.md](cli.md#orca-ns).
|
||||
`validate`, `inherit`, `set-constraint` — see below and
|
||||
[docs/cli.md](cli.md#orca-ns).
|
||||
|
||||
### Path reference (`internal/paths/`)
|
||||
|
||||
@@ -134,6 +140,50 @@ orca ns validate prod
|
||||
orca ns delete staging
|
||||
```
|
||||
|
||||
### `orca ns inherit` — set parent namespace (R-002)
|
||||
|
||||
Set the parent namespace for a namespace. Updates `ns.md` frontmatter
|
||||
(`parents` field) and validates the new chain has no cycles. The
|
||||
implicit root `_defaults` is always appended last (D-185).
|
||||
|
||||
```bash
|
||||
orca ns inherit <name> --parent <parent-namespace>
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
# Make staging inherit from prod (chain: staging -> prod -> _defaults)
|
||||
orca ns inherit staging --parent prod
|
||||
```
|
||||
|
||||
The child cannot inherit from itself transitively — the resolver
|
||||
validates the chain before writing. If a cycle is detected, the
|
||||
command exits 1 with an error.
|
||||
|
||||
### `orca ns set-constraint` — set a constraint (R-002)
|
||||
|
||||
Set a constraint on a namespace. Constraints are `key=value` strings
|
||||
(e.g., `max-allocs=10`) stored in `ns.md` frontmatter and unioned
|
||||
across the inheritance chain by the resolver.
|
||||
|
||||
```bash
|
||||
orca ns set-constraint <name> <key>=<value>
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
# Limit prod to 10 concurrent allocations
|
||||
orca ns set-constraint prod max-allocs=10
|
||||
|
||||
# Set a required node affinity
|
||||
orca ns set-constraint prod require-label=ssd
|
||||
```
|
||||
|
||||
Constraints are unioned (not overridden) across the inheritance chain:
|
||||
if `_defaults` sets `max-allocs=50` and `prod` sets `max-allocs=10`,
|
||||
the effective constraint is the most restrictive one (CEL evaluation
|
||||
determines precedence per constraint key).
|
||||
|
||||
See [docs/cli.md](cli.md#orca-ns) for the full `orca ns` reference.
|
||||
|
||||
## `ORCA_DB` override
|
||||
@@ -148,11 +198,10 @@ orca init # uses /tmp/test.db for the DB, ~/.orca/ for everything else
|
||||
|
||||
## Deprecated: v0.8 flat layout
|
||||
|
||||
> **Deprecated in v0.9**: The v0.8 flat layout (`orca.db`, `ca.crt`,
|
||||
> `ca.key`, `server.crt`, `server.key` at the namespace root) is
|
||||
> superseded by the v0.9 multi-namespace layout (R-002). The v0.8
|
||||
> layout is supported during the dual-write window via
|
||||
> `internal/certpaths` (a thin shim) and will be removed in v0.11.
|
||||
> **Removed in v0.12** (REQ-138): The v0.8 flat layout (`orca.db`,
|
||||
> `ca.crt`, `ca.key`, `server.crt`, `server.key` at the namespace root)
|
||||
> is superseded by the v0.9 multi-namespace layout (R-002) and the
|
||||
> dual-write window is closed.
|
||||
|
||||
The v0.8 flat layout stored all state at the namespace root:
|
||||
|
||||
@@ -166,12 +215,12 @@ The v0.8 flat layout stored all state at the namespace root:
|
||||
|
||||
The v0.9 re-architecture moved these to `cluster/` (CA, SSH keys) and
|
||||
per-namespace `db/` (SQLite) to support multi-tenancy (R-002). The
|
||||
`orca doctor --legacy-paths` command (v0.11-P14c) will detect v0.8
|
||||
residue and recommend migration.
|
||||
`internal/certpaths` shim that supported the dual-write window is
|
||||
removed in v0.12.
|
||||
|
||||
## See also
|
||||
|
||||
- [Install Guide](install.md) — 1-liner install with `install.sh`.
|
||||
- [Docker Guide](docker.md) — running orca in a container.
|
||||
- [CLI Reference](cli.md) — `orca ns` subcommands.
|
||||
- [Jobspec Reference](jobspec.md) — markdown frontmatter schema.
|
||||
- [CLI Reference](cli.md#orca-ns) — `orca ns` subcommands.
|
||||
- [Jobspec Reference](jobspec.md) — markdown frontmatter schema.
|
||||
|
||||
+138
-21
@@ -1,31 +1,148 @@
|
||||
# Security Runbook (v0.12)
|
||||
# Security Runbook (v0.13)
|
||||
|
||||
## Master Key Seal/Unseal
|
||||
This runbook documents the operational security procedures for orca's
|
||||
zero-trust identity model (R-021): human identity is exclusively
|
||||
external (OIDC), machine identity is exclusively mTLS/SPIFFE, and no
|
||||
passwords / Orca-issued tokens / CA-key passphrases exist anywhere in
|
||||
the system. The v0.12 milestone shipped these capabilities; the v0.13
|
||||
milestone wired them operationally (R-023).
|
||||
|
||||
- `orca cluster seal`: encrypts master key with OIDC-derived key;
|
||||
prints 5 Shamir shards for offline recovery.
|
||||
- `orca cluster unseal`: operator authenticates via OIDC; master key
|
||||
unwrapped into memory; zeroed on shutdown.
|
||||
- `orca cluster unseal --recovery`: if IdP lost, present 3 of 5 shards.
|
||||
## Master Key Seal/Unseal (REQ-147, P05)
|
||||
|
||||
## Master Key Rotation
|
||||
The cluster master key (`ClusterDir()/master.key`, mode 0600) encrypts
|
||||
all namespace `.env.secrets` via per-namespace HKDF-SHA256 sub-keys
|
||||
(AES-256-GCM). The master key can be **sealed** (encrypted at rest) and
|
||||
**unsealed** (unwrapped into memory for use).
|
||||
|
||||
`orca secrets rotate-master [--dry-run]`: generates new master key,
|
||||
re-encrypts all namespace secrets, re-seals. Atomic + automatic rollback.
|
||||
### Seal
|
||||
|
||||
```bash
|
||||
orca cluster seal
|
||||
```
|
||||
|
||||
Encrypts the raw master key with a key derived from either:
|
||||
- the OIDC ID token subject (if `orca auth login` has been run), or
|
||||
- the cluster CA fingerprint (mTLS-only offline path, D-241).
|
||||
|
||||
The sealed blob is written to `ClusterDir()/master.key.sealed` (0600).
|
||||
**Five Shamir shards (3-of-5 recovery)** are printed to stdout — store
|
||||
them offline. The raw master key is then deleted from disk so the
|
||||
cluster is sealed at rest.
|
||||
|
||||
### Unseal
|
||||
|
||||
```bash
|
||||
orca cluster unseal
|
||||
```
|
||||
|
||||
Reads the sealed blob and unwraps the master key using the OIDC ID
|
||||
token subject or the cluster CA fingerprint. The unwrapped key is
|
||||
written back to `ClusterDir()/master.key` (0600) and zeroed from
|
||||
memory on process exit.
|
||||
|
||||
### Recovery (IdP lost)
|
||||
|
||||
```bash
|
||||
orca cluster unseal --recovery
|
||||
```
|
||||
|
||||
If the IdP is permanently lost, the operator is prompted for 3 of the
|
||||
5 Shamir shards printed at seal time. With quorum, the master key is
|
||||
reconstructed and written back to disk. If quorum is unavailable, the
|
||||
cluster is unrecoverable by design (C-35: no backdoor).
|
||||
|
||||
## Master Key Rotation (REQ-129, C-30)
|
||||
|
||||
```bash
|
||||
orca secrets rotate-master [--dry-run]
|
||||
```
|
||||
|
||||
Generates a new master key, re-encrypts every namespace's
|
||||
`.env.secrets` under the new key, and re-seals the master key to OIDC.
|
||||
With `--dry-run`, reports affected namespaces without writing.
|
||||
|
||||
- **Atomic per-namespace**: each namespace is re-encrypted independently.
|
||||
- **Automatic rollback**: on any namespace failure, the old sealed key
|
||||
is restored (C-30).
|
||||
- **No passphrase** (R-021): the master key is sealed to OIDC, not to a
|
||||
human-typed passphrase.
|
||||
|
||||
## File-Mode Audit (REQ-033, REQ-130, F13)
|
||||
|
||||
```bash
|
||||
orca doctor modes
|
||||
```
|
||||
|
||||
Verifies file modes on security-sensitive files across `ORCA_HOME`:
|
||||
- private keys / secrets: `0600`
|
||||
- certs / public keys: `0644`
|
||||
|
||||
Exits 0 if all files have correct modes; exits 1 if any violation is
|
||||
found. Missing files are not counted as violations.
|
||||
|
||||
Checks: SSH key, master key (sealed blob), server cert/key,
|
||||
known_hosts, `acl.json`, OIDC client secret.
|
||||
|
||||
## Audit Log Tamper-Evidence (REQ-125, F2)
|
||||
|
||||
```bash
|
||||
orca doctor audit
|
||||
```
|
||||
|
||||
Verifies the audit log hash chain. Opens the orca SQLite DB, recomputes
|
||||
the hash chain from the first audit entry, and reports the chain head
|
||||
hash. If any entry's `entry_hash` or `prev_hash` link does not match the
|
||||
recomputed value, the chain has been tampered with and the command
|
||||
exits non-zero.
|
||||
|
||||
The audit log is append-only (SQLite trigger blocks
|
||||
UPDATE/DELETE). Each entry's `actor` field carries the OIDC `sub` or
|
||||
SPIFFE SVID. Run this after any suspected intrusion or as part of a
|
||||
regular audit cadence.
|
||||
|
||||
## Sudoers Audit (REQ-134, F22)
|
||||
|
||||
```bash
|
||||
orca doctor proxmox
|
||||
```
|
||||
|
||||
Audits the `/etc/sudoers.d/orca` file against the expected allowlist:
|
||||
- `pct` + `qm` with NOEXEC
|
||||
- `apt-get` / `dpkg` excluded (or NOEXEC'd)
|
||||
- `pvesh` EXCLUDED (AD-020: pvesh can bypass NOEXEC via the API execute
|
||||
endpoint)
|
||||
|
||||
## nft Audit (REQ-133, F21)
|
||||
|
||||
```bash
|
||||
orca doctor nft
|
||||
```
|
||||
|
||||
Audits the live nftables ingress ruleset against the on-disk
|
||||
`/etc/nftables.d/orca.nft` hash (recorded at the latest applied txn).
|
||||
Reports drift if the live ruleset does not match. Also verifies:
|
||||
- table exists
|
||||
- DNAT `:443 → 127.0.0.1:8443` and `:80 → 127.0.0.1:8080` present
|
||||
- rate-limit meter present
|
||||
- `/etc/nftables.d/orca.nft` parses
|
||||
|
||||
## Incident Response
|
||||
|
||||
1. Revoke the compromised identity (OIDC user/group or SPIFFE SVID).
|
||||
2. Rotate the master key (`orca secrets rotate-master`).
|
||||
3. Review the audit log (`orca doctor audit` verifies the hash chain).
|
||||
4. If the master key is compromised, all historical secrets are
|
||||
compromised (no forward secrecy).
|
||||
1. **Revoke the compromised identity** (OIDC user/group or SPIFFE SVID).
|
||||
2. **Rotate the master key** (`orca secrets rotate-master`).
|
||||
3. **Review the audit log** (`orca doctor audit` verifies the hash
|
||||
chain; `orca audit list` shows entries).
|
||||
4. **Check file modes** (`orca doctor modes` detects permission drift).
|
||||
5. If the master key is compromised, **all historical secrets are
|
||||
compromised** (no forward secrecy — documented residual risk).
|
||||
6. **Re-seal** the master key after rotation (`orca cluster seal`).
|
||||
|
||||
## Sudoers Audit
|
||||
## OIDC Provider Health (P06)
|
||||
|
||||
`orca doctor proxmox` audits the `/etc/sudoers.d/orca` file against the
|
||||
expected allowlist (pct + qm with NOEXEC; apt-get/dpkg excluded).
|
||||
```bash
|
||||
orca doctor oidc
|
||||
```
|
||||
|
||||
## nft Audit
|
||||
|
||||
`orca doctor nft` audits the live nftables ruleset against the emitted one.
|
||||
Checks the bundled Dex OIDC provider health. Verifies the Dex systemd
|
||||
unit is running and the `/.well-known/openid-configuration` endpoint
|
||||
responds. Run after `orca auth init-idp` or after a Dex config change.
|
||||
|
||||
+84
-14
@@ -1,27 +1,97 @@
|
||||
# WebAuthn / Passkeys (v0.12)
|
||||
# WebAuthn / Passkeys (v0.13)
|
||||
|
||||
## Overview
|
||||
|
||||
The bundled Dex uses a custom WebAuthn connector for password-free
|
||||
authentication. Passkeys are public-key credentials — the private key
|
||||
never leaves the authenticator (TPM/security key/phone Secure Enclave).
|
||||
The bundled Dex uses a custom WebAuthn connector (`orca-webauthn-connector`,
|
||||
REQ-148) for password-free authentication. Passkeys are public-key
|
||||
credentials — the private key never leaves the authenticator (TPM /
|
||||
security key / phone Secure Enclave). This directly satisfies R-021
|
||||
(no Orca-issued credentials): the authenticator proves possession of
|
||||
the private key without ever exposing it.
|
||||
|
||||
The WebAuthn connector ships as part of the v0.12 milestone (P05) and
|
||||
is operationally wired in v0.13 (P04: registration requires auth; P06:
|
||||
real Dex deployment).
|
||||
|
||||
## Registration
|
||||
|
||||
`orca auth register` opens the browser to the Dex WebAuthn endpoint.
|
||||
After the ceremony (biometric/security key), Dex maps the credential
|
||||
ID to an OIDC `sub`. Credentials stored at
|
||||
`ClusterDir()/webauthn-credentials.db` (0600, public keys only).
|
||||
```bash
|
||||
orca auth register [--no-browser]
|
||||
```
|
||||
|
||||
Opens the browser to the Dex WebAuthn registration page at
|
||||
`https://<cluster>/orca/webauthn/register`. The operator authenticates
|
||||
via an existing session or admin bootstrap token, then performs the
|
||||
WebAuthn ceremony (biometric or security key). After the ceremony,
|
||||
Dex maps the credential ID to an OIDC `sub`.
|
||||
|
||||
- **`--no-browser`**: print the registration URL instead of opening a
|
||||
browser (useful for headless operators or remote SSH sessions — copy
|
||||
the URL into a local browser).
|
||||
|
||||
Credentials are stored at `ClusterDir()/webauthn-credentials.db`
|
||||
(mode 0600, public keys only — private keys never leave the
|
||||
authenticator and are never stored by orca).
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
# Interactive (opens browser)
|
||||
orca auth register
|
||||
|
||||
# Headless / remote SSH (print URL)
|
||||
orca auth register --no-browser
|
||||
# → https://orca.local/orca/webauthn/register
|
||||
```
|
||||
|
||||
## RP ID
|
||||
|
||||
The relying-party ID is the cluster's Traefik-served domain
|
||||
(`--rp-id` on `orca auth init-idp`). HTTPS secure context is provided
|
||||
by Traefik (step-ca cert, R-017).
|
||||
The relying-party ID is the cluster's Traefik-served domain, set via
|
||||
`--rp-id` on `orca auth init-idp` (C-38). The RP ID **must** match the
|
||||
cluster's Traefik domain — WebAuthn enforces that the RP ID is a
|
||||
registrable domain suffix of the current origin.
|
||||
|
||||
HTTPS secure context is provided by Traefik (step-ca cert, R-017).
|
||||
WebAuthn requires a secure context (HTTPS or localhost); the step-ca
|
||||
cert behind Traefik satisfies this.
|
||||
|
||||
## Bootstrap Sequence
|
||||
|
||||
1. `orca init` bootstraps the cluster CA (step-ca, mTLS-only).
|
||||
2. `orca auth init-idp` deploys Dex behind Traefik (step-ca cert).
|
||||
3. First operator registers a passkey via the mTLS-authenticated session.
|
||||
4. Subsequent operators use WebAuthn.
|
||||
2. `orca auth init-idp --rp-id <cluster-domain>` deploys Dex behind
|
||||
Traefik (step-ca cert) with the WebAuthn connector configured.
|
||||
3. First operator authenticates via an existing session or admin
|
||||
bootstrap token, then registers a passkey:
|
||||
|
||||
```bash
|
||||
orca auth register
|
||||
```
|
||||
|
||||
4. Subsequent operators use WebAuthn login (`orca auth login` opens
|
||||
the browser to the Dex login page; the WebAuthn ceremony is one of
|
||||
the available upstreams).
|
||||
|
||||
## Health Check
|
||||
|
||||
```bash
|
||||
orca doctor oidc
|
||||
```
|
||||
|
||||
Verifies the bundled Dex OIDC provider is running and the
|
||||
`/.well-known/openid-configuration` endpoint responds. Run after
|
||||
`orca auth init-idp` or after a Dex config change.
|
||||
|
||||
## Security properties
|
||||
|
||||
- **No passwords**: WebAuthn is password-free. No password is ever
|
||||
sent to or stored by orca (R-021).
|
||||
- **Phishing-resistant**: the WebAuthn protocol cryptographically binds
|
||||
the ceremony to the RP ID, defeating credential phishing.
|
||||
- **Private key never leaves the authenticator**: orca stores only
|
||||
public keys.
|
||||
- **Secure context required**: HTTPS via step-ca / Traefik (C-38).
|
||||
|
||||
## See also
|
||||
|
||||
- [docs/oidc.md](oidc.md) — OIDC configuration (Dex quickstart, BYO IdP)
|
||||
- [docs/security-runbook.md](security-runbook.md) — security runbook
|
||||
- [docs/cli.md](cli.md#orca-auth) — `orca auth` CLI reference
|
||||
|
||||
+3
-3
@@ -47,8 +47,8 @@ var (
|
||||
)
|
||||
|
||||
var jobRunCmd = &cobra.Command{
|
||||
Use: "run <spec.hcl>",
|
||||
Short: "Run a job from an HCL spec file",
|
||||
Use: "run <spec.md>",
|
||||
Short: "Run a job from a markdown spec file",
|
||||
Long: "Submit a job spec, execute its tasks, and persist the result. Use --target to pin to a specific node (overrides bin-packing); --idempotency-key for cross-node dispatch dedupe.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -216,7 +216,7 @@ func renderJobs(cmd *cobra.Command, jobs []*model.Job) error {
|
||||
return printJSON(jobs)
|
||||
}
|
||||
if len(jobs) == 0 {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), "No jobs. Use 'orca job run <spec.hcl>' to submit one.")
|
||||
fmt.Fprintln(cmd.OutOrStdout(), "No jobs. Use 'orca job run <spec.md>' to submit one.")
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "%-36s %-20s %-12s %-8s\n", "ID", "NAME", "STATUS", "EXIT")
|
||||
|
||||
+15
-3
@@ -6,9 +6,21 @@ import (
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show orca daemon status",
|
||||
Long: "Display the current status of the local orca daemon, including version, uptime, and connection info.",
|
||||
Short: "Show orca daemon status (deprecated)",
|
||||
Long: `Display the current status of the local orca daemon, including
|
||||
version, uptime, and connection info.
|
||||
|
||||
**Deprecated (v0.13):** This command is a v0.1 stub that reports a
|
||||
hardcoded "daemon stopped" status. The daemon model was replaced by
|
||||
SSH-push in v0.9 (R-001) and the dual-write window closed in v0.12
|
||||
(REQ-138). Use the canonical commands instead:
|
||||
|
||||
orca node list # node registry + state
|
||||
orca metrics /healthz # liveness/health probe (daemon-mode only)
|
||||
|
||||
This command will be removed in a future release.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
warnDeprecated("orca status is deprecated (v0.1 stub): use 'orca node list' for node state and 'orca metrics /healthz' for health probes")
|
||||
status := map[string]any{
|
||||
"version": version,
|
||||
"daemon": "stopped",
|
||||
@@ -23,7 +35,7 @@ var statusCmd = &cobra.Command{
|
||||
}
|
||||
printText("orca daemon status\n")
|
||||
printText(" version: %s\n", version)
|
||||
printText(" daemon: %s\n", "stopped (daemon not yet implemented in Phase 1)")
|
||||
printText(" daemon: %s\n", "stopped (deprecated v0.1 stub; use 'orca node list' + 'orca metrics /healthz')")
|
||||
printText(" api_addr: %s\n", "https://localhost:8443")
|
||||
printText(" phase: %s\n", "1-cli-skeleton")
|
||||
printText(" milestone: %s\n", "v0.1")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// The bootstrap sequence (run via `orca node join --type proxmox`):
|
||||
// 1. Generate or load the orca SSH keypair (Ed25519, D-037)
|
||||
// 2. SSH dial with password auth + TOFU host-key capture (D-035)
|
||||
// 2. SSH dial with key auth + TOFU host-key capture (D-035)
|
||||
// 3. Deploy the orca pubkey to ~orca/.ssh/authorized_keys
|
||||
// 4. Create the `orca` Linux system user (config-overridable name)
|
||||
// 5. Create the OrcaOperator PVE role with least-privilege privileges
|
||||
@@ -16,9 +16,9 @@
|
||||
// 10. Return the node metadata for the caller to persist
|
||||
//
|
||||
// All steps are idempotent (D-036): re-running the bootstrap on an
|
||||
// already-configured host is a no-op. The password is never persisted
|
||||
// (D-031) — it is used only for the initial SSH auth and pubkey
|
||||
// deployment; subsequent orca→Proxmox access uses the deployed SSH key.
|
||||
// already-configured host is a no-op. Authentication is key-based (R-021)
|
||||
// (D-031): the orca SSH key is used for the initial SSH auth and
|
||||
// pubkey deployment; subsequent orca→Proxmox access uses the same key.
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
@@ -143,7 +143,7 @@ func BootstrapProxmox(ctx context.Context, opts Options) (*Result, error) {
|
||||
return nil, fmt.Errorf("ssh key: %w", err)
|
||||
}
|
||||
|
||||
// Step 2: SSH dial with password auth + host-key verification (D-035,
|
||||
// Step 2: SSH dial with key auth + host-key verification (D-035,
|
||||
// REQ-058). When opts.HostKeyFingerprint is set (D-044), use a pinned
|
||||
// callback that fails closed on mismatch (AD-028); otherwise use the
|
||||
// TOFU known_hosts capture callback (D-035). The TOFU wrapper fixes
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# verify-docs.sh — assert that every subcommand documented in docs/cli.md
|
||||
# exists in `orca --help` output (and vice versa). Catches doc drift.
|
||||
#
|
||||
# Usage: scripts/verify-docs.sh [binary] [docs/cli.md]
|
||||
# Exit 0 = consistent, 1 = drift detected, 2 = error.
|
||||
set -euo pipefail
|
||||
|
||||
BIN="${1:-./bin/orca}"
|
||||
DOCS="${2:-docs/cli.md}"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "verify-docs: binary not found at $BIN (run 'make build' first)" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -f "$DOCS" ]; then
|
||||
echo "verify-docs: $DOCS not found" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Extract top-level commands from `orca --help` (lines indented under
|
||||
# "Available Commands:" with two leading spaces, command name is the
|
||||
# first token).
|
||||
HELP_OUTPUT="$("$BIN" --help 2>/dev/null)"
|
||||
HELP_CMDS="$(echo "$HELP_OUTPUT" | \
|
||||
awk '/^Available Commands:/{flag=1; next} /^$/{flag=0} flag && /^ /{print $1}' | \
|
||||
grep -v '^completion$' | grep -v '^help$' | sort -u)"
|
||||
|
||||
# Extract documented commands from docs/cli.md. These appear as
|
||||
# `## \`orca <command>\`` or `## \`orca <command>\` *(deprecated)*` headers.
|
||||
DOC_CMDS="$(grep -oE '^## `orca [a-z_-]+`' "$DOCS" | \
|
||||
sed 's/^## `orca //; s/`$//' | sort -u)"
|
||||
|
||||
# Compare.
|
||||
diff_out="$(diff <(echo "$HELP_CMDS") <(echo "$DOC_CMDS") || true)"
|
||||
|
||||
if [ -n "$diff_out" ]; then
|
||||
echo "verify-docs: drift detected between docs/cli.md and \`orca --help\`:" >&2
|
||||
echo "$diff_out" >&2
|
||||
echo "" >&2
|
||||
echo "Commands in --help but not in docs/cli.md (add them):" >&2
|
||||
comm -23 <(echo "$HELP_CMDS") <(echo "$DOC_CMDS") >&2
|
||||
echo "" >&2
|
||||
echo "Commands in docs/cli.md but not in --help (remove them or fix typo):" >&2
|
||||
comm -13 <(echo "$HELP_CMDS") <(echo "$DOC_CMDS") >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "verify-docs: OK — docs/cli.md consistent with orca --help"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user