Skip to content

SSH transport support (store-and-forward) #154

@coopernetes

Description

@coopernetes

Summary

Add SSH transport alongside the existing HTTP store-and-forward flow so users can git push to jgit-proxy over SSH and have the same pre-receive hook chain (validation, approval, forwarding) run against their push.

Transparent-proxy mode is out of scope — SSH has no meaningful transparent-proxy equivalent outside of SOCKS.

Feasibility analysis

Verdict: feasible. Moderate effort, one real hard problem (auth forwarding).

Library choice

JGit ships no SSH server — only SSH client transports (org.eclipse.jgit.ssh.apache, org.eclipse.jgit.ssh.jsch). The server has to come from Apache MINA SSHD directly, specifically its sshd-git module, which provides GitPackCommandFactory — a ready-made SSH command handler for git-receive-pack / git-upload-pack that hands us a ReceivePack instance to configure.

Docs: https://github.com/apache/mina-sshd/blob/master/docs/git.md

Hook chain reuse

The good news: JGit's ReceivePack.receive(InputStream, OutputStream, OutputStream) is transport-agnostic. Hooks set via setPreReceiveHook / setPostReceiveHook fire on parsed ReceiveCommand objects — they never touch HTTP. rp.sendMessage() sideband also works over raw SSH streams because it's part of the pack protocol, not HTTP.

The current factory jgit-proxy-core/.../StoreAndForwardReceivePackFactory.java is parameterized on HttpServletRequest. For SSH we'd write a parallel adapter parameterized on MINA SSHD's ServerSession, reusing the exact same hook instantiation logic. The hooks themselves transplant unchanged. Only credential extraction and repo resolution differ (SSH principal + command arg vs. Basic header + path).

Minimal wiring sketch

SSH "git-receive-pack <repo>"
  → MINA SSHD GitPackCommandFactory
  → GitPackConfiguration.configureReceivePack(session, rp)
     (install existing hook chain via SSH-flavored ReceivePackFactory)
  → rp.receive(sshIn, sshOut, sshErr)
  → existing hooks fire identically; sideband `remote:` messages stream live

The hard problem: auth forwarding

HTTP mode forwards the client's Basic credentials upstream. SSH pubkey auth gives us nothing forwardable — the private key never leaves the client. Three realistic options:

  1. Proxy-owned bot PAT (recommended start). Map the SSH principal to a stored upstream token. Reuses ForwardingPostReceiveHook with zero change. Simplest path to a working SSH listener.
  2. Per-user upstream token mapping. Needs a user → upstream-token store; same push-then-forward shape as today. Natural second phase.
  3. SSH agent forwarding. MINA SSHD supports server-side agent forwarding, but wiring JGit's SshSessionFactory to use the forwarded agent for the upstream push is a real spike with cross-network edge cases. Most transparent UX if we can get it working.

Risks to validate with a spike

  1. Sideband flush timing under long-running hooks (approval polling, secret scan) — confirm remote: lines stream live over SSH the way they do over HTTP, not buffered until the channel closes.
  2. Orphan refs on upstream-push failure — store-and-forward already has this risk; auth expiry over SSH may make it more likely.
  3. Option 3 (agent forwarding) viability if we want true transparent auth — worth a throwaway spike before committing to it as the target design.

Effort estimate

~2-3 weeks for option-1 SSH server (MINA SSHD integration + SSH-flavored ReceivePackFactory + e2e tests with a real OpenSSH client).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:authAuthentication, authorization, identityarea:proxyenhancementNew feature or requestmoonshotAmbitious or speculative feature; may be abandoned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions