code.splrk.net

Thoughts and musings for Software Developers

CentOS Stream 8 HTTP server first steps

June 12, 2021

I recently provisionsed a CentOS stream 8 box on Linode. While this was quick and easy, there are a few things that should be done with the box before enabling public service. I’ll walk you through these few steps and of course give you the reason why each step is important.

1. Disable Root Logins

When initially setting up a server on many cloud providers, you’ll be asked to supply a password. You’ll use that password to login as root onto your server. One of the first things you’ll want to do is disable this root super user.

Why?

Root logins are an easy starting point for bad actors to get into your box. If they are successful in cracking your root password, then they can do whatever they wish on your machine. Especially if you have a weak root password your box will soon become the property of whoever can brute-force the password first. Brute-forcing is easier than it sounds.

Secondly, if you ever use the machine for collaboration in a team, it becomes a temptation to share the root login. More users using the same password means more risk that the password will be intercepted. On top of that, with multiple people using the same account, logging and introspection become more difficult. It becomes impossible to pin down who made changes effectively making unauthorized access impossible to detect. Remove the temptation of the root account and make sure you create separate accounts for each user.

How?

The first step is to create at least one admin user since you need a way to login. To enable this user to use the sudo command they need to be a member of the wheel group.

$> useradd --groups wheel -m username

The above creates a user named username. Below are explanations of the options:

  • --groups wheel addes the user to the wheel group enabling use of the sudo command
  • -m will create a home directory for the user in /home/username.

Next create a password for this user. By default the password is disabled, so we need to run passwd as this user. Make sure you use a strong password as you don’t want this to be compromised. If a bad actor is able to steal or crack this password then they will be able to own your box.

$> sudo passwd username

The above will prompt your for a password.

Next verify your login. This step is very important. If you don’t, then you may be locked out of your box. Log out of the root console and login via a terminal or ssh.

Once you make sure you know your password and username, log back in as the user you created (instead of root) to complete the rest of the commands.

Now we need to disable root login.

To disable root logins via ssh we need to change the file /etc/ssh/sshd_config. Open this file with your favorite editor, but remember to use sudo since you will need root permisions to edit. (You should be promted for your password. If you are not, then something went wrong with setting up your user.) Once the file is open change the line that reads PermitRootLogin yes to PermitRootLogin no. Save the file and restart ssh (sudo systemctl restart sshd). Alternatively, run the following commands:

$> sudo sed -i -E "s/^\s*PermitRootLogin yes(\s*#.*)?/PermitRootLogin no\1/g" /etc/ssh/sshd_config
$> sudo systemctl restart sshd

