Show HN: A proactive way of securing your Linux server
github.com
github.com
If you're using pubkey or certificate auth, have disabled password auth, and restrict what users are allowed to ssh in, this just feels like added complexity and points of failure (not to mention a possible source of crypto vulnerabilities) with not much added benefit.
Having said that, it's a cool project in general, and I feel like it could be useful to dynamically manage access to large groups of servers (perhaps not just ssh; you could use it to manage access to https interfaces or other things). Then again, if you have a large group of servers, you should have those ports blocked to the world and only allow access through a VPN and/or jump boxes.
We moved from port 22, and within days all ports were scanned and the attacks started again on the new port.
We are working on a different scheme to thwart the attackers.
Even with just one server it can make sense to use a VPN.
In my case I am running Wireguard on my server, and I use that VPN among other things for being able to remote in to my grandfather’s computer when he needs help. Since we are both behind NAT at our respective homes, a VPN was needed and I chose to use Wireguard. Wireguard is super easy to set up, and works on FreeBSD (my server and also my desktop run this), macOS (my laptop and my grandfather’s desktop run this), iOS (my phone) and Linux (currently I don’t run Linux but I have for many years and will certainly run it again on some machines again). I don’t use Windows, almost haven’t for a decade with a few small exceptions, but I think Wireguard is available for Windows also.
So anyway, I decided back when I set up Wireguard hey, now that all my machines and my phone is on this VPN, there’s really no reason to expose SSH to the internet anymore. So ever since then I have had sshd listen only on the VPN. It works great.
Besides, set up your keypair, set "PasswordAuthentication no", and you don't have to worry about brute-force password attacks anymore (if the "log noise" bothers you, as some people claim, you can filter those out).
Compare this to a secret password, if someone hijacks the SSH connection and you accept the host key (which everyone says yes to on the first connection), you give away your password which can then be used by an attacker to get access to the real server.
SSH is almost definitely handled by your OS package management, so any problems with SSH will be patched immediately as found, and automatically distributed to you if you opt in to that (or you will be notified there are updated to apply, and you can do so). If this system is not packaged and distributed by your OS, you're going to have to keep up to date on any security problems with it yourself. It may have less permissions by default because it's running through a container, but that doesn't always help (kernel exploits are a thing, a lot of successful hacks are first getting the ability to run unprivileged arbitrary code, then using a kernel exploit to gain privileged access.
There's something to be said for well engineered, well vetted simplicity. This is an interesting solution, but I would much rather just lock down the set of IPs it allows connections from to SSH to a very small subset of known good ones, including the IP address of a $5 DigitalOcean VM that I keep off but can turn on and connect to and use to jump to that box if at a location I can't connect from directly (and you can set a timed firewalld rules to allow access from your current IP if it's not permanent and shut of the VM again). Of use the free tier of AWS if you can get it to have a specific IP for free as well (I don't recall what you can get in the free tier other than the smallest VM).
Edit: Oh, and disable password auth. That's SSH hardening action #1, I kind of just assume everyone does that (because they should, if they can).
With no mention, it makes me wonder if the author is familiar with the history of this approach, which is not a good sign.
And yeah, I'm skeptical. Is this new, uncommon/unvetted, and somewhat complex "access" app just another place for potential vulnerabilities that give someone the keys to the kingdom?
Seeing as the word “proactive” keeps reappearing in a similar fashion to “decentralized” does for people promoting their blockchain app (invariably hosted web app on google cloud or aws, using Ethereum nodes from Infura, and administered by a single permissioned smart contract) yeah, sounds like OP is excited they invented this new principle.
But at that point, you’ve potentially introduced exploitable bugs in the network stack.
I would never allow my prod systems to be potentially exposed by an api that runs as root. (And the documentation is incorrect on that; it should run as an unprivileged user with sudo privs to only run a wrapper script that runs firewall-cmd).
This also makes little sense in the context of configuration management, which should be enforcing a static set of iptables rules.
Doesn't setuid just change to whichever user owns the file? So with setuid root only needs to own it if you want full administrative privileges.
I also haven't played with setcap myself. My understanding is that capabilities don't rely on file ownership but instead on extended file attributes (which can't be changed by ordinary users on a correctly configured system). So root doesn't need to own the file, but of course granting any capabilities to a binary for which a non-root user has write permission seems like it would be a really bad idea in general.
> And reside on a filesystem without the nosuid mount option?
Yes, it appears the nosuid mount option disables file capabilities. (As one would hope!) But I'm not really seeing how that's an issue since (for example) bind mounts and btrfs subvolume mounts both allow changing the nosuid option.
(man 7 capabilities)
> 1. For all privileged operations, the kernel must check whether the thread has the required capability in its effective set.
> 2. The kernel must provide system calls allowing a thread's capability sets to be changed and retrieved.
> 3. The file system must support attaching capabilities to an executable file, so that a process gains those capabilities when the file is executed.
(https://wiki.archlinux.org/index.php/Capabilities)
> Capabilities are implemented on Linux using extended attributes (xattr(7)) in the security namespace.
(man 2 execve)
> The capabilities of the program file (see capabilities(7)) are also ignored if any of the above are true.
This way, most of the junkware that does rude things to port 22 is banging on a closed door; the slightly more effective junkware that actually finds the SSH port gets banned immediately, because I know anyone trying to log in with a password is full of it.
You could potentially create a fake daemon that listens on port 22 and just logs every access attempt, and set up fail2ban to block any IP that even opens a connection to the port, regardless of what they try to send over it.
Actually, I think iptables has a LOG target that can log connection attempts even without something listening, so that could work too and be much simpler.
Another issue; the overlap between SSH scanners also running HTTP/S attacks is negligible.
From experience; what makes sense is shifting your SSH port away from 22, disabling password based authentication, whitelisting your IP address from your cloud provider's firewall, and still aggressively auto-banning incorrect logins with fail2ban.
Then, for good measure, implement a WAF to protect your HTTP/S traffic as well.
Do not turn your production system into a honeypot. Only do this with a separate system that contains no valuable data.
Short of an 0day in the SSH service, I expect brute forcing the private key(s) to take longer than I have years to live.
This had an unintended effect... most of my automated HTTP(s) attacks stopped as well-- presumably because the IPs of compromised machines were already blocked.
fail2ban is the first thing install. It’s also easy to understand, which I love. It’s basically regex on log files.
Most distros those days come with journald, and to best of my awareness journald does not have per-unit (or better, per unit per message type) configurable retention.
Still in the TODO list today: https://github.com/systemd/systemd/blob/908dbc70d6abeb9f6562... (since at least 2016), with corresponding issue gathering no traction: https://github.com/systemd/systemd/issues/9519
* Something this simple shouldn't need k8s, unless it was intended as an exercise by the developer for that reason
* It combines the idea of using a non-standard port with certificate based authentication, which you can already do with SSH-- it's functionally the same with more steps
Hypothetically, this approach can be more powerful as a centralized service running elsewhere, (i.e. cloud, your remote DC) and used for a whole bunch of jump boxes and end-users. End-users could run a "check-in" script wrapping around SSH that notified the service that a user was imminent, and then could check the server to see if 1) The bearer token is accepted for the destination server 2) That the destination server checked in, saw the new incoming request, and has already opened the port -- And then proceeds to run the SSH command if all is well, or fails with appropriate error.
This has serious quality issues and any competent and nice sysadmin should say _nope_ to run this in any serious environment, for your own good ;-)
IP addresses are just strings? At least parse/validate for IPv4/IPv6.
Why yet another database? Can't the rules be loaded from the running system?
Why not just ipset-persistent + knockd + portsentry? I know it is easy to get overexcited with a new pet project, but just be careful to not put this kind of stuff in a production system kidos.
I saw you are already implementing some ideas from my inputs, that is great, please keep it going.
Eventually, in notorious systems, you may face issues of having too many /32 rules, a good idea is to implement some mechanism to defragment them into single/bigger contiguous cidr blocks. There are other firewall implementations that can serve as inspiration.
OR
I recommend using ipset with a single iptables rule to match/block from the set. This way you don't need to reload stuff everytime. (Just add the ips into the set)
PS: I hope you don't mind my snark comments, HN is fun.
The way that I like to do this is to have a common 'entry point' for all my cloud systems. Instead of whitelisting IPs on every VPS or cluster I build, I just add them to the ACL on my management server. All the other systems only allow SSH connections in from the bastion server. In practice, it works like this:
* Add IP to the whitelist file in my change control
* Run the Terraform script to update the DigitalOcean ACL
* Start an SSH agent locally and add the key for the bastion, as well as the key for the destination
* Connect to the destination server by using ProxyJump
So, connecting to a box would always route through my bastion system first, like this:
ssh -J mgmt.mydomain.net cool-app-server.mydomain.net
I've been doing this for a couple years, and it works great. I practically never have attempted login attempts on my systems. And, since I use an ssh agent to forward the keys through the bastion without ever actually storing them, a compromise of that system doesn't really give the attacker anything other than access to port 22 on a bunch of systems that they wouldn't know where to find. Only the most sophisticated attack (https://xkcd.com/538/) would lead to a real compromise.In my experience the #1 real attack vector is the shouty middle manager demanding that: "Everything be opened right now because some outsourced admin says he needed it".
It's nigh impossible to use technological measures to defeat a CxO or a department head.
Rate limiting the number of requests that can be made to the application
So this just moves the brute forcing target from ssh to a web app. A lot of work for no added security.
If someone's bruteforcing their way in via ssh it's probably worthwhile blocking them before they start trying other services.
If "brute force" attacks are a concern, you are a fool that has password authentication turned on.
If you have password authentication turned off, why does it matter?
I know everybody likes Fail2ban, but these two iptables rules (or something just like them) actually work better and don't fill up your logs:
iptables -t mangle -A PREROUTING -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --set
iptables -t mangle -A PREROUTING -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
To actually protect the network layer, use some kind of VPN with MFA.Unfotunately it also relies on Kubernetes which means that using it for a single system isn't practical. At least, not for this server owner.
My own approach is simply security by obscurity (a non standard port) with APF/BFD doing the needful for locking bots out if they figure out the port. I've had to change ports only once in 6 years, so it's working to keep bots out rather nicely.
And really that's all these things are- a way to keep bots out. A determined attacker will figure this stuff out anyway.
But install a new firewall management system? Sounds like it will definitely introduce more risks than the problems it solves, which for the SSH example isn't really a problem at all.
And yes, ssh pubkey + fwnop + WireGuard + fail2ban is ridiculous overkill, but hey, it’s my homelab server. That’s how I learn this stuff.
If having 22/TCP open to the world is an issue, then set up Wireguard on the host and only allow SSH connections that are coming in over the Wireguard interface.
Got a bunch of machines to deal with? Set up a jumpbox or two running OpenBSD, lock it down, give your users access to it via SSH (optionally, over a Wireguard connection) and then only allow SSH access to all of those other hosts from the jumpbox(es).
Then there's the fact that I have a whole lot more trust in the security of OpenSSH than I do some random web application!
To me, this just seems kinda pointless -- there's a bunch of other, better (IMO) ways to deal with this -- but I guess if it fits your needs ...
Step 2: deploy this.
Step 3: ...
Step 4: profit!
The line between something like this and a locking the service behind a VPN is very narrow. They both achieve the same thing, protect high risk/value targets by not even allowing unauthorized people to talk to them at the network level.
The JWT you got after you plugged the private key into a random website is going to protect access to your machine?
If you want something like this look into platform level firewalls (ex: AWS security groups) or run spiped in front of your SSH server. I’d trust that a hell of a lot more than a REST API.
host i-* mi-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
[1] https://docs.aws.amazon.com/systems-manager/latest/userguide...
This a JWT based port knocking over https framework that would be useful when used in a much broader sense.
Framing it as proactive fail2ban is technically correct, it also masks the other and more powerful use-cases.
I could see this in use as a vpn bypass for a prosumer production system, where normal operational commands go over a secured vpn but in a pinch, you can disable that restriction for direct control during a partial failure.
Option A) Expose all firewall rules via some hacky web-based API.
Option B) https://nvlpubs.nist.gov/nistpubs/ir/2015/NIST.IR.7966.pdf
Then you can't even get login attempts, unless they're from the same IP... am I missing something?
Wasn't SSH made for that?