PRD 0008: Git gate #11
Reference in New Issue
Block a user
Delete Branch "git-gate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a per-bottle git-gate sidecar that fronts the agent's declared git upstreams as a bidirectional mirror. Push is gated: a
pre-receivehook runs gitleaks against incoming refs and, on clean, forwards each ref to the real upstream using a credential the gate holds. Fetch is mirrored:git daemon's--access-hookrunsgit fetch origin --pruneagainst the upstream before everyupload-pack, so an agent fetch returns whatever the upstream has now (fail-closed if the upstream is unreachable). The agent never holds the upstream credential — a misbehaving agent cannot push a secret-bearing commit past the gate, and cannot acquire push access by reading its own filesystem.The agent-side rewrite is one
[url "git://<gate>/<name>.git"] insteadOf = <real-url>block in~/.gitconfig, so push, fetch, clone, pull, and ls-remote all route through the gate transparently. The three sidecars keep distinct blast radii: ssh-gate is L4-dumb, pipelock terminates internet-facing TLS, and the git-gate is the only one that holds upstream push credentials. Smolmachines / microVM colocation is left to the backend.bottle.gitentries take an optionalExtraHosts: { hostname: ip }map the docker backend surfaces as--add-hoston the gate sidecar. This is for upstreams whose default container DNS doesn't resolve to the reachable IP (e.g. a Tailscale-only host whose public A record points elsewhere): the gate's/etc/hostsgets the override while the agent'sinsteadOfrewrite still keys off the original hostname, soUpstreamURLs in the manifest stay human-readable.Covered by unit tests (manifest parsing + validation, script-render shape, ExtraHosts aggregator conflict detection) and integration tests against a real Docker daemon (fail-closed ls-remote, secret-in-commit push rejection, bidirectional round-trip against a sibling sshd-based upstream).
Two integration tests against a real Docker daemon: - test_ls_remote_succeeds_against_fresh_gate: a freshly-started gate has its empty bare repo exported via git daemon; ls-remote from a sibling container on the internal network returns no refs and exits 0. - test_push_with_secret_is_rejected: the PRD 0008 success criterion — a push containing an AKIA-shaped synthetic that trips gitleaks's aws-access-token rule is rejected by the pre-receive hook with a non-zero exit on the client and a gitleaks rejection in the response. Dockerfile.git-gate switches base to zricethezav/gitleaks (alpine 3.22 + gitleaks v8.30.1, pinned by digest) since gitleaks isn't packaged for alpine, and adds git-daemon (the sub-package the listener needs; the core git binary in the base doesn't include the daemon).A pair of integration tests against a real sshd-based "upstream" sibling container that prove every operation through the gate is observably equivalent to the same operation against the upstream: - test_clone_and_refetch_reflect_upstream: clone via gate returns the upstream's current commit; an out-of-band commit on the upstream shows up via the gate on the next ls-remote. - test_push_through_gate_lands_on_upstream: a clean push routed through the gate lands on the upstream's bare repo. The upstream container is a tiny inline-built alpine image with openssh-server, a `git` user (passwd -u so sshd doesn't reject the locked account), and a baked bare repo seeded with one commit. Host keys are baked in at build so the test can pin KnownHostKey on the manifest entry before the container starts. While wiring this up the access-hook gained a one-shot HEAD sync: `git init --bare` defaults HEAD to refs/heads/master, and upstreams that use main would leave the bare repo's HEAD unresolvable — clones came through but the working tree was empty. The hook now does a `rev-parse --verify HEAD` check after the first fetch and runs `ls-remote --symref` to repoint HEAD if it doesn't resolve. One extra round-trip on first fetch only.