application
SSHGate¶
active
SSH proxy that fingerprints the client KEXINIT handshake before connections reach sshd.
Written with AI. This project was developed with the help of an AI assistant (Anthropic's Claude, via Claude Code). The code has been reviewed and tested, but treat it accordingly: read it before you run it.
sshgate is a small TCP proxy that passively fingerprints the client's
plaintext SSH handshake before forwarding traffic to a real sshd. It records
HASSH-style fingerprints from SSH_MSG_KEXINIT, then allows operators to
approve or block those fingerprints before key exchange reaches the backend.
It can run standalone or synchronize observations and decisions with Gatehub.
Security boundary¶
SSH client fingerprints are spoofable. This is not authentication.
sshgate is a friction and logging layer against generic scanners and
unexpected client stacks. The real sshd must still perform user and key
authentication, and its normal hardening must remain in place.
Fingerprints describe an SSH client's advertised implementation and algorithm set, not a unique device or user. Multiple machines may produce the same value. Blocked and pending clients may receive a cached SSH banner, but they do not open a backend connection. SSHGate reads the backend banner once per route at startup. The backend must be reachable when SSHGate starts.
Safe quick start¶
Build and test with Go 1.27.1 or newer:
Keep sshd unchanged on port 22 and initially run sshgate on a second port:
./sshgate serve \
--allow-unknown \
--route '[::]:2222=127.0.0.1:22' \
--metrics-listen 127.0.0.1:9108 \
--db ./sshgate.db \
--config ./config.json
The config file is optional. From a second terminal, connect through port 2222, inspect the new observation, and approve it:
ssh -p 2222 [email protected]
./sshgate list -v --db ./sshgate.db
./sshgate approve --db ./sshgate.db \
--label "OpenSSH on my laptop" <fingerprint>
Restart without --allow-unknown, then verify the approved client can still
connect through port 2222. Keep an existing SSH session open while changing
firewall rules or moving sshd to an internal-only backend port.
For production, unknown fingerprints are blocked by default:
Validate the same inputs without opening the database or binding a port:
Deployment¶
The included Ansible playbook installs the binary, dedicated service account, configuration, systemd unit, and hardened writable paths:
cd ansible
cp inventory.example inventory
cp group_vars/sshgate.yml.example group_vars/sshgate.yml
# Edit inventory and group_vars/sshgate.yml for your deployment.
ansible-galaxy collection install ansible.posix
ansible-playbook --syntax-check playbook.yml
ansible-playbook playbook.yml
The real inventory and group variables files are ignored so host names,
fingerprints, and deployment-specific settings are not committed accidentally.
Because sshgate is inline with live SSH sessions, deployments use a graceful
tableflip handoff instead of terminating established connections.
See deployment and graceful upgrades for binaries, containers, inventory variables, fingerprint seeding, and reload behavior.
Documentation¶
- Deployment and graceful upgrades
- Operations, configuration, troubleshooting, and fingerprint reference
- How the five Gate projects fit together
- Gatekit node library
- Gatehub control plane
License¶
MIT. See LICENSE.
