about blog projects contact

The Cogwheel Blog

Fork me on GitHub
## Linux VPS Server Configuration ### Background The propagation of public cloud and VPS (Virtual Private Server) providers has made the prospect of provisioning a server a viable reality for many. I have benefitted from this trend myself as I have forayed into signing onto AWS EC2 instances as well as one or two smaller VPS providers like Linode. [VPS Benchmarks](https://www.vpsbenchmarks.com/) is a handy reference for those looking to compare/contrast the performance and features of many of these providers. Setting up a server from scratch is not without its pitfalls and many of the providers seeking to compete in this space today have put together some very useful tools to help with this. Providers vary greatly in their hand-holding from the over-the-top approach of AWS (which goes above and beyond others by giving specific instructions on ssh access to instances, walking one through the public-private keygen process) to the more bare-bones approach of smaller shops. It was recently when I was having to provision one of these servers for myself that it occurred to me it would be beneficial to write down a baseline protocol to follow to provision a *nix server from scratch. As such, I formulated the following rules for a Linux/Ubuntu instance, which is a popular choice among subscribers. (Note: I have a Linode-specific provisioning article which I posted to my [GitHub](https://github.com/builderLabs/linux_server_config/blob/master/LinuxSrvCfg.md) page for those who might be interested. Since it's posting, I have seen a proliferation in similar such write-ups which is likely an indication that this space and use-case scenario is only growing). ### Summary Steps: Conceptually-speaking, the steps to acquiring and configuring a VPS or cloud instance for one's own use would involve the three (fairly obvious) steps of: ``` 1). Provisioning the Server --- this means signing up with a vendor, choosing an OS and disk image, booting it up, and so forth 2). Securing the Server --- following basic steps to 'make it your own' 3). Updating Software and Monitoring for future use ``` Among these, the 'Securing the Server' step is of special importance and that is the focus of this blog entry. Since I found myself having to repeat the same basic steps of securing a given instance following the 'best practices' of today and from what I've learned from experience working at smaller shops, I decided to write these down for reference. These are the basic steps I will be detailing in this entry: ``` 1). REMOVE ATTACK VECTOR: root login 2). REMOVE ATTACK VECTOR: password-based authentication 3). REMOVE ATTACK VECTOR: default ssh port log-in 4). REMOVE ATTACK VECTOR: unauthorized traffic (setup a firewall) ``` (CAVEAT EMPTOR: Every use-case scenario is bound to vary and these steps might not be applicable to every situation but I hope collecting these here might prove to be of use). ### Securing a Server: baseline steps for a Linux Ubuntu instance: ``` 1). REMOVE ATTACK VECTOR: root login ``` It's highly likely that the provisioning phase of acquiring a server involved you (or the owner) setting up a root account with superuser privileges (as is par for the course). Obvious though it might seem, it's good practice to create a different user account with root privileges known only to you and/or those on a need-to-know basis and to disassociate the superuser role from the account called 'root' which everybody already knows. The first time you do this, it's likely you'll be having to log in to your freshly setup root account for which you have (hopefully) generated a strong password for password-based authentication (also par for the course for many providers from the get-go). Assuming you have logged into your instance as root, you can now use the following (in Ubuntu-speak) to do this: ``` a). sudo adduser [user] ``` Follow the command prompts and answer the questions which ensue. Next, assign the new user profile root privileges. Create a sudoer file as follows: ``` /etc/sudoers.d/[user] ``` (Note that the file can be called anything, but common practice is to give it the profile name.) put this line inside the file: ``` [user] ALL=(ALL:ALL) ALL ``` This means that for the user we are granting permission to run all commands as all users and all groups from all hosts. You can verify that the user has been added by: ``` sudo cat /etc/passwd | grep [user] ``` Check to make sure you get output from this command, otherwise the user has not been properly added. Having added a new superuser with root privileges, it would be wise at this point to disable logins for the actual user 'root'. Switch from our root profile to the new superuser profile: ``` su - [user] ``` Verify your prompt changes to: ``` [user]@localhost:~$ ``` Edit the server-side sshd_config file: ``` /etc/ssh/sshd_config ``` Change the line 'PermitRootLogin' to read: ``` PermitRootLogin no ``` save and quit. Now restart the ssh service to read the updated configuration changes we made: ``` sudo service ssh restart ``` Confirm you have disabled root log-in by exiting your profile and from your log-on session: ``` exit exit ``` Verify that the following command from your local host: ``` ssh root@12.345.67.890 [ root @ your VPS public IP address ] ``` fails. Conversely, verify that you are able to log on to your server doing: ``` ssh [user]@12.345.67.890 ``` ``` 2). REMOVE ATTACK VECTOR: password-based authentication ``` As the next basic step, remove all the uncertainties/vulnerabilities associated with password-based authentication and replace this entirely with Secure Shell (ssh) login, using public key encryption, instead. On your local machine, create an ssh directory (if not there): ``` mkdir ~/.ssh ``` Create a file called 'config' in this directory to which we will add our remote server's connectivity details. Put the following lines in this file: ``` Host [hostalias] Hostname=12.345.67.890 ``` The value next to 'Host' is the name you want to give your remote server. The value for 'Hostname' should be the public IP address you were given by the vendor for your machine. Note: this step is meant to simplify remote logins to our server. Whereas up to now we used the public IP address of our server to login, we can now use our more descriptive hostname to connect just by typing: ``` ssh [user]@[hostalias] ``` To replace password-based authentication to the server, create a key pair. You can read more on this protocol [here](https://en.wikipedia.org/wiki/Ssh-keygen). Run the following command on your local machine (assuming you haven't done so already before. If you have, you have the option of using your existing key pair combination for this step): ``` ssh-keygen ``` By default, the encryption used is 'RSA'. This can be changed to another format with the '-t' option. Refer to [this](https://en.wikipedia.org/wiki/Ssh-keygen#Key_formats_supported) for alternative encryption protocols. (Yet another option is to use the -f argument to give a specific name to the keys you generate here). Assuming the standard RSA encryption was used, this will generate two keys in your ~/.ssh directory: ``` id_rsa id_rsa.pub ``` Copy the contents of your PUBLIC KEY id_rsa.pub (the private key id_rsa should remain private and on the local client). Back on the REMOTE SERVER we provisioned, create the .ssh directory under the superuser's home directory: ``` mkdir ~/.ssh ``` Create a file called: ``` authorized_keys ``` and paste your id_rsa.pub content into it (alternatively, use rsync/scp to copy the content of your public key over to this file on the remote server). We'll need to grant permissions on this file/directory accordingly, thus: ``` sudo chmod 700 ~/.ssh ``` ( grant user read/write/execute permissions ) ``` sudo chmod 644 ~/.ssh/authorized_keys ``` ( grant user read/write, group and others read permissions ) Define localhost in hosts file with server IP address. This file establishes static associations between IP addresses and hostnames and is read first before any DNS lookups: edit: ``` /etc/hosts ``` to ensure the first entry that it looks like this: ``` 127.0.0.1.1. localhost.localdomain localhost ``` (Note: different distributions have different requirements. As a result, should host resolution errors crop up downstream - when attempting to service a request for a hosted web application, for instance - edit this file to include the explicit IP address of your server, as follows: 127.0.0.1.1 localhost ip-12.345.67.890) Having done all this, test the key pair based login. First quit the server: ``` exit ``` Back on the client machine, let us ssh back to the remote server: ``` ssh [user]@[ipAddress] -i [pathToPrivateKey] ``` thus: ``` ssh [user]@12.345.67.890 -i ~/.ssh/id_rsa ``` This should work. If not, review the steps above to ensure they were executed correctly. Note that if you took the extra step to edit your ~/.ssh/config file on your local machine, our login command to the remote server reduces to simply: ``` ssh [user]@hostname ``` thus: ``` ssh [user]@[hostalias] ``` This way, you don't have to remember the IP address every time. Once you have confirmed this works, disable password-based authentication on the remote server. Back on the remote server, edit the file: ``` /etc/ssh/sshd_config ``` change the line: ``` PasswordAuthentication yes ``` to read: ``` PasswordAuthentication no ``` (Note: make sure you're on the server editing /etc/ssh/sshdconfig which is the ssh daemon which runs server-side. If all you see is /etc/ssh/sshconfig, you're on your local machine as this is the client side OpenSSH config file). For these config changes to take effect, restart the service: ``` sudo service ssh restart ``` Alternatively, you can wait until you've completed Step 3 (below) to restart the ssh service on the remote server before exiting and reconnecting. ``` 3). REMOVE ATTACK VECTOR: default ssh port log-in ``` While on the REMOTE server, edit the configurations file above such that the ssh port is set to something other than the default 22. Modify ``` Port 22 ``` to read: ``` Port [port] [some other available port of your choosing ] ``` Since this was yet another configuration change, we need to restart the config read process for the ssh service: ``` sudo service ssh restart ``` With password-based authentication disabled and our ssh port changed from 22 to another port of our choosing, test connections to the remote server by quitting and logging back in again. Exit the server: ``` exit ``` Now attempt to reconnect from our local machine using the following command: ``` ssh [user]@12.345.67.890 - ~/.ssh/id_rsa -p [port] ``` Again, to make this process more convenient, you can edit the ssh config file on your local machine such that the section with our new server (with hostalias) reads as follows: ``` Host [hostalias] Hostname=12.345.67.890 Port=[port] ``` (Note all we're doing here is appending our non-default ssh port number to the information we already added in a previous step). If you do have this information in your ~/.ssh/config file, logging on to your remote server should now be as easy as: ``` ssh [user]@[hostalias] ``` ``` 4). REMOVE ATTACK VECTOR: unauthorized traffic ``` With our server now minimally secured, it is out there, connected to and accessible via the internet and as such it is prudent to be selective with any incoming traffic from potentially malicious entities. Basically, this means setting up a **firewall**. Here, we take steps to configure firewall settings so as to permission and delegate specific types of traffic/requests to their respective ports using Ubuntu's Uncomplicated Firewall UFW which is a high-level tool that has made the once tedious tasks of micro-managing iptables for firewall configurations a thing of the past. While we're likely to want to allow ourselves to be able to reach out to the world from our server, incoming traffic to our server is another matter altogether and for which our guiding philosophy will be: **Start with no traffic and add on piecemeal** The UFW does not come enabled by default. This means that with a fresh Ubuntu Linux server set up, all traffic is enabled from the get go. (Note: be sure to process each of the following commands correctly. If you make a mistake, you will have a chance to fix it before we enable the firewall in the last step. Before it is enabled, however, you need to make sure that you haven't setup the firewall to do unintended things - key among which would be locking yourself out of the server by disabling your chosen ssh port). 1). Blank Slate: reign in all traffic allow ourselves all outgoing traffic: ``` sudo ufw default allow outgoing ``` deny all incoming traffic: ``` sudo ufw default deny incoming ``` 2). First and foremost, allow incoming ssh requests: ``` sudo ufw allow [port] [ make sure your configured ssh port is here ] ``` (Note: if you've only allowed yourself ssh-based access, having followed the steps above, **MAKE SURE YOU DO THIS** otherwise you'll lock yourself out of your server once you quit it). 3). Allow http requests to our server (especially if you're going to host a web app on it): ``` sudo ufw allow 80 ``` 4). Finally, add any other incoming traffic which might be of value of importance to you. A reference for many of these can be found [here](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers). 5). With these commands processed, take a moment to look back in your command history to ensure you haven't done anything unintended. In particular, make sure you have *allowed incoming ssh requests* via the port you configured in /etc/ssh/sshd_config (originally 22). Each rule is updated as you enter the command, hence the return: ``` Rules updated ``` If you have made a mistake and need to change a port number, simply enter the command with the correct port number. Otherwise, enable the firewall as a last step, if all settings appear to be in order: ``` sudo ufw enable ``` At which point you're liable to see: ``` Command may disrupt existing ssh connections. Proceed with operation (y|n)? ``` Answer 'y' to confirm. You should then see a confirmation message as follows: ``` Firewall is active and enabled on system startup ``` With these basic four steps, we have addressed the most rudimentary security requirements for securing a freshly provisioned Linux VPS with Ubuntu image. As stated before, requirements vary according to different use-case scenarios and your particular needs will very likely require either more precautionary measures than those listed here and/or different ones, depending on your needs. As a baseline-guide, however, these steps might prove useful to anyone looking to provision a server for themselves from scratch.

-A. Ozan Akcin