To explain the sed options:

  • -i instructs sed to change the file in-place
  • -E uses the extended regular expression syntax
  • s/ is the search and replace command for sed
  • The regular expresssion breakdown:

    • ^ Match the beginning of a line
    • \s* Match zero or more white space characters
    • PermitRootLogin yes Match the option we want to change
    • (\s*#.*)? Optionally match any trailing comments and capture them in group 1
    • $ Match the end of the line
  • /PermitRootLoing no\1/ Replace the line with the new option and append any trailing comments captured in group 1
  • /g Do a global search
  • /etc/ssh/sshd_config The file to search and replace in

Verify this worked by trying to login as root via ssh. You will be prompted for a password, but no matter what you put in it should deny you.

Next we want to lock the root account:

$> sudo usermod -L root

And finally we want to change the default shell for root to the no login shell. Edit the /etc/passwd file (with sudo) and change the line root:root:x:0:0:root:/root:/bin/bash to root:root:x:0:0:root:/root:/sbin/nologin. Alternatively the following sed command will also work:

$> sed -i -E "s/^root:([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)$/root:\1:\2:\3:\4:\5:\/sbin\/nologin" /etc/passwd

Here is a brief explanation of the regular expression:

  • ^root Match the line starting with root
  • : Match the field separator (colon)
  • ([^:]*) Match any character that is not a separator and capture it in a group. Notice this is repeated six times.
  • $ Match the end of the line

And the explanation of the replacement string

  • root:\1:\2:\3:\4:\5: Keep the first five fields as is by using each of the captured groups
  • \/sbin\/nologin use /sbin/nologin as the last field (the default shell for the user) using \ to escape the slashes

2. Disable ssh password authentication

Why?

Passwords are just a first step to security. For some services, its okay to stop at a password. However, as you are probably aware because of the multitude of enforced 2-factor authentication from many online services, a password is not considered secure enough for senstive environments. This is because passwords can be guessed, brute-forced and coerced from users. Using separate public key authentication makes sure that an attacker must compromise a user’s machine or network to login via ssh. Secondly, the attacker must also gain the password in order to use the sudo command. This forces bad actors to find two separate pieces of information before they can completely control your machine.

How?

This is also an sshd configuration option, but before doing this you need to make sure you don’t lock yourself out. On your local machine create a new ssh key with the following command. Note: I reccommend setting a password for your key file, just in case it is stolen. Make sure you use a strong password here. If the password is weak and the key is stolen then you’ve lost most of the advantage of having a password since the attacker will likey be able to brute-force the password. Make sure this is different from the login password.

$> ssh-keygen -t ed25519 -f ~/.ssh/unique_key_name

Then you need to append the public key unique_key_name.pub to the ~/.ssh/authorized_keys file on your server. You can do a manual copy and paste or you can run the following command:

$> cat ~/.ssh/unique_key_name.pub | \
   ssh username@yourhostname.or.ip -o "IdentitiesOnly=yes" \
   "tmp=$(echo ) && mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && cat $tmp >> ~/.ssh/authorized_keys && cat ~/.ssh/authorized_keys

Here is an explanation of the previous command

  • cat ~/.ssh/unique_key_name.pub | take your public key (Make sure this is the PUBLIC KEY with with the .pub extension) and pipe it as stdin into your ssh command
  • ssh username@yourhostname.or.ip -o "IdentitiesOnly=yes" Log in to your server. The IdentitiesOnly=yes option ensures that your ssh client does not try all of your local keys. If you don’t do this, then you can run into a “Too many authentication failures” message if you have a fair number of local ssh keys.
  • "tmp=$(echo ) Store stdin in a variable
  • mkdir -p ~/.ssh Ensure the .ssh folder exists on the remote machine in your new users home folder
  • chmod 700 ~/.ssh Make sure you as the user are the only one allowed to write to this file. SSH will ignore this files if it is too permisive. i.e if Anyone other than the user is allowed to modify it, then ssh can’t trust that the keys are actually from the current user.
  • touch ~/.ssh/authorized_keys Ensure the authorized_keys file exits
  • chmod 600 ~/.ssh/authorized_keys Make sure you as the user are the only one allowed to write to this file. SSH will ignore this files if it is too permisive. i.e if Anyone other than the user is allowed to modify it, then ssh can’t trust that the keys are actually from the current user.
  • cat $tmp >> ~/.ssh/authorized_keys Append your public key to the authorized_keys file
  • cat ~/.ssh/authorized_keys Echo the result of the command for your verification

To double check that this works, try loggin in with your new key

$> ssh username@yourhostname.or.ip -i ~/.ssh/unique_key_name

If it worked, you should be prompted for the key file password and not your username password. That should log you in. Furthermore you can add the key to your ssh agent keyring and enter the password only once per local system login. Also, to avoid having to use the -i flag everytime you can append the following config to your local ~/.ssh/config file. Note: If ~/.ssh/config does not exist, make sure you use the chmod 600 command to tighten permissions.

# Description of your server here
Host yourhostname.or.ip
  HostName yourhostname.or.ip
  User username
  AddKeysToAgent yes
  IdentityFile ~/.ssh/unique_key_name

Here is an explanation of the above config:

  • Host yourhostname.or.ip The hostname you will reference on the command line. (*Note: this can be different from the Hostname line below if you need to use two separate keys for the same host, but this is not a common situation`*)
  • HostName yourhostname.or.ip Tha actual ip or hostname to use. Most often this will be the same as Host
  • User username The user to use when connecting. This will allow you to omit the username@ portion of the command when you login.
  • AddKeysToAgent yes If the ssh agent is running, then once the passphrase in entered for the key file, add it to the agent removing the need to prompt for the passphrase for the durtaion of the local session.
  • IdentityFile ~/.ssh/unique_key_name The private key file to use when connection to this server

3. Install fail2ban

fail2ban is a utility that will ban ip addresses from connecting to your server when they are exhibiting malicioua behavior. For instance, if 3 failed ssh login attempts are tried then fail2ban adds entries to the firewall to ban that IP for 10 minutes.

Why?

Hackers will often scan the internet for vulnerable machines they can control. One easy method is to brute forcing a machine’s root password with a script. This does not prevent a brute force, but it slows down that hacker enough that it won’t be worth their time to keep trying.

How?

For CentOS you need to enalbe the EPEL Repository

$> sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm

Now you can install fail2ban

$> sudo dnf install -y fail2ban

Now enable the ssh “jail”. fail2ban comes with many templates for services, but none enabled. The following shows how to enable a single jail for sshd, however as you start enabling or installing other services you may want to configure more jails.

We must create a local customization file. This file will override settings in /etc/fail2ban/jail.conf. Often distributions will rewrite the fail2ban jail config file when updating the package, so creating a local override will preserve customizations between updates. To enable the sshd jail just add the enabled flag for the sshd section:

$> sudo bash -c "cat > /etc/fail2ban/jail.local << EOF
[sshd]
enabled = true
EOF"

Next the service needs to be started and enabled to start on boot.

$> sudo systemctl enable fail2ban.service
$> sudo systemclt start fail2ban.service

To verify the ssh jail is active check the status:

$> sudo fail2ban-client status

This should show sshd in the Jail list. You can explore more custom configuratons in the fail2ban manual.

4. Update the firewall

Why?

CentOS Stream 8 comes with a firewall already configured, with very few ports open. However, as an admin you need to know exactly which ports are open, who opened them and what service is running on them. This last one will vary for each server, but the principle is the same. Make sure you lock down the firewall from the start, and then only open ports as needed. This reduces the surface area for malicious actors to find vulnerabilities and reduces blind spots.

How?

CentOS comes with firewalld installed and running. This makes it really easy to open and remove ports. To see what ports are open, run

$> sudo firewall-cmd --list-all

You might see something like this in the output

    services: dhcpv6-client ssh cockpit

This lists ports open by their human readable name. ssh makes sense as that is how to administrate the server. The other two are optional. Cockpit is a web app for managing and configuring your server. Whilst this might be a convenient way to manage the server, I don’t use it. The first reason is that I’m simply more comfortable on the command line as opposed to a web GUI. Second is that it’s another potential vector for attack. The app itself could have vulnerabilities, or it could be configured incorrectly allowing malicious actors to login in and take control of the server. The same goes with dhcpv6-client. I don’t plan on making the jump to IPv6 anytime soon, so why leave a port open for it? If there comes a time where I find a good use for either of these things, or just want to learn them, the ports can be opened explicitly. But when starting off its best to keep everything unused closed.

$> sudo firewall-cmd --remove-service=cockpit --remove-service=dhcpv6-client
$> sudo firewall-cmd --runtime-to-permanent

The first command removes ports for the unwanted services. The second make the rules permanent for restarts.

Now, unless you are creating a jump box, you probably do want to open some ports. Lets take and example of opening up ports for a webserver. Specifially we want to open 443 and 80.

$> sudo firewall-cmd --add-service=http --add-service=https
$> sudo firewall-cmd --runtime-to-permanent

The first command add ports for the wanted services. The second makes the rules permanent for restarts.

Ready to Go

Your server certainly isn’t bullet proof, but you’ve locked down a few of the most common vectors and done it from the get-go. Taking a little time for server configutionation is extremely important for linux hygene. Most cloud providers are giving you very generic servers that are intended for quick and easy setup. There’s nothing wrong with this, but you as the administrator must know what you’re getting into. Make sure your server is yours, and don’t take for granted that the defaults are the best or the most secure for your situation.


I am Ryan Seal and have been developing software professionaly for the last 12 years. Currently I live and work in South Africa wirting software for a non-profit organization.