In the previous installment of the home lab build guide, I covered the initial setup of the Raspberry Pi and the configuration of a few useful network services, spefically, DNSmasq, NTP, OpenVPN and Wake-On-LAN. In this article I'm going to cover some ways of securing the Raspberry Pi and it's services in preparation for making the features accessible from the public Internet.
As I'm sure most people will understand, there is no such thing as a completely secure computer system. If someone is determined and skilled enough they will always find a way in. That said, we can prevent ourselves from being a soft target. Unlike some attacks against large corporate and government entities, attacks against individuals are unlikely to be targetted and coordinated and will more often than not be opportunistic in nature, unless you're Edward Snowden or Julian Assange, in which case you might need to think about your security strategy in a bit more detail.
For us mere mortals however, there are a number of simple steps we can take to keep ourselves relatively safe. In this article, I'm going to cover the steps I performed to harden the Raspberry Pi before exposing it to all the nastiness of the Internet.
A Word of Warning
If you're a bit prone to typos like me, it might be a good idea to bust out the USB keyboard and HDMI display. If you're not careful you can quite easily lock yourself out of the Raspberry Pi while messing around with iptables and the OpenSSH service. Even if you mess up the firewall or completely screw up SSH, you'll still be able to login locally with a keyboard and display attached to the device.
Set Strong Passwords
This is probably quite obvious, but you really ought to set strong passwords for all your accounts, not just the Raspberry Pi logins. Use a mixture of upper and lowercase letters, numbers and special characters and make the password as long as practical. Don't use dictionary words or adaptations of dictionary words.
Substituting numbers for letters in dictionary words is also a bad idea as it makes them easier to brute force and even potentially guess. Another good approach is to memorise a long phrase. A sentence with multiple words in it works well as a password, simply because of the length of it. Just be sure not to use a famous phrase or quote. Come up with something original and abstract and repeat it until it's burned into your brain.
The problem with strong passwords is that they're often difficult to remember, and so people may end up writing them down. Generally this is a bad idea, but in the case of the Raspberry Pi, it wouldn't be the end of the world.
Think about it. I'm assuming you trust those who either live in, or have access to your home, but if you write it down on a bit of paper and leave it on your desk, someone is going to have to break into your house to get the password. At which point, they could presumably just pick up the Pi and put it in their pocket anyway. Plus, your house has just been burgled; you've got bigger things to worry about than whether or not your Raspberry Pi is still secure.
If you take a slip of paper around with you in your wallet with the password written on it, even if it falls into the wrong hands, provided there is no contextual information, like the public IP of the Pi it's not going to be a lot of use to anyway as nobody is going to know what it's for.
A much better idea than writing passwords down however, is to use a programme like KeePass. You can use KeePass to store all of your credentials in an encrypted database. You can then put that database onto cloud storage such as Dropbox or Google Drive so the same database is accessible from multiple devices. This is handy if you have a desktop computer, laptop, tablet and or smartphone. There are some additional considerations for this kind of setup, although it's quite common and information is readily available through a quick online search. I plan to write an article in the not too distant future covering this type of KeePass setup in more detail.
This is one of the simplest steps you can take towards keeping your system secure. As security vulnerabilities are discovered and exploits find their way into the wild, the open source community are hard at work fixing code, plugging holes and releasing updated versions of vulnerable packages for numerous Linux distributions. Updating the software packages in Raspbian is a relatively simple affair. First login, then run the following commands:
# sudo apt-get update
# sudo apt-get upgrade
I run my Raspberry Pi in a "headless" state, meaning I don't have a keyboard, mouse or display connected to it for the majority of the time. To access the Pi and carry out administrative tasks I use SSH and connect via a client like PuTTY over my local network. I also have the ability to connect to my Pi via SSH over the Internet. The problem with this, is that if I can connect to it over the Internet, so can anyone else in the world with an Internet connection.
There are numerous ways to make this service harder to compromise. I'm going to cover two of them. First, change the default SSH port. I'm not normally a fan of security through obscurity, but after watching my logs fill up with hundreds of messages about connections to the SSH port followed by failed login attempts from all over the world, I decided I'd better set a different port number.
The second, and most important thing to change about the default OpenSSH configuration is to disable keyboard-interactive logins. In other words, you don't accept a typed password as a method of authenticating. This makes it impossible for an attacker to brute-force or guess the username and password via SSH to gain access to the system. The only downside of this approach is that you'll need to generate a public/private keypair and then install that private key on every client from which you want to access SSH, which in my opinion is a small price to pay.
Changing the default OpenSSH port
This one is easy to achieve. Edit the OpenSSH server configuration file:
# sudo vi /etc/ssh/sshd_config
Near the top of the file you'll see a couple of lines that read:
# What ports, IPs and protocols we listen for
Simply change the line that reads Port 22 and change 22 to another number. When determining which port number to use, you can choose any number up to about 65000. It's a good idea not to choose a port number that is already associated with another service. The Internet Assigned Number Authority (IANA) are responsible for the allocation of port numbers for different "well known" services. You can check and search their existing port number assignments here.
To be honest though, you can choose whatever port you want. Provided you don't specify a port number that's going to be used by another service you're running on your Pi, you'll be fine. For example, don't set the OpenSSH port to 80 and then install Apache and expect everything to work fine, because it probably won't. Although having said that, there are ways of getting some services to share the same ports. Just pick a high port number like 54321 for example and use that for SSH.
Disabling Password Logins in OpenSSH
The first step here, is to create a public/private keypair. You need to create the keypair on a client device and not on the Raspberry Pi. This can be done using Puttygen, a utility which comes with the awesome PuTTY SSH client. You can download Puttygen here.
Launch Puttygen and on the GUI that appears, click Generate.
Move your mouse around randomly and as you do so the key will be generated and the progress bar will fill up.
Once completed, you'll be presented with the following screen.
You can set the Key Comment to anything you like, and setting a key passphrase is entirely up to you. If you plan on storing the private key file somewhere where it is potentially vunerable, like on a mobile device or cloud storage it is a good idea to set a strong passphrase. If you set a passphrase, you'll be prompted to enter the private key passphrase each time you connect to the Pi, so essentially it'll still be like you're logging in with a username and password, except the password you'll be entering will be for your private key as opposed to authenticate the account you're trying to log into.
Click Save Private Key and select a location in which to save the file and give it a name. If you want it to be accessible from multiple devices it is a good idea to save it into a Dropbox, Google Drive or similar folder location. If you're going to do this however, you really should set a strong passphrase.
In the Puttygen window, select and highlight all of the text in the field at the top of the window above which it says "Public key for pasting into OpenSSH authorized_keys file:".
Logon to your Raspberry Pi as whatever user account you want the key to be used for. Once logged in, issue the commands:
# mkdir ~/.ssh
# vi ~/.ssh/authorized_keys
Yes, sorry only the American spelling of authorised is acceptable here. Paste the public key content you copied out of Puttygen into the new file, then save and close the file by typing :wq!
Set the permissions of the authorized_keys file and the .ssh directory:
# chmod 600 ~/.ssh/authorized_keys
# chmod 700 ~/.ssh
You'll now need to edit the SSH configuration file to prevent the service accepting password logins.
# sudo vi /etc/ssh/sshd_config
Look for the line that reads:
Uncomment it, and change it to read:
All you now need to do to activate the changes is restart the SSH service. Be warned, you'll probably lose your existing SSH session when you do this, so if you got anything wrong, you could find yourself locked out of the Pi and will need to attach a local display and keyboard to get back in and correct any problems.
To restart the SSH service enter the command:
# service ssh restart
Configuring your SSH Client
Assuming you're using Putty on a Windows client (and you should be, it's superb) you will have to modify your connection settings to be able to successfully connect to your Pi via SSH. It might be a good idea to save a profile once configured so you don't have to repeat these steps every time you want to connect.
The first thing you'll need to do is change the SSH port number from 22 (the default) to whatever number you set earlier in the configuration file.
Under Connection on the left, expand SSH and click on Auth. Click on the Browse button next to the field labelled "Private key file for authentication:". Browse to the location where you saved your private key file earlier and select it.
Scroll back up to the top of the left hand pane and click on Session In the Save Sessions field, type in a name like "My Raspberry Pi" and then click Save. Click Open and you should be connected to your Pi. You'll be prompted for the username. Enter it and press enter. If you chose to specify a passphrase for you key pair, you'll be prompted to enter than before you are logged in. If not, you should be automatically connected and logged in to your Raspberry Pi.
Enabling SSL for Apache
The Wake-On-LAN service runs via a PHP page that is served up by Apache. If you want to expose the Apache service to the Internet, there are a number of steps you can take to secure it. The configuration changes I've made disable unencrypted HTTP traffic on port 80, enable SSL and use .htaccess files to password protect access to the web page itself. Combine this with the fact that to perform any Wake-On-LAN commands you also have to enter a passphase and you've got a fairly over the top Apache security configuration for what it's actually doing.
These instructions assume you've already completed the steps in Home Lab Build: Part 12 -- Raspberry Pi and that Apache2 is already installed. First of all, enable Apache mod_ssl:
# sudo a2enmod ssl
Next you'll need to create a self-signed SSL certificate...
# sudo mkdir /etc/apache2/ssl
# sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/apache2/ssl/server.key -out /etc/apache2/ssl/server.crt
After running this command you'll be prompted to provide details to go into the certificate such as County, State, City and so on. You don't need to enter any real information at these prompts, except for Common Name which should be the IP Address or FQDN of your Raspberry Pi.
Now you'll need to edit the file /etc/apache2/sites-available/default-ssl
Near the top you'll see the ServerAdmin directive. Add a new line just below it that reads:
...where FQDN is the fully qualified name of your Raspberry Pi. You can also use the IP Address instead of the FQDN. Now search for directive...
If SSLEngine is not set to "on" edit it so that it is. Now locate the directives SSLCertificateFile and SSLCertificateKeyFile. You will need to change these so that they reference the files you created with the openssl command a few steps back. It should look like this when it has been edited...
Password Protecting DocumentRoot
While we're editing the Apache configuration file we also want to configure Apache to prompt for a username and password before it allows access to any web page which it servers up. To do this, locate the Directory entry for /var/www. It should look like this:
[cc escaped="true"] <Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride Order allow,deny allow from all </Directory> [/cc]
Edit the line which reads "AllowOverride" and append the word "AuthConfig" so that it looks like this...
[cc escaped="true"] <Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride AuthConfig Order allow,deny allow from all </Directory> [/cc]
Next, we need to create a location outside of the DocumentRoot (/var/www) to store a password file, populate it with credentials and set the permissions of the file and directory so that it can be read by Apache:
# mkdir -p /etc/apache2/secure
We'll create a new user called user1 in a file called passwords:
# htpasswd -c /etc/apache2/secure/passwords user1
Now check which user runs the Apache service:
# grep APACHE_RUN_USER /etc/apache2/envvars
The output should look like this...
This means that essentially, the user "www-data" is the Apache service account. We need to give this user access to our directory and file containing our user and password.
# chown www-data:www-data -R /etc/apache2/secure
# chmod 0660 -R /etc/apache2/secure
Create a file under /var/www called .htaccess with the following contents:
# vi /var/www/.htaccess
AuthName "Authorised access only."
Require user user1
By default Apache is a bit loose lipped. It will spew forth information about itself to anyone accessing an invalid URL. For example:
Straight away, any would-be attacked knows the exact version of Apache, and the OS type we're running. To prevent this behaviour, you'll need to edit the file /etc/apache2/conf.d/security. Locate the lines that read:
Change them to read:
Apache Final Steps
We need to enable the SSL site and disable the default plain HTTP site to prevent Apache from listening on port 80.
# a2ensite default-ssl
# a2dissite default
Finally restart the Apache2 service:
# service apache2 restart
Now when you access your Raspberry Pi via your browser, you should find that the HTTP URL doesn't respond at all and the HTTPS URL first creates a certificate warning in your browser (because it is self-signed) and prompts you for a username and password before loading the page.
Configure iptables Firewall
This can get a bit messy if you make a mistake, so make sure you have a keyboard and HDMI display handy. If things go awry you're going to be unable to access the Pi over the network.
First set the policies on each chain to the default of ACCEPT, this completely opens up the firewall.
# iptables -P INPUT ACCEPT
# iptables -P OUTPUT ACCEPT
# iptables -P FORWARD ACCEPT
Add a rule to accept all loopback interface traffic:
# iptables -A INPUT -i lo -j ACCEPT
Add a rule to drop all inbound traffic with a dodgy destination in the range 127.0.0.0/8:
# iptables -A INPUT -d 127.0.0.0/8 0j DROP
We want to accept inbound connections which are related to outbound connections we initiated:
# iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
Now we need to poke some holes for the services we want to allow:
# iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT # Accept inbound HTTPS connections
# iptables -A INPUT -p tcp -m tcp --dport 54321 -j ACCEPT # Accept inbound SSH connections where 54321 is whatever port you configured SSH to listen on.
# iptables -A INPUT -p udp -m udp --dport 1194 -j ACCEPT # Accept inbound OpenVPN connections.
We also need to accept inbound connections from our OpenVPN clients:
# iptables -A INPUT -s 10.8.0.0/16 -j ACCEPT
We also want to accept any inbound connections from hosts on our local network. I'm assuming you're going to trust your local network, if you don't then skip this step.
# iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT
If you don't trust your local network, you will need to give them access to TCP port 53 so that DNS works and UDP port 123 so NTP works:
# iptables -A INPUT -s 192.168.0.0/16 -p tcp -m tcp --dport 53 -j ACCEPT
# iptables -A INPUT -s 192.168.0.0/16 -p udp -m udp --dport 123 -j ACCEPT
Also, if you want clients on your local network to be able to get DHCP leases, you'll need to add a rule for that too:
# iptables -A INPUT -i eth0 -p udp -m udp --sport 67:68 --dport 67:68 -j ACCEPT
Finally, we'll set the default INPUT policy to DROP, so anything not explicitly allowed above will not get through. We will also set the default OUTPUT policy to ACCEPT, assuming you want the Pi to be able to communicate outbound to anywhere on any port.
# iptables -A INPUT -j DROP
# iptables -A OUPUT -j ACCEPT
Save the firewall configuration to a file using the following command:
# iptables-save > /etc/iptables.conf
The last step we need to perform is to make sure that the firewall rules are activated on each boot before the network interface is brought online:
# vi /etc/network/if-pre-up.d/iptables
Add the following contents:
[cc escaped="true" lang="bash"]
iptables-restore < /etc/iptables.conf
As a final step, you may want to reboot the Raspberry Pi just to make sure all of the services that have been reconfigured have been restarted and also to ensure that your configuration properly loads at each boot.
Exposing the Pi to the Internet
I'm assuming at this point that you're Internet connection utilises some kind of broadband router/modem device. Although these devices all differ somewhat depending on your ISP, generally they seem to offer broadly similar functionality.
To make the services you're running on your Pi accessible from the Internet, you'll need to make some changes on your router/modem to allow traffic through. This can normally be achieved by either setting up Port Forwarding rules or by making your Raspberry Pi a DMZ host. Which approach you choose to take will depend on the functionality provided by your broadband router.
The simplest method, is to simply specify the IP of the Raspberry Pi as a DMZ host. This means, all incoming traffic not related to a connection initiated inside your LAN will be passed directly to the Pi by the router. This method will completely expose your Pi to the Internet, so the iptables configuration is doubly important.
Port forwarding requires you to specify a host which connections on specific ports will be forwarded to. For instance, let's say we're using ports TCP 443 for Apache, TCP 54321 for SSH and UDP port 1194 for OpenVPN on the Pi; all services which you want to make accessible. On your broadband router, you would need to add an entry for each of these ports and map it to the IP address of your Raspberry Pi.
With this configuration, only those ports which are forwarded will be allowed through, so the iptables configuration isn't quite so important, but it's still a good idea to implement it as described.