I close SSH port 22 (and what I use instead)
A common recommendation in security guides is to harden SSH by limiting access to key-only authentication and employing fail2ban. While these measures offer some protection, an open port 22 on a publicly accessible machine remains vulnerable. Automated scanners can easily probe it, revealing the SSH version string and banner, generating log noise with each failed attempt.
More critically, such exposure makes the server a potential target for zero-day exploits. To address these issues, one approach is to make the SSH daemon itself inaccessible, devoid of any banner, version string, or targets for scanning tools. The port knocking method was an early solution, requiring a specific sequence of connection attempts on closed ports before the SSH daemon would temporarily open a real port.
However, this method has a significant weakness - the knock sequence travels in cleartext, making it susceptible to interception and replay attacks. fwknop introduced a more secure alternative using Single Packet Authorization (SPA). Instead of a knock sequence, SPA employs a single encrypted and cryptographically signed UDP packet.
The server only opens port 22 upon successfully verifying the authenticity of the packet. Prior to establishing an SSH connection, the client sends an encrypted UDP packet to the server, which fwknopd validates and temporarily inserts a firewall rule to open port 22 exclusively for the source IP for a configurable duration (usually 120 seconds).
After this window, the rule is automatically removed. In cases where the connection is already established, the session persists as the connection was initiated prior to the rule's disappearance. From a scanner's perspective, port 22 remains inaccessible - nmap reports it as filtered, indistinguishable from dropped packets. The SPA packet, using UDP, may be lost during transit, necessitating a resend if SSH encounters a connection failure. fwknopd is stateless, allowing safe resending of the packet.
The SPA packet itself is authenticated using HMAC-SHA512 and encrypted, preventing replay attacks. To implement this, two keys are required: an encryption key to protect the packet's contents and an HMAC key for authentication. These keys must be distinct for optimal security, with the encryption key safeguarding the packet's data and the HMAC key verifying its authenticity.
During key generation, both keys should be securely stored for future reference. The encryption key and HMAC key serve different purposes and should not be the same, as using a single key weakens the security of the scheme. Key generation is a one-time task, and the resulting output must be securely stored and accessible by both the server and client machines.
I recommend storing the keys in Ansible vault and Bitwarden, with Ansible reading them during server deployment and chezmoi retrieving them from Bitwarden on client machines. Each host shares the same pair of keys, simplifying key rotation. The Ansible role fwknop_server is responsible for installing fwknop-server, deploying the server configuration and access file, and adjusting UFW rules.
After fwknop deployment, the public SSH UFW rule is removed, and iptables only opens port 22 when a valid SPA packet arrives. fwknopd achieves this by directly inserting iptables rules beneath UFW’s management layer, rendering the temporary rules invisible to ufw status. The fwknop_access_timeout directive in access.conf.j2 determines the duration for which the firewall rule remains active after a valid SPA packet.
In contrast, ACCESS_EXPIRE sets the period after which the stanza ceases to accept SPA packets altogether. On the client side, the ~/.fwknoprc file, managed by chezmoi, contains a stanza per host, including the resolved IP address and base64-encoded keys generated during the initial key generation. The knock process can be fully transparent by incorporating a ProxyCommand in each host's ~/.ssh/config file, enabling seamless SSH connections once the packet reaches the server.
For machines undergoing initial provisioning, SSH connections proceed directly without fwknop, as the fwknop_server role has not been executed yet. Subsequent Ansible runs will automatically incorporate the knock process, requiring the user to initiate the SPA packet sending by invoking fwknop -n server1 before establishing the actual TCP connection to the open port.
While fwknop provides robust protection, it is not the sole access method. The Tailscale interface (tailscale0) remains universally open, allowing SSH access from any Tailscale-connected device without the need for the knock sequence. This serves as an alternative access path, independent of fwknop, and proves particularly useful in scenarios where fwknop encounters configuration issues, ensuring uninterrupted remote access to the server.
Written by urgent.news from Hacker News's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.