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/configuration.md +39−6

@@ -53,7 +53,8 @@ vole admin account list
53 53 | `acme_domains` | empty | Host names to obtain certificates for; defaults to the host in `public_url` |
54 54 | `acme_cache_dir` | `<data_dir>/acme` | Where the ACME account key and certificates are stored |
55 55 | `acme_directory_url` | Let's Encrypt production | ACME directory; use the staging URL or a private CA while testing |
56 | `dev_login` | empty | Development only: sign loopback browser requests in as this account |
56 | `dev_login` | empty | Development only: sign loopback browser requests in as this account; requires `dev_only` |
57 | `dev_only` | `false` | Acknowledge that this instance is a development instance; without it `dev_login` is refused |
57 58 | `open_registration` | `false` | Let anyone create an account by registering a passkey; requires `public_url` to be HTTPS |
58 59 | `open_registration_role` | `"none"` | Instance-wide role a self-registered account gets; applies to public repositories, `admin` is refused |
59 60 | `max_blob_bytes` | `104857600` (100 MiB) | Largest single file accepted in a push |
@@ -221,22 +222,54 @@ vole admin acl set-default-role demo read
221 222 ## Development sign-in
222 223
223 224 Browsers require HTTPS for passkeys, which makes a local `http://127.0.0.1:8080`
224 preview awkward to use for write testing. `dev_login` is an explicit escape hatch:
225 preview awkward to use for write testing. `dev_login` is an explicit escape hatch,
226 and `dev_only` is what makes it explicit:
225 227
226 228 ```toml
227 229 http_addr = "127.0.0.1:8080"
230 dev_only = true
228 231 dev_login = "you"
229 232 ```
230 233
231 234 When set, a request from loopback is signed in as that account using a normal
232 server-side session. Vole refuses to start if `http_addr` is not a loopback
233 address, and logs a warning when the setting is active. The session cookie is
234 deliberately not named `__Host-` so a browser will store it over plain HTTP.
235 Never enable this on an instance other people can reach.
235 server-side session. Vole refuses to start unless all of these hold: `dev_only`
236 is set, `http_addr` is a loopback address, there is no `https_addr`, and
237 `public_url` is unset or itself loopback. It logs a warning when the setting is
238 active. The session cookie is deliberately not named `__Host-` so a browser will
239 store it over plain HTTP. Never enable this on an instance other people can reach.
240
241 The request check does not trust the `Host` header alone. A loopback listener
242 behind a proxy sees loopback as the peer address, so a development request must
243 also name the configured `http_addr` (same port, and either the same host or
244 another loopback name), and any request carrying `Forwarded`,
245 `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto` or `X-Real-IP` is
246 refused. A proxy that rewrites `Host` to `127.0.0.1` therefore gets no session.
236 247
237 248 Signing out opts the browser out of automatic dev sign-in; visit `/dev/login` to
238 249 sign back in. Remove `dev_login` and restart to turn it off entirely.
239 250
251 ## Refused configurations
252
253 `vole serve` validates the configuration before it opens anything and refuses to
254 start on a combination that cannot be safe. It reports every problem at once, so
255 one restart is enough to fix the file.
256
257 | Refused when | Why |
258 |---|---|
259 | `dev_login` without `dev_only` | A credential-free session must be a decision, not a copied line |
260 | `dev_login` with `https_addr` | That listener is reachable from the network |
261 | `dev_login` with a non-loopback `public_url` | A published instance is not a development instance |
262 | `dev_login` with a non-loopback `http_addr` | The session is only for the machine it runs on |
263 | `open_registration` without an HTTPS `public_url` | WebAuthn credentials are bound to an HTTPS origin |
264 | `open_registration_role` = `admin` | Self-registration cannot grant instance administration |
265 | `public_url` with a user, path, query or fragment | It is an origin that links are appended to |
266 | `pages_domain` that is not a bare DNS suffix | Site routing matches a visitor's `Host` against it |
267 | `receive_max_bytes` below `max_blob_bytes` | No push could ever contain a permitted file |
268 | `https_addr` with no `acme_domains` or `public_url` | There is no name to obtain a certificate for |
269 | `acme_email` that is not one address | The certificate authority has nobody to contact |
270 | `acme_directory_url` that is not HTTPS | Renewal would fail after the typo was forgotten |
271 | a negative size, lease or limit | It is never what the operator meant |
272
240 273 ## Running behind a proxy
241 274
242 275 If a proxy already terminates TLS, leave `https_addr` empty. Vole serves plain