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.

Validate the configuration and harden what a deployment creates

ed1812123cd9e9df0f34fdaf0d2511a340c2aaa6 · Keenan Nemetz · 13d ago · parent 13cff33f

Validate the configuration and harden what a deployment creates

Two ways an instance used to run in a state it should have refused.

The configuration was never checked. `dev_login` was guarded only by a loopback
listener, so a proxy that rewrites Host to 127.0.0.1 inherited a credential-free
session; open registration could be enabled on a plain-HTTP origin WebAuthn would
never accept; a public_url could carry a path or credentials; a size could be
negative. Config.Validate now runs before anything is opened and reports every
problem at once, and dev_login requires an explicit dev_only opt-in next to a
loopback listener with no public address. The per-request check no longer trusts
Host at all: it must name the configured listener, and a request carrying a
forwarding header gets no session.

The files were never checked either. directories were created 0755 and the audit
log 0644, and a file keeps the mode it was created with, so an upgraded instance
stayed exposed. internal/secure creates secrets at 0600 in directories at 0700,
tightens what an existing install left, keeps group access for a shared backup
group, and refuses to start when a mode cannot be changed. Every audit writer and
the account backup go through it. The service unit drops all capabilities,
sets UMask=0027, disables core dumps, hides other processes and devices, and
restricts address families; docs/operations.md now states the production
baseline.

docs/operations.md +45

@@ -93,6 +93,14 @@ repository's `vole` branch.
93 93 - The SSH host key is the only long-term secret Vole stores. Keep it readable
94 94 only by the Vole user (`0600`). Browser session and enrollment tokens are
95 95 stored as hashes.
96 - Vole creates its own secrets at `0600` and its data directory at `0700`: the
97 roster (`users.db`), the audit log, the SSH host key and the ACME account
98 directory. It also tightens an existing instance on startup, and logs the paths
99 whose mode it changed, so an upgrade from an earlier version cannot leave a
100 private key world-readable. If a mode cannot be changed, startup fails rather
101 than serving keys it cannot protect.
102 A site directory that is deliberately shared with a backup group is fine at
103 `0750`: group access is left alone, world access is not.
96 104 - Nothing Vole writes is signed: not pushes, not approvals, not CI results. Vole
97 105 relies on the SSH key at the connection, the checks before a push is accepted,
98 106 and the audit log.
@@ -117,3 +125,40 @@ repository's `vole` branch.
117 125 otherwise put it behind a proxy that terminates TLS and keeps the `Host`
118 126 header, so site routing still works. See
119 127 [Configuration § Automatic HTTPS with ACME](configuration.md#automatic-https-with-acme).
128
129 ## Production baseline
130
131 A public instance is a web server, a Git server and a policy engine in one
132 process. The settings below are the ones that matter on the day it is exposed;
133 the linked sections explain each in full.
134
135 - **Web behind TLS, on loopback.** Leave `https_addr` empty, bind
136 `http_addr = "127.0.0.1:8080"`, and put a proxy in front that terminates TLS
137 and passes the `Host` header through unchanged. Alternatively set `https_addr`
138 with `acme_email` and `acme_domains` and let Vole do it. Set `public_url` to
139 the canonical `https://` address so links and passkeys agree with it. See
140 [Configuration](configuration.md).
141 - **SSH firewalled.** The SSH listener is the only write transport and the only
142 place an account is authenticated. Expose it to the people who push, not to the
143 internet at large, and leave it on a port other than 22 unless you also run
144 `sshd` elsewhere.
145 - **No development shortcuts.** `dev_only` and `dev_login` are refused together
146 with a public address, and an instance that needs neither should set neither.
147 The same goes for `open_registration`, which is off by default.
148 - **Conservative limits.** The defaults are chosen for a small instance:
149 `git_http_max_concurrent = 16`, `git_http_max_per_client = 8`,
150 `max_blob_bytes = 100 MiB`, `receive_max_bytes = 1 GiB`, and a 600 s budget for
151 one clone. Raise them with measurements, not with optimism; the read path is
152 anonymous, so a limit that is too low is a slow clone and a limit that is
153 missing is an outage.
154 - **Audited administration.** Remote administration uses the same SSH listener
155 and is restricted to instance admins. Ship the audit log off the host: local
156 append-only is not tamper-evident. See
157 [Architecture § Trust model](architecture.md).
158 - **Backups you have restored once.** `users.db` is not derived and not
159 rebuildable; everything else either is, or can be re-pushed. Test the restore,
160 not the backup. See [Back up an instance](#back-up-an-instance).
161 - **The service unit.** The packaged unit drops all capabilities, sets
162 `UMask=0027`, and hides other processes and devices. If you serve HTTPS or SSH
163 on a port below 1024, grant `CAP_NET_BIND_SERVICE` with a drop-in rather than
164 running the service as root. See [Packaging](packaging.md).