Create a non-root user with an SSH key and sudo
Using the root
user all the time is risky, and most tasks on a server do not
require full privileges. In this lesson we'll create an unprivileged user with
sudo
access.
Why not root directly
You can certainly use root
for all the things when managing a WordPress
server, however there are quite a few risks associated with doing so, such as
typos, audits, blast radius when things go wrong, and even compliance.
Sticking to the least privilege principle is a great security practice,
especially if you'll be granting other people access to your server in the
future. Using sudo
allows you to set up granular control and keep a log of
privileged commands and attempts.
Escape hatch
When working on things that could potentially lock you out, it's recommended to keep a separate root shell open while testing SSH settings in a different shell. That way if you make a mistake you still have a chance to correct it instead of being locked out completely.
Creating a new user
In this course we will stick to just one user with full sudo privileges. I'll
name mine karl
but you're welcome to use a different name. While logged in
as root
, use useradd
to create a new user, and usermod
to add them to
the sudo
group:
useradd -m -s /bin/bash karl
usermod -aG sudo karl
The -m
flag automatically creates a home directory for karl at /home/karl
.
We do need a home directory for karl to store an authorized SSH key, and some
other configuration files in the future.
The -s /bin/bash
flag sets the user's default shell to /bin/bash
.
Authorized keys
Using a password to log in is insecure these days, so we'll be using an SSH key. You can use an existing key or create a new one for this server specifically. The GitHub Docs site has everything you need to know about SSH keys, passphrases and agents.
Let's add a new authorized_keys
file to our configuration repository under
the misc
directory. Place the public portion of your SSH key into this file,
commit and push it. You can do this directly on the server as the root user,
or push it from a local clone, and pull to the server from GitHub:
cd /config
echo "ssh-ed25519 AAAAC3NzaC..." > misc/authorized_keys
git add misc/authorized_keys
git commit -m "Adding authorized_keys"
git push
Now while still as root, let's create a symbolic link to that file from
Karl's authorized_keys
:
mkdir -p /home/karl/.ssh
chown karl:karl /home/karl/.ssh
ln -s /config/misc/authorized_keys /home/karl/.ssh/authorized_keys
Here I created a new .ssh
directory and made sure karl
is the owner
and group. Then I created the symbolic link using ln
. This allows me to
log in as karl
using that SSH key. However there are a couple more things
we need to configure before using this new user.
Passwordless sudo
By default sudo
will ask for the user's password when invoked. Since creating
the karl
user we haven't assigned them a password, so it will be impossible
to invoke sudo
. We can solve this by setting a password for Karl:
passwd karl
Or we can configure sudo
to not ask for a password at all for this user.
While a tiny bit less secure, I prefer this option since I already have
a passphrase on my SSH key which is used to access the server.
Let's create a new configuration file in /config/misc/sudoers
with the
following contents:
karl ALL=(ALL) NOPASSWD: ALL
Don't forget to use a different username if you're not Karl. Commit and push
this change, and create a symbolic link for this sudoers
file.
ln -s /config/misc/sudoers /etc/sudoers.d/sudoers
Now, let's log out of the server as root
and log back in as karl
using the
SSH key and make sure sudo
is working:
ssh [email protected]
sudo id
The sudo id
command runs id
as the root
user, so your output should
begin with uid=0(root)
. You can also use the sudo --list
command to check
the sudo configuration for the current user. Here's what I see:
Matching Defaults entries for karl on origin:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User karl may run the following commands on origin:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALL
Now karl
has pretty much the same privileges as root
when using sudo
, so
there is no longer a reason to use root directly. Let's make a few tweaks to
make sure the root user stays secure.
Note that going forward, when you are logged in as a non-root user, you should
always use sudo
when interacting with our configs repository (to retain correct
ownership). If you're relying on SSH agent forwarding to pull/push changes,
you'll need to pass the current environment with sudo -E
:
cd /config
sudo -E git pull
Same goes for creating new files or editing existing files. If you made a mistake, you can change ownership of an existing file using chown.
Disabling root login
Let's create a new /config/misc/sshd.conf
file in our configuration repo
with the following contents:
PasswordAuthentication no
PermitRootLogin no
This disables password authentication (for SSH) and disallows the root user to log in via SSH. Commit and push this file, and add the following symlink:
ln -s /config/misc/sshd.conf /etc/ssh/sshd_config.d/sshd.conf
Next, restart the SSH service:
sudo systemctl restart ssh
Exit the system and try logging back in as root:
ssh [email protected]
You should see a similar error message:
[email protected]: Permission denied (publickey).
Finally, if your root user had a password set, we can make sure it's locked
using the passwd
command. Run this as karl
(or equivalent) user on your
server:
sudo passwd -l root
This locks the password, but doesn't disable the account itself. You're still
able to use sudo
and even sudo su
to get a root shell.
Symlinks
You may have noticed a trend: all our configuration files live in the /config
repository and then symlinked to the appropriate place. We've done this
with authorized_keys
, sshd.conf
and sudoers
, and we'll do a lot more
in the coming lessons.
This technique allows us to keep everything in a single repository, rather than multiple smaller repositories scattered around the system. It does come with a slight drawback though and that's symlink management.
It's very easy to remove a symlink, intentionally or accidentally, and
restoring them might be tricky. You can keep a list in the README.md
file
for reference, though I usually prefer to have a helper script to restore
all the symlinks for me.
This is especially useful for disaster recovery and migrations. Here's an example with the three files we covered in this lesson:
#!/bin/bash
mkdir -p /home/karl/.ssh
chown karl:karl /home/karl/.ssh
ln -sfn /config/misc/authorized_keys /home/karl/.ssh/authorized_keys
ln -sfn /config/misc/sshd.conf /etc/ssh/sshd_config.d/sshd.conf
ln -sfn /config/misc/sudoers /etc/sudoers.d/sudoers
Write this to bin/symlinks.sh
and make it executable with:
chmod +x bin/symlinks.sh
Don't forget to commit and push the helper script to our configs repository. Now you can easily restore all symlinks using the helper script with:
sudo /config/bin/symlinks.sh
You can get a lot more complex than this, with dynamic file lists, permission checks, file and directory modes and more. Feel free to build that complexity for this and other helper scripts, however, I will try to keep things as simple as possible for learning purposes.