clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

Serve only git's own paths over HTTPS, for public repos only

cat committed cc80fa3
git http-backend now gets every clone path, smart and dumb (HEAD,
info/refs, objects, packs), without GIT_HTTP_EXPORT_ALL, so it serves
only repos with git-daemon-export-ok and a private repo is the 404 a
missing one is. Nothing else inside a bare repo is served any more: the
raw file server used to hand out forge/ (review logs, comments,
collaborators), config and hooks to anyone who asked.

Without fcgiwrap there are no HTTPS clones; the container and
provision.sh install it.
commit
cc80fa3fa9b28afe1bacda9a7f2d7b7b9245f091
parent
70f465d24fa0c76cc3b5f12a474b4066ba130744
author
cat <admin@dev.site>
kind
fix risk score 2.3 of 3, Jev's estimate
4 files changed, +85 -16
Mbin/forge20++++++-----
Mserver/Caddyfile.example22+++++++++---
Mserver/provision.sh4+-
Atest/scenarios/private-http.sh55++++++++++++++++++++++++++++++
M bin/forge +11 -9
@@ -857,18 +857,21 @@ $addr {
857857 respond "<!DOCTYPE html><meta name=\"go-import\" content=\"$gohost/{re.goget.1}/{re.goget.2} git https://$gohost/{re.goget.1}/{re.goget.2}.git\"><p>go get $gohost/{re.goget.1}/{re.goget.2}</p>" 200858858 }859859 EOF860- # Smart HTTP for clones and fetches (git http-backend through fcgiwrap):860+ # Clones and fetches go to git http-backend (through fcgiwrap): smart HTTP861861 # packs only what the client needs and supports shallow fetches, which the862- # go command uses. Read-only: only upload-pack is routed; pushes are ssh.862+ # go command uses, and it answers the dumb protocol's paths too (HEAD,863+ # info/refs, objects). It serves only repos with git-daemon-export-ok, so864+ # a private repo is a 404, the same as a missing one. Read-only: only865+ # upload-pack is routed; pushes are ssh. Nothing else inside a bare repo866+ # is served (no forge/, config or hooks).863867 if command -v fcgiwrap >/dev/null 2>&1; then864868 cat <<EOF865- @smart path_regexp smart ^/[A-Za-z0-9_-][^/]*/[^/]+\\.git/(info/refs|git-upload-pack)$866- handle @smart {869+ @git path_regexp git ^/[A-Za-z0-9_-][^/]*/[^/]+\\.git/(HEAD|info/refs|objects/info/(alternates|http-alternates|packs)|objects/[0-9a-f]{2}/([0-9a-f]{38}|[0-9a-f]{62})|objects/pack/pack-([0-9a-f]{40}|[0-9a-f]{64})\\.(pack|idx)|git-upload-pack)$870+ handle @git {867871 reverse_proxy unix//run/fcgiwrap.socket {868872 transport fastcgi {869873 env SCRIPT_FILENAME /usr/lib/git-core/git-http-backend870874 env GIT_PROJECT_ROOT $FORGE_ROOT871- env GIT_HTTP_EXPORT_ALL 1872875 env PATH_INFO {path}873876 }874877 }
@@ -876,10 +879,9 @@ EOF
876879 EOF877880 fi878881 cat <<EOF879- @repo path_regexp repo ^/[A-Za-z0-9_-][^/]*/[^/]+\\.git(/.*)?$880- handle @repo {881- root * $FORGE_ROOT882- file_server882+ @bare path_regexp bare ^/[A-Za-z0-9_-][^/]*/[^/]+\\.git(/.*)?$883+ handle @bare {884+ respond 404883885 }884886 EOF885887 # Old site paths redirect to each site's hostname (lib/pages-server.ts).
M server/Caddyfile.example +17 -5
M server/provision.sh +2 -2
A test/scenarios/private-http.sh +55 -0
@@ -0,0 +1,55 @@
1+# HTTPS clones: Caddy hands git's paths (smart and dumb) to git2+# http-backend, which serves only repos with git-daemon-export-ok, and3+# serves nothing else from inside a bare repo. A private repo gets the 4044+# a missing one gets.5+. "$(dirname "$0")/../lib.sh"6+user dami7+repo --private dami/secret8+repo dami/open9+for r in secret open; do10+ clone dami "dami/$r" "$T/w/$r"11+ at "$T/w/$r" commit "first"12+ ok push_as dami "$T/w/$r" origin main13+done14+15+# The Caddyfile, as on a host with fcgiwrap.16+mkdir "$T/bin"17+printf '#!/bin/sh\n' >"$T/bin/fcgiwrap"18+chmod +x "$T/bin/fcgiwrap"19+PATH="$T/bin:$PATH" forge server caddyfile git.test >"$T/Caddyfile" 2>"$T_LOG" || fail "caddyfile"20+lacks "$T/Caddyfile" 'GIT_HTTP_EXPORT_ALL'21+lacks "$T/Caddyfile" "root \\* $FORGE_ROOT\$"22+has "$T/Caddyfile" 'SCRIPT_FILENAME .*/git-http-backend$'23+has "$T/Caddyfile" 'respond 404'24+eq "$(grep -c 'file_server' "$T/Caddyfile")" 125+if command -v caddy >/dev/null 2>&1; then ok caddy adapt --config "$T/Caddyfile" --adapter caddyfile; fi26+re=$(sed -n 's/^ @git path_regexp git //p' "$T/Caddyfile")27+[ -n "$re" ] || fail "no @git route"28+h40=0123456789abcdef0123456789abcdef0123456729+for p in info/refs git-upload-pack HEAD objects/info/packs "objects/01/${h40#01}" "objects/pack/pack-$h40.pack" "objects/pack/pack-$h40.idx"; do30+ printf '/dami/open.git/%s\n' "$p" | grep -Eq "$re" || fail "@git doesn't route $p"31+done32+for p in config description forge/reviews/1 forge/readers hooks/pre-receive git-receive-pack objects/info/commit-graphs/x refs/heads/main packed-refs; do33+ if printf '/dami/open.git/%s\n' "$p" | grep -Eq "$re"; then fail "@git routes $p"; fi34+done35+36+# git http-backend itself, as fcgiwrap runs it.37+cgi() {38+ env -u GIT_HTTP_EXPORT_ALL GIT_PROJECT_ROOT="$FORGE_ROOT" PATH_INFO="$1" REQUEST_METHOD=GET QUERY_STRING="${2:-}" \39+ "$(git --exec-path)/git-http-backend" </dev/null >"$T_LOG" 2>/dev/null || true40+}41+cgi /dami/open.git/info/refs service=git-upload-pack42+has "$T_LOG" 'application/x-git-upload-pack-advertisement'43+has "$T_LOG" 'refs/heads/main'44+cgi /dami/secret.git/info/refs service=git-upload-pack45+has "$T_LOG" '^Status: 404'46+lacks "$T_LOG" 'refs/heads'47+denied=$(output)48+cgi /dami/nope.git/info/refs service=git-upload-pack49+eq "$denied" "$(output)"50+cgi /dami/open.git/HEAD51+has "$T_LOG" 'ref: refs/heads/main'52+cgi /dami/secret.git/HEAD53+has "$T_LOG" '^Status: 404'54+cgi /dami/secret.git/info/refs55+has "$T_LOG" '^Status: 404'