Skip to content

Repository files navigation

sshgate

sshgate mark
Porter mascot for the gate tools

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 the SSH banner, but they do not complete key exchange with the backend.

Safe quick start

Build and test with Go 1.26.5 or newer:

go build -o sshgate .
go test ./...

Keep sshd unchanged on port 22 and initially run sshgate on a second port:

SSH client ──> sshgate :2222 ──> sshd 127.0.0.1:22
                    │
                    └──> SQLite fingerprint decisions
./sshgate serve \
  --allow-unknown \
  --route '[::]:2222=127.0.0.1:22' \
  --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 user@server.example.com
./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:

./sshgate serve \
  --route '[::]:22=127.0.0.1:2222' \
  --db ./sshgate.db \
  --config ./config.json

Validate the same inputs without opening the database or binding a port:

./sshgate doctor \
  --db ./sshgate.db \
  --config ./config.json \
  --route '[::]:2222=127.0.0.1:22'

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

License

MIT. See LICENSE.

About

TCP proxy for SSH that fingerprints the client KEXINIT handshake (HASSH-style) and approves/blocks connections before they reach sshd.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages