SSH Protocol Guard (ssh)
The SSH Protocol Guard plugin extends RouteWarden with deep Layer 4 and Layer 7 inspection for Secure Shell connections (RFC 4253). It validates client identification strings, terminates outdated or vulnerable SSH-1.x clients before cryptographic key exchange, and passively monitors upstream responses for authentication failures to automatically ban dictionary brute-force bots.
Capabilities & Threat Defense
| Threat / Attack Vector | Defense Mechanism | Action Taken |
|---|---|---|
| SSH Dictionary Attacks | Intercepts SSH_MSG_USERAUTH_FAILURE (type 51) | IP automatically banned after max_auth_failures |
| Obsolete SSH-1 Clients | Parses client identification string | Immediate disconnect with RFC-compliant error |
| Connection Flooding | Token bucket rate limiting | Connections delayed or dropped at proxy layer |
| Service Probing / Scanners | Synthetic custom protocol banner | Masks true OpenSSH daemon version |
Installation
Install the plugin either via the command line or declaratively in your configuration file:
# Install using short nametcp-warden plugins install ssh# Or install via Git repository URLtcp-warden plugins install https://github.com/routewarden/plugins/sshRouteWarden will verify, download, and register the plugin automatically during boot.
Configuration Reference
Add an SSH guard service to tcp-warden.yaml under services:
services: ssh_bastion: listen: ":2222" upstream: "127.0.0.1:22" protocol: "ssh" rate_limit: connections_per_minute: 20 burst: 5 max_auth_failures: 3 ban_after_failures: 3 ban_duration: "2h" plugin_config: banner: "RouteWarden SSH Guard" max_auth_tries: 3Configuration Options
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":2222" | Local address and port for RouteWarden to bind. |
upstream | string | "127.0.0.1:22" | Target OpenSSH server address and port. |
protocol | string | "ssh" | Must be set to "ssh". |
max_auth_failures | int | 3 | Number of failed authentication packets (type 51) before banning. |
ban_duration | string | "2h" | How long an offending IP address remains banned ("30m", "2h", "24h"). |
plugin_config.banner | string | "RouteWarden SSH Guard" | Optional custom identification string returned during initial handshake. |
plugin_config.max_auth_tries | int | 3 | Max auth attempts permitted per single connection. |
Network & Deployment Architecture
Option A: Kernel Firewall Redirection (nftables / iptables)
Leave OpenSSH listening on standard port 22 on 127.0.0.1. Redirect external traffic arriving on your public interface (eth0) to RouteWarden port 2222:
# 1. Create RouteWarden NAT tablesudo nft add table ip routewarden_nat# 2. Add prerouting hooksudo nft add chain ip routewarden_nat prerouting '{ type nat hook prerouting priority dstnat; policy accept; }'# 3. Redirect external port 22 to RouteWarden port 2222sudo nft add rule ip routewarden_nat prerouting iifname "eth0" tcp dport 22 redirect to :2222Option B: Port Swapping (Native OpenSSH Config)
Bind OpenSSH to internal port 22222 on loopback, and let RouteWarden bind to :22:
# /etc/ssh/sshd_configPort 22222ListenAddress 127.0.0.1Option C: Docker Compose Network Isolation
Run the SSH server inside an isolated container network, exposing port 22 only through RouteWarden:
services: routewarden: image: ghcr.io/routewarden/tcp-warden:latest ports: - "22:2222" # Map host SSH port 22 to RouteWarden port 2222 - "9091:9091" # Management API volumes: - ./tcp-warden.yaml:/etc/routewarden/tcp-warden.yaml:ro depends_on: - ssh-server ssh-server: image: linuxserver/openssh-server:latest environment: - PUID=1000 - PGID=1000 - PASSWORD_ACCESS=true - USER_PASSWORD=securepassword - USER_NAME=sshuser # Do NOT publish host ports; keep internal to bridge networkTesting & Verification
Verify legitimate connections, simulate password brute-forcing, and query the active ban table:
# Connect legitimately to OpenSSH through RouteWardenssh -p 2222 user@127.0.0.1