FTP Control Guard (ftp)
The FTP Control Guard plugin inspects File Transfer Protocol control connections (RFC 959). It validates command syntax, optionally restricts anonymous logins, tracks authentication failures (530 Login incorrect), and supports transparent AUTH TLS encryption handovers.
Capabilities & Threat Defense
| Threat / Attack Vector | Defense Mechanism | Action Taken |
|---|---|---|
| Credential Brute-Force & Spraying | Intercepts 530 Login incorrect response codes; enforces connection close with 421 Too many auth failures when cap is reached (since v3.4.0) | Client IP banned after max_auth_failures threshold; connection terminated immediately |
| Anonymous Login Abuse | Intercepts USER anonymous / USER ftp | Command blocked with 530 Anonymous access disabled |
| Connection Flooding | Enforces rate limits per minute and burst caps | Connection throttled at the proxy layer |
| AUTH TLS Handover | Detects 234 Proceed with negotiation | Transitions to bidirectional TLS pipe |
Installation
Install the plugin via CLI or declaratively:
# Install using short nametcp-warden plugins install ftp# Or install via Git repository URLtcp-warden plugins install https://github.com/routewarden/plugins/ftpConfiguration Reference
Add an FTP guard service to tcp-warden.yaml:
services: ftp_guard: listen: ":2121" upstream: "127.0.0.1:21" protocol: "ftp" rate_limit: connections_per_minute: 30 burst: 5 max_auth_failures: 5 ban_after_failures: 5 ban_duration: "2h" plugin_config: allow_anonymous: false max_auth_failures: 5Configuration Options
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":2121" | Local proxy listen address and port. |
upstream | string | "127.0.0.1:21" | Target FTP server (e.g. vsftpd) address and port. |
protocol | string | "ftp" | Must be set to "ftp". |
max_auth_failures | int | 5 | Number of failed login attempts before IP is banned. Since v3.4.0 the connection is actively closed with a 421 Too many auth failures response when the cap is reached, and individual lines from clients are bounded to prevent heap exhaustion. |
ban_duration | string | "2h" | Duration of the automated IP ban ("30m", "2h", "24h"). |
plugin_config.allow_anonymous | bool | true | When false, blocks USER anonymous or USER ftp commands. |
Network & Deployment Architecture
Option A: Kernel Firewall Redirection (nftables / iptables)
Redirect external traffic destined for port 21 to RouteWarden port 2121:
# Redirect external traffic destined for port 21 to RouteWarden port 2121sudo nft add rule ip routewarden_nat prerouting iifname "eth0" tcp dport 21 redirect to :2121Note on Passive FTP (PASV): The FTP plugin inspects and protects the control channel (port 21). Passive data connections flow directly or via RouteWarden's generic port range listeners.
Option B: Docker Compose Network Isolation
Run the FTP server inside an isolated container network, exposing control port 21 only through RouteWarden:
services: routewarden: image: ghcr.io/routewarden/tcp-warden:latest ports: - "21:2121" # Map host FTP control port 21 to RouteWarden - "9091:9091" # Management API volumes: - ./tcp-warden.yaml:/etc/routewarden/tcp-warden.yaml:ro depends_on: - ftp-server ftp-server: image: fauria/vsftpd:latest environment: - FTP_USER=ftpuser - FTP_PASS=secretpassword - PASV_ENABLE=YES # Do NOT publish port 21 to host; keep internal to bridge networkTesting & Verification
Verify anonymous access rejection and authentication failure tracking:
# Connect to FTP control port via standard CLI client$ ftp -n 127.0.0.1 2121Connected to 127.0.0.1.220 (vsFTPd 3.0.3)ftp> quote USER anonymous530 Anonymous access disabled by RouteWardenftp> QUIT221 Goodbye.