Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

#49

Validate the configuration and harden what a deployment creates

merged · by keenan 13d ago · merged into main at ed181212 by keenan 12d ago

keenan opened this 13d ago

Closes #7. Closes #8.

Two P0 items with one shape: the instance refused to check the state it was about to run in. Together they make vole serve fail on a configuration that cannot be safe and fix the file modes an earlier version left behind.

#7 — Validate the configuration and guard dev_login

Config.Validate() runs before anything is opened and reports every problem at once. It refuses:

  • dev_login without dev_only, with https_addr, with a non-loopback public_url, or with a non-loopback http_addr
  • open_registration without an HTTPS public_url, and open_registration_role that names admin or no role at all
  • a public_url with a user, path, query or fragment, or another scheme
  • a pages_domain that is not a bare DNS suffix, and a pages_ref git would refuse
  • a negative size, lease or limit; receive_max_bytes below max_blob_bytes
  • https_addr with no name to certify, a non-address acme_email, an acme_directory_url that is not HTTPS

dev_only is the explicit opt-in the issue asked for: a credential-free session has to be a decision, not a line copied from an example.

The proxy hole is closed at the request too. isLoopbackRequest trusted Host, so a proxy rewriting it to 127.0.0.1 inherited a dev session. It is now loopbackBrowser: the peer must be loopback, the request must name the configured http_addr (same port; same host or another loopback name, so localhost still works), and any request carrying Forwarded, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto or X-Real-IP is refused.

#8 — Harden deployment file modes and the service unit

internal/secure is one place that creates a secret and checks the mode of one that already exists: 0700 directories, 0600 files, group access retained because 0750 with a dedicated group is a deliberate deployment, world access not. Startup hardens the data directory, users.db and its -wal/-shm, the index and run databases, the audit log, the SSH host key and the ACME cache, logs the paths whose mode it changed, and fails when a mode cannot be tightened — an instance that cannot protect its keys should not serve them. Every audit writer now uses secure.OpenAppend, so an existing log inherited at 0644 stops being readable by everyone the next time it is written. vole admin account backup tightens its copy too: VACUUM INTO created it with the process umask, and that file is every account, key and passkey.

The systemd unit gains UMask=0027, LimitCORE=0 (a core dump of this process is the host key), an empty CapabilityBoundingSet= with a documented drop-in for the one capability port 80/443 needs, PrivateDevices, ProtectProc=invisible, ProcSubset=pid, ProtectKernelLogs, RemoveIPC, RestrictAddressFamilies=, and MemoryDenyWriteExecute. systemd-analyze verify accepts it; the forge runs no JIT, and the container runtime a CI runner needs is a separate process.

docs/operations.md gains a production baseline (loopback web behind TLS, firewalled SSH, conservative limits, audited administration, a restore that has been tested) and docs/packaging.md documents the capability drop-in.

Tests

  • forge/config: every refusal above, a development instance that must pass, a production instance that must pass, multi-error reporting, and a check that the duplicated account-name rule still matches the roster’s.
  • internal/secure: inherited 0644 tightened, 0750 left alone, 0777 losing world access, append keeping earlier lines, only changed paths reported, directory/file mismatches refused.
  • server: a fresh instance has no world-readable roster, host key or audit log; an instance whose secrets were 0644 is tightened by a restart and still starts; a backup is not readable beyond its owner.
  • server/web: a proxy-rewritten Host, a missing port, a forwarded header and a remote peer all get no session; the opt-out cookie still works.

gofmt, go vet ./..., go test ./..., and vole workflow run verify (which includes go test -race ./... and govulncheck) are clean.

Discussion (0)

No discussion yet.