Back to the Servers 1
A Practical Guide to Server Hardening with Cloud Init The IT industry is gradually shifting from cloud solutions back to traditional servers, which raises both new and old questions. One of the key questions is: how do...
A Practical Guide to Server Hardening with Cloud-Init
The IT industry is gradually shifting from cloud solutions back to traditional servers, which raises both new and old questions. One of the key questions is: how do we secure a server in an elegant, IaC way?
As DHH mentioned at the last Rails World: “Just close the main door” - you often don't need as much security complexity as you might think. In this post, I'll share my approach to "good enough" server hardening for web applications.
Good old Cloud-Init
For server provisioning and hardening, you don't need cutting-edge tech. A lot can be accomplished with cloud-init, a tool that has been around for over a decade and is used by major cloud providers.
Since I often deploy on Hetzner, which natively supports cloud-config during server provisioning, this is my go-to solution. Below is a full example of a cloud-config setup. Take a look and see if you can guess what it does (don't worry - it's not too complicated).
#cloud-config
packages:
- apache-utils
- fail2ban
- ufw
- docker.io
- curl
- git
package_update: true
package_upgrade: true
users:
- name: app_user
groups: users, admin, docker
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZe78S7iGyDQJW8Dx3gYl5Iloa/CKFeFIyzu3p9uGH6 zde@hasek
runcmd:
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
- systemctl enable fail2ban
- ufw allow OpenSSH
- ufw allow 80
- ufw allow 443
- ufw --force enable
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)KbdInteractiveAuthentication/s/^.*$/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)ChallengeResponseAuthentication/s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
- sed -i -e '/^ClientAliveInterval/ c\ClientAliveInterval 60' -e '/^ClientAliveCountMax/ c\ClientAliveCountMax 10' -e '$a\ClientAliveInterval 60' -e '$a\ClientAliveCountMax 10' /etc/ssh/sshd_config
- sed -i '$a AllowUsers app_user' /etc/ssh/sshd_config
- rebootBreaking Down the Config
Let's go over each section of the configuration:
#cloud-config: This line is crucial. Without it, the script won't execute, and unfortunately, Hetzner won't report any errors.packages: Specifies the packages we want to install (e.g., fail2ban, ufw, docker.io).
users: Defines the user app_user. I prefer using a unique, non-standard username (instead of root or admin) to add a layer of security. We also add an SSH public key here. Be sure to replace the key with your own!
runcmd: Contains commands to secure the server. We:
- Disable root login and password-based authentication.
- Limit authentication attempts.
- Set SSH timeouts and restrict access to app_user.
- Configure the firewall to allow only essential ports (SSH, HTTP, HTTPS).
What If My Provider Doesn't Support Cloud-Config?
No problem! Cloud-config, powered by cloud-init, is open-source and works on most major Linux distributions. Here's how you can set it up manually:
- Install the package:
apt install cloud-init - Copy your cloud-config to
/etc/cloud/cloud.cfg.d/99-custom-config.cfg - Validate the syntax:
cloud-init schema --config-file /etc/cloud/cloud.cfg.d/99-custom-config.cfg - Run the configuration:
# Clean the Cloud-Init State
sudo cloud-init clean
# Reinitialize Cloud-Init
sudo cloud-init init
# Apply the configuration
sudo cloud-init modules --mode=config
sudo cloud-init modules --mode=finalWith this setup, you'll have a hardened server ready for web app deployment - all while keeping things simple and manageable. Happy coding!