The genesis of this guide was documenting a repeatable process for creating a hardened baseline configuration to protect friends and family using Raspberry Pi (RPi) devices for Internet of Things (IoT) projects, many of which were exposed to the internet.
The documentation turned into something that can serve as a general recommendation for security hardening and monitoring (see the NIST Cybersecurity Framework for Protection and Detection). This includes some common applications used in IoT projects, such as a web server configured to serve static web pages.
My goal for this guide is to help anyone with minimal knowledge of security to deploy a basic RPi project with confidence. Scripts and configuration files are well commented so folks understand why it should be configured that way and help any troubleshooting that may need to occur.
A properly configured system should perform only the necessary functions required to deliver a service. This, in combination with patching security vulnerabilites, vastly decreases the attack surface of your device and therefore the ability for someone to compromise your system.
Raspbian 11 (bullseye)
operating system. I have made every effort to make these configurations work while performing them remotely on a headless system, but I recommend having your Pi physically accessible with a screen and keyboard to recover from locking yourself out.
git clone https://gitlab.com/cgoff/raspberry-pi-hardening.gitYou can keep it updated with the following command:
git pull
Ideally access to the system is restricted as far as possible without impacting usability. This means controlling who can talk to the RPi device, and who the RPi device can talk to. This can be done in the form of controlling it at the network, and at the device. The most secure method would be doing both as this provides layers of security that can compensate for vulnerabilities at the expense of complexity.
At the network level, you can do this a few different ways. The recommeded route would be to logically isolate your RPi device with virtual local area networks (VLANs). Configuring your network equipment is beyond the scope of this guide, however this should provide you with enough information to research the solution.
At the system level you can provide access control with the local firewall to control access to listening services, or some services offer application level whitelisting or authorization configurations (e.g. SSH daemon can whitelist by IP and or username/pw, certificates, etc.).
The goals should be the following:
1. Make sure only approved devices can talk to the RPi. 2. Make sure the RPi can only talk to approved devices. 3. Alert a human when an interesting event occurs. 4. Automatically apply security patches. 5. Make sure the RPi can only run approved software. 6. Control privileged user access. 7. Make sure only approved software can run.Of course it's easier said than done.
pi
user, which will be disabled. After this, commands requiring root or admin privileges will have to be preceded with the sudo
command.
pi
pi
, enter the following command and follow the instructions:
passwdI recommend choosing a password 24 characters or more in length. Use a password manager such as 1Password or an online tool such as this Diceware generator to make this process easier and more secure.
sudo adduser <username> sudo adduser <username> sudoExit, then log back in as your new user. Run the
visudo
tool; if successful your new user has the correct privileges assigned.
sudo visudoNow log back in as your newly created account and disable the
pi
account.
pi
accountpi
account:
sudo usermod --lock --expiredate 1 piTest this by logging out then attempting to log back in as the
pi
user account. If it fails, this was successfully configured.
sudo vim /boot/config.txtAdd the following to the file:
# Disable wireless services dtoverlay=pi3-disable-wifi dtoverlay=pi3-disable-btExit and reboot your Pi.
sudo reboot
msmtp
so system alerts (such as unattended upgrades status) can be e-mailed to you. You will need configuration information from your e-mail provider for this next step.
Example e-mail provider documentation for Gmail.
sudo apt install msmtp msmtp-mta
sudo vim /etc/msmtprcPlace the following in the
msmtprc
file:
# Default values for all accounts defaults auth on tls on # Gmail account gmail host smtp.gmail.com from username@gmail.com port 587 user username password **your password** # Syslog logging with facility LOG_MAIL instead of the default LOG_USER. syslog LOG_MAIL # Set a default account account default : gmail
Note that the above configuration logs to the syslog service by default. This can be found here:
/var/log/mail.logTest the configuration:
$ echo "This is a test email" | msmtp --debug your@emailaddress.com
unattended-upgrades
for automatic updates of your RPi system.
sudo apt install unattended-upgrades apt-listchanges apticron
Copy 50unattended-upgrades
from the git respository into /etc/apt/apt.conf.d/
↪ 50unattended-upgrades
configuration file.
sudo unattended-upgrade -d --dry-runLog location for monitoring:
/var/log/unattended-upgrades/unattended-upgrades.log
ssh
configuration
ssh-users
group:
sudo groupadd ssh-users sudo usermod -a -G ssh-users $USEROn the host system(s) where you will be connecting to the RPi system from via SSH you will need to generate a private and public keypair, and then add the public key of the host system to the RPi system:
ssh-keygen -t ed25519 -a 777 ssh-copy-id -i <pub key> <username>@<rpi hostname>Edit
/etc/ssh/sshd_config
with the following configuration changes:
PermitRootLogin no PubkeyAuthentication yes PasswordAuthentication no UsePAM no X11Forwarding no AllowGroups ssh-usersRestart ssh to apply the configuration:
sudo systemctl restart ssh
network id
,
you will need this to connect devices to the network.
Specific to the Raspberry Pi, run the following to install the latest version of the ZeroTier agent:
curl -s https://install.zerotier.com | sudo bash sudo zerotier-cli join <network id> assign from portalAlso install the latest agent for the system(s) you wish to have remote access to your Raspberry Pi (e.g. a Windows desktop).
ufw
.sudo apt install ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw limit ssh sudo ufw allow http sudo ufw allow https sudo ufw logging on sudo ufw enableNote that the
ufw limit
command only works for IPv4 connections at this time. It is pre-configured to deny connections if a single IP address attempts to initiate 6 or more connections within 30 seconds.
Verify the firewall rules:
sudo ufw status numbered
nginx
Web Servernginx
and harden the configuration.
Install nginx
:
sudo apt install nginxIn order to make changes to the default html root, you'll need to change the ownership of the
/var/www/html
directory to both your user and the www-data
group:
sudo chown -R "$USER":www-data /var/www/htmlYou should now be able to manage files and directories in the html root as you please. This isn't required, but we can add some rate-limiting functionality to help prevent small-scale DoS attacks:
sudo vim /etc/nginx/nginx.conf # Add to the html{} section: http { limit_req_zone $binary_remote_addr zone=global:10m rate=20r/m; limit_conn_zone $binary_remote_addr zone=addr:10m; }
sudo vim /etc/nginx/sites-enabled/default # Add to the server{} section: server { location { limit_req zone=global burst=10 nodelay; limit_conn addr 1; limit_rate 100k; limit_req_status 429; } }Finally, let's disable the
www-data
user from making outbound connections. If this account is compromised, it could potentially be used to make outbound connectivity. Note that you have essentially two firewalls, one each for IPv4 and IPv6 protocols. ufw
makes changes to both automatically, but when manually entering iptables
commands both iptables
and ip6tables
must be configured.
sudo iptables -A OUTPUT -o ethX -m owner --uid-owner www-data -j REJECT # Add to the IPv6 firewall; only required if utilizing IPv6 sudo ip6tables -A OUTPUT -o ethX -m owner --uid-owner www-data -j REJECT # Reload the firewall config to apply sudo ufw reload
sudo apt install certbot python-certbot-nginx
sudo certbot --nginxYou can verify that it worked by checking your site through SSL Labs. Let's tidy up the SSL configuration and get that A+ score:
sudo vim /etc/letsencrypt/options-ssl-nginx.conf # Add the following to options-ssl-nginx.conf ssl_session_timeout 10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/<domain>/fullchain.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
Restart nginx
to apply the configuration change:
sudo systemctl restart nginx
nginx
add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options SAMEORIGIN always; add_header X-XSS-Protection "1; mode=block" always; add_header Strict-Transport-Security "max-age=3600; includeSubDomains; preload" always; add_header Content-Security-Policy: "default-src 'self' <your domain> *.<your domain>"; add_header Referrer-Policy "same-origin"; add_header Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec crowdsec-firewall-bouncer-iptables -y
ossec
server to complete. If you do not have one, I recommend Security Onion, which includes it as part of the distribution.
Wazuh
agent configuration
Wazuh
is an updated fork of ossec
. Wazuh
still utilizes ossec
configurations, however for the purposes of this guide you can use the terms interchangeably.
Since there isn't a Raspbian
binary available from the developer, you'll need to compile from source. Use the defaults from the install.sh
file except selecting AGENT and using the IP address of the ossec
manager you already have in your environment.
apt install make gcc libc6-dev curl policycoreutils automake autoconf libtool curl -Ls https://github.com/wazuh/wazuh/archive/v3.9.5.tar.gz | tar zx cd wazuh-* sudo ./install.sh sudo /var/ossec/bin/agent-auth -m <ip of ossec manager> -p 1515 sudo /var/ossec/bin/ossec-control startVerify connectivity by tailing the
ossec
log on both the manager and agent.
tail /var/ossec/logs/ossec.log
As a bonus, if you'd like to send the Wazuh
/ossec
data (and more) across a seperate security monitoring network, read my guide on how to do so with ZeroTier.
logwatch
logwatch
is a handy tool we will configure to send daily system summary messages to your e-mail.
sudo apt install logwatch sudo vim /usr/share/logwatch/dist.conf/logwatch.confAdd the following to
dist.conf
, assuming you have previously configured msmtp
:
mailer = "/usr/bin/msmtp user@domain" TmpDir = /tmp MailFrom = noreply@domain Range = yesterday Detail = high Format = htmlTest your configuration:
sudo /etc/cron.daily/00logwatch
SHODAN now offers a free monitoring service with some restrictions. If you have a publically facing RPi project, this is worth configuring. Updates will be sent to your e-mail address.
Once configured, SHODAN will notify you of vulnerabilities found at your IP address(es), and visibile services listening on ports.
Qualys offers a free Community Edition of their vulnerability scanner which can be used on up to 3 external assets. Handy for learning how their product works, and keeping your RPi services updated and properly configured.
If you're interested in remotely monitoring the uptime of your RPi, I'd suggest signing up for Freshping or Uptime Robot. These services, once configured, will notify you if your IP address becomes unavailable.
Typical inexpensive SD cards have a limited number of writes available. The solution to this is to prolong the life by writing to memory and periodically writing the data back to disk.
In order to do this with minimal configuration, there is an excellent little tool called zram-config
which makes the whole process painless. Installation and documentation can be found on at this git
repository: https://github.com/ecdye/zram-config.