Latest / Linux Server Admin with Fexingo: Sysadmin, Bash, and Server Engineering / Why Your SSH Key Setup Is Probably Wrong
Transcript
- Lucas: So most people who manage a Linux server think they've got SSH security handled. Generate a key pair, disable password login, job done. Luna: Right — and that's basically the standard advice you find in every 'hardening your server' tutorial. Lucas: Exactly. But here's the thing — that advice misses several critical layers, and a fintech startup I talked to earlier this year learned that the hard way. They had password authentication off, strong RSA keys, the works. But during a routine penetration test, the auditor found that every single engineer's key had no restrictions. Luna: No restrictions meaning… anyone with access to that private key could log in from any IP, to any account? Lucas: Exactly. And that's exactly what happened. One engineer's laptop got compromised — a simple phishing email — and the attacker had a full shell into production within minutes. The company got lucky: the attacker was noisy and they caught it. But it triggered a complete rebuild of their SSH configuration. Luna: So what should they have done differently? I mean, beyond the obvious 'don't get phished'. Lucas: Right — and that's the point. You can't rely on every engineer never making a mistake. So you build layers into the key itself. OpenSSH's authorized_keys file supports options that most people never touch. The two I want to focus on: from= and command=. Luna: I've seen from= in some hardened setups — it restricts which source IP addresses that particular key can authenticate from. Lucas: Exactly. So you can say 'this key is only valid when the connection originates from the office IP range or the VPN subnet.' That way, even if the private key is stolen, it's useless from a random coffee shop or a foreign IP. Luna: That's elegant. And command=? Lucas: Command= forces a specific command to run on login — the user never gets a shell. So for automation scripts or monitoring agents, you can pin the key to a single command like '/usr/bin/check_disk' or '/usr/lib/nagios/plugins/check_swap'. If an attacker tries to use that key, they just get that one command executed and then the session ends. Luna: That's huge for CI/CD pipelines and backup scripts where you don't need interactive access. Lucas: Exactly. And you can combine them. So a typical entry in authorized_keys might look like: from='10.0.0.0/8,192.168.0.0/16',command='/usr/bin/backup-wrapper.sh',no agent forwarding,no port forwarding ssh-ed25519 AAAAC3... Luna: That's a dense line. But it's more secure than a naked public key. Lucas: Much more. And let's talk about key types for a second. That example used ed25519 — which is the default in newer versions of OpenSSH. It's faster than RSA, smaller, and considered cryptographically strong. The old advice of 'use RSA 4096' is still fine, but ed25519 is now the recommendation from the OpenSSH project itself. Luna: Right, I've been generating ed25519 keys for a while. But I still see a lot of legacy RSA keys in production. Lucas: And that's okay. RSA 4096 isn't broken. But if you're generating new keys today, there's no reason not to use ed25519. The key itself is only 68 characters — versus RSA's 700-plus. It means less data over the wire, faster authentication, and better performance on embedded systems. Luna: Okay so new keys: ed25519. Existing keys: maybe migrate during a maintenance window. What about the authorized_keys file itself — any gotchas? Lucas: Yeah, a big one. The authorized_keys file should be owned by the user and only writable by that user. If it's writable by anyone else — even root, in some configurations — SSH will refuse to use it. But more subtly, the directory — the user's.ssh directory — also needs strict permissions. 0700, owned by the user. Luna: I've seen that trip people up. They copy a key file with the wrong ownership and then wonder why sshd ignores it. Lucas: Exactly. And debugging that is a pain because the logs just say 'Authentication refused' with no detail. You have to check /var/log/auth.log and look for 'bad ownership or modes'. Luna: Let's talk about the bigger picture. You mentioned the fintech startup — what did they end up doing? Lucas: They moved to a full SSH certificate authority model using OpenSSH's built-in CA functionality. Instead of distributing public keys to every server, each server trusts the CA's public key. Then engineers get short-lived certificates signed by the CA. If a laptop is compromised, the certificate expires in hours, not years. Luna: That's a significant operational lift, though. Not every team has the bandwidth for that. Lucas: Totally fair. But even without a full CA, you can get 80 percent of the benefit just by adding from= and command= restrictions, and using ed25519 keys. One hour of configuration work can prevent a catastrophic breach. Luna: If today's episode was useful to you and you want to keep it ad-free, buy me a coffee dot com slash fexingo helps. Lucas: Yeah, it really does. Listener support is the only reason we can spend time on this level of detail without sponsors pushing us to keep it shallow. Luna: Alright. So we've covered key types, restrictions, and permissions. What about sshd config options — any you'd flag? Lucas: Yeah, two come to mind. First, set 'MaxAuthTries' to a low number, like 3. That limits brute-force attempts per connection. Second, use 'AuthenticationMethods publickey' — explicitly, so even if someone accidentally re-enables password auth, it won't work. Luna: And that's a common misstep — people think 'PasswordAuthentication no' is enough, but if a newer version of sshd defaults to keyboard-interactive, you can still get password-like prompts. Lucas: Right — and keyboard-interactive can include PAM modules that allow password authentication even if PasswordAuthentication is off. So explicitly setting 'AuthenticationMethods publickey' closes that loophole entirely. Luna: So to recap: ed25519 keys, from= and command= restrictions, strict permissions on.ssh, and explicit AuthenticationMethods. Lucas: Plus one more: audit your authorized_keys files regularly. Use a script to check for keys without restrictions, or keys that haven't been used in 90 days. Remove them. The fewer keys floating around, the smaller your attack surface. Luna: Do you have a recommended tool for that? Lucas: I like 'ssh-audit' by jtesta — it's a Python script that checks both client and server configuration. But even a simple grep for lines without 'from=' or 'command=' will catch the low-hanging fruit. Luna: Good advice. And if anyone listening is in the middle of a server migration, now is a perfect time to clean up SSH keys. Lucas: Exactly. Treat SSH keys like passwords — rotate them, restrict them, and don't leave them lying around. Your future self will thank you when you avoid that 3 a.m. incident.