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 | |