Managing Users on Linux Server Securely
Guide to securing your Linux server by managing users, controlling access, and configuring sudo safely.
Securing your Linux server involves skillfully managing users.
It's crucial to ensure that users can only access their own data, know how to protect login credentials, and use secure methods for server access.
Additionally, understanding the risks associated with using the root user and learning how to use sudo to grant either full root privileges or specific privileges is essential.
Familiarity with working with the sudoers
file is also important.
This guide will cover these aspects in detail, along with other key aspects related to securely managing users on Linux servers.
Preparation
To make the most of this guide, ensure you have a properly set up Ubuntu server.
If you don’t have one, consider getting a free VPS server to follow along.
Potential Risks
When accessing your server for the first time, you may use the root user, but this is not recommended due to the significant risks involved.
The root user possesses unrestricted control over the entire server, allowing you to execute any command, even those that could potentially break the system.
Firstly, root is the default all-powerful user on Unix and Linux systems, making it a prime target for hackers attempting to brute force your server. Not disabling root access leaves your server vulnerable to attacks.
Additionally, there's a risk of unintentionally damaging the server.
To enhance security, it's essential to create a new user with root privileges, while completely disabling access for the root user.
This non-root user should prepend each command with the sudo
prefix and enter their password, providing a safer approach.
Furthermore, using an unfamiliar username for this user helps mitigate the risk of brute force attacks.
For now, I'm only discussing the scenario where you are the only administrator.
Consider the scenario of multiple admins in a company having access to the server with the root password. This is not ideal as it poses security risks.
If an admin stores the root password in an insecure place or leaves the company, changing the password becomes necessary, requiring distribution of the new password to all admins.
Moreover, in a corporate environment, it's often preferable to assign specific commands to specific users rather than granting full access.
Understanding the proper way to manage users on your Linux server is crucial, especially in scenarios involving multiple administrators.
In this guide, I will teach you the best practices to ensure the secure management of users on your Linux server.
Adding Users
The first step is understanding how to add users on a Linux server, a task that is quite straightforward.
Two commands are commonly used for adding new users.
The adduser
Command
The first command is adduser
, typically found on Debian-based distributions such as Ubuntu.
It is a user-friendly command that interactively prompts for user information, streamlining the user creation process.
To add a new user, execute the following command:
adduser username
Apart from creating a new user, the adduser
command performs the following tasks:
- Creates a new group for the user.
- Creates the home directory for the new user under the
/home
directory. - Asks for a password.
- Prompts for additional information such as Full Name, Room number, Work phone, Home phone, and other details. Simply press ENTER to skip through these optional details.
The useradd
Command
The second command is useradd
, commonly used on Red Hat-based distributions like CentOS but also available on Ubuntu.
On Ubuntu, it requires specifying user details and options directly in the command line, offering a more manual approach to user creation.
While it creates a new user like the adduser
command, it has a few differences:
- It does not create a home directory for the new user by default.
- It does not create a password for the user.
- It does not ask for any additional information.
Something important to know is that when using the adduser
command, the default shell for the user is the Bash shell. In contrast, when using the useradd
command without any options, the default shell is the classic SH shell.
If you want the useradd
command to perform the same tasks as adduser
, execute the following command:
useradd -m -d /home/<username> -c "Full Name" -s /bin/bash <username>
The -m
option is used to create a home directory for the user. The -d /home/username
option allows you to specify the path of the home directory.
Using the -c "Full Name"
option allows you to set the full name associated with the user account.
Lastly, the -s /bin/bash
option specifies the default shell for the new user as Bash.
Combining these options, the command creates a new user with a home directory, assigns a full name, and sets the default shell to Bash.
After creating the user, remember to use the passwd
command to set a password for the new user:
passwd username
Enter and confirm the password.
Which One To Use
Now, which command to use depends on your needs and use-case, but in most cases, the adduser
command is preferable as it is easy to use and allows you to add users quickly.
However, this doesn't mean the useradd
command is useless. If you temporarily want to create a user without providing much detail, you can use it. Generally, it's used in bash scripts for quick user creation without interactive prompts.
Locking Down Home Directories
Each Linux distribution comes with default security settings for users' home directories.
If you're working in a mixed environment with different distros, it's important to be aware of these variations.
Let's add a user named John using the useradd
command on an Ubuntu 20.04 server:
useradd -m -d /home/john -s /bin/bash john
Next, let's list the contents of the /home
directory:
ls -l /home
Output:
total 4
drwxr-xr-x 2 john john 4096 Jan 19 08:50 john
Notice that John's home directory has open permissions, allowing everyone to execute and read. This is not ideal, as it exposes John's home directory to all users on the server.
Although users can manually adjust their home directory permissions, it's preferable to set the correct permissions by default when adding any new user.
To achieve this, modify the default permissions for home directories by opening the /etc/login.defs
file.
Look for the line starting with UMASK
and change the value to 077
like this:
UMASK 077
Now, new users' home directories will have secure default permissions without requiring manual adjustments.
I will add a user named Ivan now for verification:
useradd -m -d /home/ivan -s /bin/bash ivan
Next, let's once again list the contents of the /home
directory:
ls -l /home
Output:
total 4
drwx------ 2 ivan ivan 4096 Jan 19 09:02 ivan
drwxr-xr-x 2 john john 4096 Jan 19 08:50 john
Do you notice the change? Access to Ivan's home directory and the viewing of their files are now restricted.
But, this change won't apply to the adduser
command, and using it will still utilize the old permission setting.
That's why we need to open the /etc/adduser.conf
file and change the value of DIR_MODE
to 0700
like this:
DIR_MODE=0700
With this, access to a new user's home directory and the viewing of their files are now restricted.
Now, let's talk about Ubuntu 22.04 and 24.04.
The default permissions for both the useradd
and adduser
commands have been changed to a more secure setting, which is excellent.
If you open the /etc/login.defs
file, you will notice a new line under UMASK
called HOME_MODE
with a value of 0750
by default.
If you open the /etc/adduser.conf
file, you will notice that the value of DIR_MODE
has been changed to 0750
.
These changes mean access permissions for a user's own personal group, which is acceptable.
This effectively ensures that only the respective owners of the various home directories can access them.
Password Management
Now that you've learned how to add users, let's talk about managing their passwords.
It's crucial to keep passwords secure in one central place, rather than each user storing them individually. This is where a password manager comes in.
Nowadays, using a secure password manager is essential for storing and generating strong passwords.
With a tool like Bitwarden (which I personally recommend and use), you can store all user passwords in one accessible place.
Bitwarden offers a feature called Bitwarden Organizations which adds a layer of collaboration, allowing secure sharing of information among users, such as passwords.
Now that you know how to manage passwords, let's delve into enforcing the use of strong, lengthy passwords and setting up periodic password changes.
Strong Password Criteria
We want to enforce users to use strong passwords that meet the criteria we specify.
For that, we will use the pwquality
module for the Pluggable Authentication Module (PAM).
Let's begin by installing the module:
apt install libpam-pwquality
Now, to configure the module, there is only one file to edit, which is the /etc/security/pwquality.conf
file.
Open the file in your preferred editor and take a look. It is well-documented.
The two important variables to adjust are minlen
and minclass
.
The minlen
variable determines the minimum length of the password, while the minclass
variable specifies the minimum number of character classes that the user should use when creating the password.
I always set the minlen
variable to 32
, and the minclass
variable to 4
, ensuring users include symbols in their passwords.
These values pose no problem since users will use the password manager both for creating complex passwords and securely storing them.
I'll leave the rest up to you to meet your own criteria, as mentioned before, the file is well-documented. Don't forget to uncomment the variables for them to take effect.
Once you're finished, save the file, and that's it.
To test the new criteria, add a new user and attempt to change their password.
Password Expiration
The first time you add a user, you assign their password and store it in the password manager.
Now, you want to enforce a policy where users must change their passwords every month, requiring a change every 30 days from the last password update.
To do this, we change the value of the PASS_MAX_DAYS
variable inside the /etc/login.defs
file to 30
like this:
PASS_MAX_DAYS 30
Add a new user for verification and use the chage
command to check the expiration data of that user:
chage -l <username>
No one needs root privileges to see their own expiration data, but they still need to specify their username. They cannot view any expiration data of other users or make changes to them.
Let me add a user named Elie and check their expiration data after the change:
Last password change : Jan 25, 2024
Password expires : Feb 24, 2024
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 30
Number of days of warning before password expires : 7
As you can see, the password of the user Elie will expire on Feb 24, 2024, which is 30 days after its creation on Jan 25, 2024. This implies that Elie needs to change their password every 30 days.
They will receive a warning 7 days before the password expires, indicating that they should change their password.
If we leave the expiration settings as they are, any user could change their password at any time before password expiration, allowing them to do so every day. While changing it every day may be overly secure, it's a practice I don't prefer.
In our scenario, passwords expire every 30 days, and users receive a warning 7 days before expiration, effectively starting on the 23rd day after the password change.
I prefer users to have the ability to change their password only during this 7-day window, spanning from the time they receive the warning until the password reaches its expiration date.
To achieve this, we need to adjust the value of the PASS_MIN_DAYS
variable and set it to 23
like this:
PASS_MIN_DAYS 23
Let me add a user named Issa and check their expiration data after the change:
Last password change : Jan 25, 2024
Password expires : Feb 24, 2024
Password inactive : never
Account expires : never
Minimum number of days between password change : 23
Maximum number of days between password change : 30
Number of days of warning before password expires : 7
The password for the user Issa now expires after 30 days, and the user can only change their password within the 7 days leading up to password expiration.
With this, we have learned how to specify specific criteria for creating passwords and how to enforce users to change their passwords periodically.
Implementing Sudo
When sudo is set up correctly, it significantly boosts the security of our server.
Here are its key advantages:
- Assign full root privileges to specific users, while granting others access to execute only designated commands.
- Users can perform administrative tasks without needing the root password. They can use their own password instead. This avoids the necessity of sharing the root password with everyone.
- Implementing sudo allows us to disable the root user, preventing brute force attacks targeting the root user. Additionally, attackers remain unaware of usernames with root privileges, enhancing overall server security.
- It enables us to monitor user activities closely. We can track and review actions performed with sudo capabilities.
These are the points I love about sudo, but it has more features to offer.
In the following, I'm going to teach you the important things about implementing sudo and how you can do it the right way.
Full Root Privileges for Users
When exploring Linux server security guidelines, it's common advice to create a non-root user, add them to the sudo group for full root privileges, and disable root access entirely.
But have you wondered why the sudo group and where its powers come from? This is precisely what we are about to delve into.
The key to these privileges is the /etc/sudoers
file.
It is a configuration file that specifies which users or groups are granted permission to run specific commands with elevated privileges using the sudo
prefix.
When you attempt to run a command that requires root privileges using the sudo
prefix, the server checks your username against the sudoers
file.
If you have the privilege to run the command, you are good to go, otherwise, you will receive an error.
To access the sudoers
file, we use the visudo
command. It's important not to open the file with a regular text editor. This command checks for errors and warns before saving changes.
The command will open the file with the default editor used by the server, which is often nano. If you want to use another editor, you can change the default editor by using the following command:
update-alternatives --config editor
The command will prompt you to choose between editors. Select your preferred one and press the ENTER key.
Now, run the visudo
command and take a look at the sudoers
file:
# User privilege specification
root ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
The percent sign indicates that we’re working with a group.
Look, the sudo group has the same powerful privileges as the root user.
So, when you add a user to this group, they can run any command needing root privileges.
Let's break down those ALLs:
- The first one means the rule applies to all hosts.
- The second one says the user can run commands as any user.
- The third means commands can be run as any group.
- Lastly, the last ALL means these privileges apply to any command.
Let me add the user Ivan to the sudo group using the following command:
usermod -aG sudo ivan
Now, if I run the apt update
command without the sudo
prefix, it won't work directly. I need to run the command like this: sudo apt update
and then enter my password.
We can also manually add any user to this file with a specific rule and set of privileges.
I will add a rule for the user Ivan under the one for the root user like this:
ivan ALL=(ALL:ALL) ALL
In simpler terms, the new line now allows the user Ivan to execute any command as any user and any group on any host, just like the root user.
Save the file, and if you don't see any warnings, you're good to go.
If you wish to add a user to the sudo group while creating their user account, use the following command:
useradd -G sudo -m -d /home/john -s /bin/bash ivan
The -G
option directly adds the user Ivan to the sudo group.
Customized User Privileges
Now that you've learned how to grant users full root privileges, let's move on to understanding how to assign users specific, limited privileges.
The idea is to minimize the number of people with full root privileges, ensuring that the rest have only the necessary privileges for their job.
Imagine this situation: two folks are on the Software team. We just want to let them run commands related to software. We can either make two separate entries in the sudoers
file for each user, or we can make things simpler using aliases.
Now, in the sudoers file, let's make a new user alias called SOFTWAREADMINS
that includes the two users we want to let run software-related commands:
# User alias specification
User_Alias SOFTWAREADMINS = ivan, john
Next, create a new command alias called SOFTWARECOMMANDS
that includes commands related to software:
# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt, /usr/bin/dpkg
Finally, link the SOFTWARECOMMANDS
alias to the SOFTWAREADMINS
alias like this:
SOFTWAREADMINS ALL=(ALL:ALL) SOFTWARECOMMANDS
Ivan and John, who are part of the SOFTWAREADMINS
alias, can now run software-related commands listed in the SOFTWARECOMMANDS
alias.
If a new person joins the team, just add them to the SOFTWAREADMINS
alias.
Similarly, if you want to let them run a new command, simply add it to the SOFTWARECOMMANDS
alias. Easy!
Caution: Command Specification
It's crucial to understand that when we define a command alone, as we did in our SOFTWARECOMMANDS
alias, users can use it with any subcommands, options, or arguments.
This includes the ability to delete software, which might not be our intention.
But when a command is listed in the command alias with a subcommand, option, or argument, that’s all a user who's assigned to the command alias can run.
If we want users only to install new software and update existing ones, we need to modify how we specify the command like this:
# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install
But will this work when installing new software? No.
If you try running sudo apt install postfix
to install Postfix, it won't work.
Users could run sudo apt install
without specifying what to install, which is pointless.
That's why we need to adjust our alias once again, like this:
# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install *
Using the asterisk (wildcard) means users can specify anything after the command, allowing them to install any software.
Now, users in the SOFTWAREADMINS
alias can update existing packages and install new software.
In another scenario, you might encounter a similar issue to what we faced. This situation arises when managing a team focused solely on services, and you need to provide them with the privilege to execute the systemctl
or service
commands.
Consider this command alias:
Cmnd_Alias SERVICECOMMANDS = /usr/sbin/service, /usr/bin/systemctl
What do you think would happen if we assign users to this command alias? What would they be able to run?
If left as is, users assigned to this command alias would have the capability to shut down or reboot the server, edit services files, or change system targets. This is not the desired outcome.
I want them to only check the status of services, for example, I could modify the command alias to something like this:
Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl status
But will this work when checking the status of the ssh
service? No.
Now we encounter the same issue we faced when installing new software. We should modify our command alias to include the asterisk:
Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl status *
Now, users assigned to the SERVICECOMMANDS
alias can check the status of any service on the server.
We can limit users' actions in another way.
For example, if I want to grant a user the privileges to run the systemctl
command with all its subcommands, options, or arguments but only against the ssh
service, I could do it like this:
Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl * ssh
Users can now perform all necessary tasks with the Secure Shell service, but they are restricted from shutting down or rebooting the server, editing services files, or changing system targets.
Be careful when specifying commands. I never recommend specifying a command by itself. Always specify what comes after the command and be specific about what these users could do.
Mitigating Shell Escapes for Users
Some programs, like text editors and pagers, have a feature called Shell Escape that allows users to run commands without exiting the program first.
For example, from the command mode of the vim editor, I can run the :!ls
command like this:
#!/bin/bash
echo "This is a bash script"
~
~
:!ls
Output:
bib.csv lilly.sh script.sh
Press ENTER or type command to continue
As you can see, the ls
command listed all files in my current working directory.
This may not look harmless at first, but what would happen if the user runs a command that requires root privileges?
Consider this entry in the sudoers
file:
ivan ALL=(ALL:ALL) /usr/bin/vim /etc/ssh/sshd_config
The user Ivan should only be able to edit the sshd_config
file.
Let me open the file and run the :!apt update
command like this:
:!apt update
Oops, I'm able to run this command as the root user, even though I don't have the privilege to run it.
Let me try running the :shell
command and see what happens:
:!shell
Output:
root@testing:/home/ivan#
Look what happened! Now I've switched to the root user and can do anything I want.
So, by only granting Ivan the privilege to edit the sshd_config
file using vim, I inadvertently gave them full root privileges. How can this be resolved?
We can fix this by allowing Ivan to use the sudoedit
command instead of the vim editor, like this:
ivan ALL=(ALL:ALL) sudoedit /etc/ssh/sshd_config
The sudoedit
command doesn't allow any user to escape to a shell with root privileges. If you still try to use the :shell
command, you will escape to your own non-privileged shell.
Some programs also have a shell escape feature, such as emacs, less, and more. To address this issue, you can use the NOEXEC
option inside the sudoers
file like this:
ivan ALL=(ALL:ALL) NOEXEC: /usr/bin/less /etc/ssh/sshd_config
Now, the user Ivan can use the less
command to view the content of the sshd_config
file, but they can't use the shell escape feature.
We could have used the NOEXEC
option with the vim editor from the beginning, but I wanted to demonstrate two different methods.
Preventing Abuse of Shell Scripts
Let's assume that the user Ivan created a shell script that requires root privileges to run and asks for the privilege to run it.
Consider this entry in the sudoers
file:
ivan ALL=(ALL:ALL) /home/ivan/script.sh
Now, the user Ivan is able to run the script as the root user.
Since Ivan is the owner of the script, he can edit it the way he wants.
Therefore, he can essentially add the sudo -i
command to the script, which will open a new shell with the root user, granting Ivan full root privileges.
And this is a problem.
That's why I never recommend giving any user the privilege to run any script owned by them as the root user.
To solve this problem, we move their script to the /usr/local/sbin/
directory after they finish editing it and after we finish reviewing it.
Then, we change its ownership to the root user and give them the privilege to run it.
This way, Ivan can run the script, but they can't edit it.
Viewing sudo Privileges
If you are unsure of your privileges, using the sudo -l
command will provide you with an overview of the privileges you possess.
When running the command, first, you will see some of the environmental variables for your user account and then your privileges.
Let me run this command as the user Ivan and see the results:
sudo -l
Output:
Matching Defaults entries for ivan on testing:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User ivan may run the following commands on testing:
(ALL : ALL) /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install *
(ALL) /usr/bin/systemctl status *
On this testing
server, I, the user Ivan, can update existing packages, install software, and check the status of any service.
Great, now I know what I'm capable of!
The sudo Timer
Have you ever wondered why, after running any command using sudo
and entering your password, running another command with sudo
won't ask you for your password?
This is due to the sudo timer. By default, it is set to five minutes.
This means that once you run a command with sudo
and enter your password, your password will be cached for five minutes, allowing you to run subsequent commands without entering your password during that time frame.
However, this could pose a risk when users leave their desks and leave the terminal open. That's why you may want to consider disabling the sudo timer.
To do this, add the following line to the Defaults
section of the sudoers
file:
Defaults timestamp_timeout = 0
Now, try running two commands in succession and observe that you need to enter your password each time.
If you are on a server where the sudo timer is enabled and you want to leave your desk without closing the terminal, consider resetting the timer using this command:
sudo -k
This will reset the timer, and now if someone tries to run a command using sudo
, they will need your password.
Monitoring User Activities
One of the great features of sudo is its ability to monitor how users utilize sudo and track their actions.
By default, sudo logs its activities in the auth.log
file. However, I prefer to separate log files for easier investigations.
For this, we need to add one thing to the Defaults
section in the sudoers
file:
Defaults logfile=/var/log/sudo.log
Now, sudo will log its activities in the sudo.log
file, capturing both successful and failed attempts.
Conclusion and Final Thoughts
Awesome job reaching the end!
I hope this guide has been helpful in giving you a solid understanding of how to securely manage users on a Linux server.
By implementing these practices, you’re taking important steps to protect your server and its data.
If you found value in this guide or have any questions or feedback, please don't hesitate to share your thoughts in the discussion section.
Your input is greatly appreciated, and you can also contact me directly if you prefer.
Discussion