Letmein is a simple port knocker with a simple and secure authentication mechanism. It can be used to harden against pre-authentication attacks on services like SSH, VPN, IMAP and many more.
Letmein hides services on a server behind a knock authentication barrier to reduce the attack surface of a service. The service will not be accessible unless a knock authentication is successful. In case of a successful knock, the letmeind server will only open the knocked port for the client IP address that performed the knocking. Machines with different IP addresses still won’t have access to the protected service.
Machines that can’t successfully authenticate the knock sequence
won’t be able to access the protected service. They will receive a
TCP/ICMP reject on the protected service port with the
provided example nftables.conf. (You can also decide to
drop the packets in your nftables.conf
instead).
Letmein requires an nftables based firewall. It will
not work with iptables. If you use an
iptables based firewall, please convert to
nftables before installing letmein. There are descriptions
about how to do that on the Internet. It’s not as hard and as much work
as it sounds. :)
On the server install the letmein server software (see sections below).
On the client install the letmein client software (see sections below).
Please read the nftables.conf example
configuration file provided with this project. Adding a letmein specific
input chain to your existing nftables configuration is
required. Modify your nftables.conf accordingly.
Generate shared secret key and a user identifier to be installed on the server and client with the following client command:
letmein gen-key -u 00000000The gen-key command will print the generated key string to the
console. By default this will generate a secure random key for the user
identifier 00000000. You can manually edit the user
identifier, if you want, or you can just leave it as-is.
Add the generated string (user identifier and the shared secret) to
the server configuration in /opt/letmein/etc/letmeind.conf.
Put the generated key string together with the user identifier into the
[KEYS] section of the configuration file.
Add the same generated string (user identifier and shared secret) to
the client configuration in /opt/letmein/etc/letmein.conf.
Put the generated key string together with the user identifier into the
[KEYS] section of the configuration file.
Create a resource in the server that describes the
sshd port that can be opened. In the
[RESOURCES] section of the server configuration file
/opt/letmein/etc/letmeind.conf all ports that may be opened
must be specified. A resource consists of a resource identifier followed
by a port identifier like that:
[RESOURCES]
00000022 = port: 22
The resource identifier is an 8 digit hexdecimal number. In this
example it is 22(hex), but it can be any number. It just has to be the
same number on the server and the client. After port: the
port number (in decimal) that can be knocked-open is specified.
Add the same resource with the same resource identifier and the same
port number to the client configuration in
/opt/letmein/etc/letmein.conf.
Restart the letmein server:
systemctl restart letmeind.serviceNow remove your static sshd port (22)
accept from your nftables.conf firewall
configuration. Letmein will install such a rule dynamically into the
letmein input chain after successful knock authentication. Then restart
nftables:
systemctl restart nftables.serviceDone! You should now be able to knock-open the sshd port
on your server:
# This must fail! No successful knock authentication, yet.
# If this does not fail, check if you have removed the sshd accept rule from nftables.conf.
ssh your-server.com
# Knock-open port 22 (sshd) on the server using user-id/key 00000000:
# (You do not have to specify -u 00000000 if that is your default user (see config).)
letmein knock -u 00000000 your-server.com 22
# Now you should be able to ssh into your server successfully:
ssh your-server.comTo automatically knock the port before connecting with ssh, you can
add a Match exec rule to your ~/.ssh/config
file:
Match host your-server.com exec "letmein knock -u 00000000 your-server.com 22"
The Rust compiler must be installed to build letmein. It is recommended to use the latest version of the stable Rust compiler:
The Rust installer will install the compiler and the build tool
cargo.
The build requires the additional cargo-audit and
cargo-auditable tools to be installed. Run this command to
install both tools:
cargo install cargo-audit cargo-auditableAt runtime the nftables nft binary is required to be
installed reachable in the $PATH. On Debian please install
the nftables package:
apt install nftablesRun the build.sh script to build letmein.
After installing all build prerequisites, run the build script:
./build.shThen run the install-client.sh to install the letmein
client to /opt/letmein/:
./install-client.shThe client is used to send a knock packet to the server.
The public network facing part of the letmein server runs with reduced privileges to reduce the attack surface.
For this to work, the user letmeind and the group
letmeind have to be present in /etc/passwd and
/etc/group. It is recommended for this user to not have a
shell and home assigned and therefore not be a login-user.
You can use the following helper script to create the user and group in your system:
./create-user.shAfter building and creating the letmeind system user,
run the install-server.sh to install the letmeind server to
/opt/letmein/:
./install-server.shInstalling the server will also install the service and socket into systemd and start the letmeind server.
The server is used to receive knock packets from the client. Upon
successful knock authentication, the server will open the knocked port
in its nftables firewall.
The client application letmein is portable and should
run on all major platforms. Tested platforms are:
The server application letmeind is Linux-only, because
it only supports nftables as firewall backend.
Please be aware that the user identifiers and resource identifiers from the configuration files are transmitted over the network without encryption in clear text.
Make sure the user identifiers and resource identifiers do not include any private information.
These identifiers are merely meant to be an abstract identification
for managing different letmein keys, installations and
setups.
The main design goals of letmein are:
-4 and -6 client
command line options.The program has carefully been designed to be secure, to the best of my knowledge.
However, nobody is infallible.
I am interested to hear your opinion.
If you found a security vulnerability, see the vulnerability reporting process for how to proceed.
There are a couple of known weaknesses that exist in letmein. In this paragraph we discuss why these weaknesses exist.
These weaknesses are not addressed by the design of letmein to make the design simpler. It is a tradeoff between a simple design and a weakness that doesn’t practically impact security.
It is believed that these weaknesses do not make letmein insecure in practical use. The simple design is supposed to reduce the attack surface and as such improve security.
Knock packet is
not protected against a replay attack.
Knock
packet can successfully be replayed by an attacker. But that doesn’t
mean much. The attacker will still not be able to successfully solve the
Challenge. The authentication of the Knock is
only in place, because it’s easy to implement in the given design and it
stops port knocks that don’t have a key at all early.Copyright (c) 2024 Michael Büsch m@bues.ch
Licensed under the Apache License version 2.0 or the MIT license, at your option.