Security hardening, 2026 feature set, public-repo restructure (v0.9.0)#5
Merged
Conversation
… atomic releases, OTel, secrets, cosign) Implements the full backlog from PORTER_IMPROVEMENT_PLAN.md against 2026-current research. Zero new Go module dependencies (external-tool features shell out to sops/cosign by design). Security (Phase 0): - hostkey.go: verified host keys across all SSH/scp/rsync paths — TOFU (pins on first use, rejects changed keys as MITM) / strict / insecure modes, plus TrustHostCA for step-ca host certificates. Drops every InsecureIgnoreHostKey and StrictHostKeyChecking=no. - Sudo password fed via `printf '%s\n' '<quoted>' | sudo -S -p ''` (never echo, never the process table, quoted against injection) — core executor + all 25 porterui sites via porterui/shell.go (sudoStdin/shellQuote). sshpass moved to SSHPASS env. - porterui auth wiring fixed: SetupRoutesWithAuth now actually enforces (r.Use(AuthMiddleware) + self-filtering public/agent allowlist), gated by PORTER_AUTH. React auth fails closed. AES crypto fails closed. admin/admin replaced by random/PORTER_ADMIN_PASSWORD. WS CheckOrigin same-origin (porterui/wsorigin.go). scp fallback -> accept-new. Features (Phases 1-3): - ensure.go: EnsureFile/Dir/Symlink/Package/Line/ServiceRunning/ServiceEnabled fact-gather -> diff -> no-op, with a real --dry-run preview. - executor.go: Changed stat now meaningful (exec returns changed; read-only and converged tasks report ok-not-changed). - release.go: atomic releases + health-gated rename(2) symlink swap + Rollback, for systemd/VM targets. - tracer.go: deploy-as-an-OTel-trace (JSONL spans, stable deployment.environment.name) + slog structured logs with trace_id correlation. - secrets.go: Secret() decrypts via SOPS+age locally, ships 0600 over SFTP, never logged. - verify.go: VerifyBlob/VerifyImage cosign pre-deploy admission gate. - sshcert.go: ConnectWithCert (step-ca/Vault SSH user certs) + StartKeepalive; Config.Port. Tests: hostkey_test, features_test, porterui/auth_middleware_test, porterui/security_test. Build/vet/test all green. Worked example in examples/modern/main.go. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agent-token auth, SSE/CORS + dir-transfer hardening - assert.go: Goss-style health assertions (AssertServiceActive/Enabled, Process, PortListening, FileExists/Contains, Package, HTTPStatus, Command) — read-only fail-closed gates for post-deploy smoke tests / pre-flight guards. - SecretCommand(fetchCmd, dest): pluggable secret backend for Vault / OpenBao / 1Password / Infisical via their CLIs; same 0600/never-logged guarantees as the SOPS Secret(). - PORTER_AGENT_TOKEN: optional shared-secret auth on the agent / standalone-agent / build-client channels (constant-time compare, header or query) so they can be locked down independently of human PORTER_AUTH; open when unset. - SSE endpoints (logs.go, streaming.go): wildcard Access-Control-Allow-Origin:* replaced with same-origin/allowlisted allowSSEOrigin (CSRF on log/output streams). - UploadDir/DownloadDir (connection.go): shell-quote all paths; unpredictable remote temp names via randomID (no /tmp race/symlink attack) instead of time.Now().UnixNano(). PQ-SSH (mlkem768x25519-sha256) confirmed already negotiated by default in the installed x/crypto — no change needed. Build/vet clean; 154 test assertions green (assert/SecretCommand builders, agentTokenValid, agent-channel gate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nded idempotent primitives - bastion.go: ConnectViaJump(target, jumps...) — ProxyJump through one or more bastions with host-key verification at every hop and no agent forwarding (2026 best practice). Hop + PasswordAuth/KeyAuth/AgentAuth helpers. - PORTER_AUTH now secure-by-default: JWT enforced unless explicitly disabled with PORTER_AUTH=0/off/false/no. First-boot random admin password covers the default; agent channels still gated separately by PORTER_AGENT_TOKEN. - porterui deploy tracing: manifest deploys now record an OTel-shaped JSONL trace under <dataDir>/traces/ + structured slog (manifest.go, deploytrace.go). - ensure.go: EnsureCron (fixes cron_add duplicate-append + injection), EnsureUser (fixes non-idempotent useradd), EnsureMode, EnsureOwner, EnsureAbsent, EnsureGitRepo (clone-or-fast-forward) — all idempotent with --dry-run previews. 159 test assertions green; build/vet/gofmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…waterfall viewer
- cmdRunner seam: Executor command execution now goes through a small interface
(*goph.Client satisfies it; NewExecutor wires runner: client). No behavior
change, but runtime_test.go can now drive the dispatch switch with a fake —
closing the audit's "entire runtime untested" gap. Covers ensure_* no-op
detection, writes-when-different, service start, idempotent cron, assertions
(fail-closed), retry attempts, and Changed accounting (read-only vs mutating).
- Trace viewer (porterui/traces.go): GET /api/traces (list), GET
/api/traces/{name} (parsed spans, path-traversal-guarded), and a
self-contained waterfall UI at /traces. Renders the JSONL traces the manifest
deploy path already writes. Tested in traces_test.go.
167 test assertions green; build/vet/gofmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x CI - Remove the committed porterw binary (24MB) and the 88 built UI assets under porterui/build/ from version control; keep a placeholder index.html so the go:embed still compiles, and gitignore the rest (rebuilt via `make ui`). - Move scattered root docs into docs/ (journalctl-examples, mysql-auth, and the internal plan -> docs/ROADMAP.md). - Trim the internal "Engineering Review" section from the README (its findings are addressed); keep the public-facing content. - Add CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, .golangci.yml. - CI: run on push/PR (not just manual), build/test the whole module (was only ./porterui/...), add golangci-lint + gofmt gates. - Makefile: add check/test/lint/fmt/ui targets; stage the UI build for both the CLI and the porterui embed. - gofmt the whole module clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The monolithic switch in executor.go (executor.dispatch) is replaced by an action registry: each action is a small handler registered in init() and looked up by name. Handlers are grouped into cohesive actions_*.go files (file, ensure, assert, secret, exec, archive, pkg, net, systemd, docker, wait, io, build, sysinfo). Behavior-preserving: handler bodies are byte-identical to the former case bodies, and the dispatch-level action set matches exactly (139 cases -> 139 registered handlers, verified by diff). register() panics on a duplicate name so copy-paste mistakes surface at startup. executor.go drops from 1943 to 1205 lines. Build, vet, and all 167 test assertions remain green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/ + cmd/porter-ui Make the importable library (root package porter) the product, and move the optional web dashboard out of the way: - porterui/ -> web/ (package porterui -> package web) - ui/ -> web/ui/ (React frontend under the dashboard subtree) - cmd/porter -> cmd/porter-ui (it is the dashboard server, not a library CLI) Fixes a latent build defect: cmd's `//go:embed build/*` had no committed placeholder, so a fresh clone couldn't `go build ./...`. Both embed sites (web/ and cmd/porter-ui/) now ship a tracked placeholder index.html; the real UI is built with `make ui`. Updates Makefile, Dockerfile, .dockerignore, .gitignore, README, and the package doc for the new paths and the porter-ui binary name. README now states auth is on by default with a generated admin password (the admin/admin note was stale). doc.go documents the full modern API surface. Build, vet, and all tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tions - Add docs/architecture.md: repo layout, core flow, the action registry and how to add an action, idempotency/change accounting, transport security, observability, and the optional dashboard. - Replace the internal audit log at docs/ROADMAP.md with a public roadmap (done / considered next / non-goals); remove internal names and references. - Fix stale porterui references and import paths in web/README.md. - CHANGELOG: document the modern API surface, the registry refactor, the repository restructure, and the security changes. - README: add Documentation, Contributing, and License sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss-compiled) Prevents a built binary at the root from being committed while not matching the cmd/porter-ui source directory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Run staticcheck across the tree and fix all findings: - rsync.go: convert RsyncBuilder to TaskBuilder instead of a struct literal (S1016) - web/agent_ai.go: collapse if-return-bool (S1008), lowercase error strings (ST1005), variadic append instead of append-in-loop (S1011), and remove the dead callOpenAI/callAnthropic (kept-but-policy-blocked) helpers (U1000) - web/machines.go: remove the unused legacy uploadFileToMachine (U1000) - web/scheduler.go: remove the unused updateJobStatus, superseded by updateJobStatusWithError (U1000) - web/dashboard_ws.go: drop the unused closed/mu fields on DashboardClient (U1000) Build, vet, staticcheck, and all tests are clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ran `gopls modernize -fix ./...` over the whole tree: interface{} -> any,
strings.SplitSeq range, and other current Go idioms. Mechanical, tool-verified;
build, vet, staticcheck, and all tests remain clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From a per-domain audit of the shell commands each action emits, apply the no-behavior-surprise fixes: - apt: set DEBIAN_FRONTEND=noninteractive on install/remove/upgrade (was only on the ensure path) so an unattended deploy can't hang on a debconf prompt. - tar: drop the verbose -v from create/extract (cf/xf/czf/xzf) — pure log noise in automation; the archive bytes are unchanged. - curl: add --proto-redir =https --tlsv1.2 so a download can't be silently downgraded https->http on redirect (the initial URL scheme is unaffected). - streaming.go / web/ssh.go: use errors.As for *ssh.ExitError instead of a bare type assertion (handles wrapped errors; modern Go idiom). Behavior-changing suggestions from the audit (docker run --restart/--init/log rotation, npm ci, go release flags, rsync -z default, useradd -m, full-upgrade) are deliberately NOT flipped as defaults; tracked for opt-in modifiers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2026 audit flagged a plain `docker run -d` as missing the defaults a long-lived service wants. Rather than change the default (behavior change), add opt-in builder modifiers: - .Restart(policy) -> --restart <policy> (survive daemon/host restart) - .Init() -> --init (reap zombies, forward signals) - .LogRotate(sz, n) -> --log-opt max-size/max-file (cap json-file logs) buildDockerRun learns the new opts; defaults are unchanged. Tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- README: add an "audited commands" capability bullet. - CHANGELOG: note the safe command defaults (DEBIAN_FRONTEND, quiet tar, curl TLS), the modernize pass, and the new docker run modifiers. - ROADMAP: track the behavior-changing audit findings as future opt-in modifiers (apt --no-install-recommends/full-upgrade, npm ci, release go-build flags, rsync -z, tar --zstd, useradd -m) rather than default flips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings porter to a clean, public-ready v0.9.0.
Security
TrustHostCAstep-ca host certs); SSH-certificate auth (ConnectWithCert), bastion/ProxyJump (ConnectViaJump), keepalives.SSHPASSenv.PORTER_AUTH=0to disable); random first-boot admin password; agent-channel shared-secret auth; WS/SSE origin-checked; AES credential storage fails closed.2026 feature set
Ensure*primitives (fact-gather → diff → no-op) + a real--dry-runpreview; fixedChangedaccounting.Assert*health gates.NewRelease,Rollback).NewTracer) +slog; dashboard/traceswaterfall.Secret) and pluggable CLI backends (SecretCommand).VerifyBlob/VerifyImage.Repo / code quality
web/+cmd/porter-ui.actions_*.go).docs/architecture.md; fixed a fresh-clonego:embedbuild break.DEBIAN_FRONTEND, quiettar, curl TLS hardening,errors.As);gopls modernize; whole module isstaticcheck-clean.Build, vet,
go test -race, gofmt, and staticcheck all green.🤖 Generated with Claude Code