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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

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

Bound the anonymous Git HTTP read path

86991e7020a418a08a5e3a36ce8f296561392e3b · Keenan Nemetz · 13d ago · parent e05f1c48

Bound the anonymous Git HTTP read path

The transport that needs no account had no bound on anything: the request body
was capped only at "not negative", the backend ran without a request context or
deadline, the path check accepted any suffix after `git-upload-pack` and handed
it to the CGI as `PATH_INFO`, and the server set no `ReadTimeout` and no
`MaxHeaderBytes`.

Bound what the read side can spend, which is every dimension available to a
fetch and none of the ones a push would need: a 2 MiB body cap with a required
`Content-Length`, exactly two endpoints with one method and one service each so
the CGI never sees an unvalidated path, `exec.CommandContext` plus `WaitDelay`
plus an explicit kill so a disconnect or an expired budget stops git instead of
leaving it running, and a concurrency semaphore with a per-client bound that
answers 503 with Retry-After rather than queueing.

The client address is the connection's own unless the operator names a proxy
header, because behind a proxy the connection address is the proxy. Both limits
and the header name are configuration, `git_http` turns the transport off
entirely, and `status` lists them so an operator can see what is in force.

`WriteTimeout` is deliberately not set: clone, archives, LFS objects and site
files are streamed, so those flows are bounded where they are produced.

forge/config/config.go +26

@@ -33,6 +33,27 @@ type Config struct {
33 33 SSHAddr string `toml:"ssh_addr"`
34 34 // MaxBlobBytes rejects individual objects in pre-receive.
35 35 MaxBlobBytes int64 `toml:"max_blob_bytes"`
36 // GitHTTP enables the anonymous smart-HTTP read transport: clone, fetch and
37 // the LFS downloads that share its URLs. It is on by default because reading a
38 // public repository without an account is the point of a public repository.
39 // An instance whose repositories are all internal can switch it off and get a
40 // smaller anonymous surface; pushes are on SSH either way.
41 GitHTTP bool `toml:"git_http"`
42 // GitHTTPMaxConcurrent bounds simultaneous reads on that transport. Past it a
43 // clone is refused with 503 and Retry-After rather than queued, because a
44 // queue is another unbounded thing to hold.
45 GitHTTPMaxConcurrent int `toml:"git_http_max_concurrent"`
46 // GitHTTPMaxPerClient bounds simultaneous reads from one address. Zero
47 // disables the per-client rule and leaves only the concurrency bound.
48 GitHTTPMaxPerClient int `toml:"git_http_max_per_client"`
49 // GitHTTPClientIPHeader names the request header a trusted proxy sets with the
50 // client address, e.g. "X-Forwarded-For". Empty means the connection address is
51 // the client, which is right when nothing is in front of Vole. Behind a proxy
52 // without this, every visitor shares one identity and one per-client budget.
53 GitHTTPClientIPHeader string `toml:"git_http_client_ip_header"`
54 // GitHTTPTimeoutSeconds bounds one read, including the git process behind it.
55 // A big clone over a slow link is minutes; a stalled one is hours.
56 GitHTTPTimeoutSeconds int `toml:"git_http_timeout_seconds"`
36 57 // ReceiveMaxBytes caps a whole push, enforced by git's own
37 58 // receive.maxInputSize in each repo rather than by buffering in the proxy.
38 59 ReceiveMaxBytes int64 `toml:"receive_max_bytes"`
@@ -106,6 +127,11 @@ func defaults() *Config {
106 127 MaxBlobBytes: 100 << 20, // 100 MiB per blob
107 128 ReceiveMaxBytes: 1 << 30, // 1 GiB per push
108 129
130 GitHTTP: true,
131 GitHTTPMaxConcurrent: 16,
132 GitHTTPMaxPerClient: 8,
133 GitHTTPTimeoutSeconds: 600,
134
109 135 LFSObjectMaxBytes: 2 << 30, // 2 GiB per LFS object
110 136 LFSWarnBytes: 5 << 20, // flag binaries over 5 MiB that are not in LFS
111 137