Skip to content

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 VectorDefense MechanismAction Taken
SSH Dictionary AttacksIntercepts SSH_MSG_USERAUTH_FAILURE (type 51)IP automatically banned after max_auth_failures
Obsolete SSH-1 ClientsParses client identification stringImmediate disconnect with RFC-compliant error
Connection FloodingToken bucket rate limitingConnections delayed or dropped at proxy layer
Service Probing / ScannersSynthetic custom protocol bannerMasks 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/ssh

RouteWarden 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: 3

Configuration Options ​

FieldTypeDefaultDescription
listenstring":2222"Local address and port for RouteWarden to bind.
upstreamstring"127.0.0.1:22"Target OpenSSH server address and port.
protocolstring"ssh"Must be set to "ssh".
max_auth_failuresint3Number of failed authentication packets (type 51) before banning.
ban_durationstring"2h"How long an offending IP address remains banned ("30m", "2h", "24h").
plugin_config.bannerstring"RouteWarden SSH Guard"Optional custom identification string returned during initial handshake.
plugin_config.max_auth_triesint3Max 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 :2222

Option 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.1

Option 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 network

Testing & 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

Released under the MIT License.