Smart SysAdmin: Automating User Management on Linux with Bash Scripts
Abasifreke Nkanang
Posted on July 3, 2024
Introduction
This article is a step-by-step walkthrough of a project I recently worked on, where I needed to automate user management in Linux This project was one of the tasks for the HNG internship, a program designed to accelerate learning and development in the tech industry.
This guide will provide an in-depth look at how I developed this bash script, explaining the reasoning behind each line of code and the approach I took. By sharing my thought process and important considerations, I aim to demonstrate my proficiency in bash scripting and system administration. Whether you are a beginner or an experienced sysadmin, this guide will help you understand the nuances of automating user management in Linux.
Understanding the Requirements
Overview:
- Write a bash script called
create_users.sh
. - The script reads a text file containing usernames and groups.
- Create users and groups as specified.
- Set up home directories with appropriate permissions and ownership.
- Generate random passwords for the users.
- Log all actions to
/var/log/user_management.log
. - Store generated passwords securely in
/var/secure/user_passwords.csv
.
Input Format:
- Each line in the input file is formatted as
user;groups
. - Multiple groups are separated by commas
,
. - Usernames and groups are separated by a semicolon
;
.
Criteria:
- Users should be created and assigned to their groups.
- Logging actions to
/var/log/user_management.log
. - Storing passwords in
/var/secure/user_passwords.csv
.
Detailed Script Execution
Let's break down the code step by step:
A. Ensuring Root Privileges
#!/bin/bash
This is the shebang line that specifies the script should be run using the bash shell. It ensures the script is executed in the correct shell environment.
# Check if the script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root"
exit 1
fi
This section checks if the script is being run as the root user. The id -u
command returns the user ID of the current user. The root user has a user ID of 0. If the script is not run as root (i.e., the user ID is not 0), it prints an error message and exits with a status of 1. Running as root is necessary because creating users and modifying system files requires superuser privileges.
Alternatives: Instead of exiting, you could prompt the user to re-run the script with sudo
or use a function to elevate privileges automatically.
B. Setting Up Log and Secure Directory
Logging actions and securely storing generated passwords are crucial for monitoring and auditing purposes.
# Log file
LOG_FILE="/var/log/user_management.log"
SECURE_DIR="/var/secure"
PASSWORD_FILE="$SECURE_DIR/user_passwords.csv"
These lines define variables for the paths of the log file and the secure directory.
-
LOG_FILE
is the path where the script will log its actions. -
SECURE_DIR
is the directory where the password file will be stored. -
PASSWORD_FILE
is the file where generated passwords will be securely stored.
# Create secure directory if it doesn't exist
mkdir -p "$SECURE_DIR"
chmod 700 "$SECURE_DIR"
-
Explanation:
-
mkdir -p "$SECURE_DIR"
: This command creates theSECURE_DIR
directory if it doesn't already exist. The-p
option ensures that no error is reported if the directory already exists, and it creates any parent directories as needed. -
chmod 700 "$SECURE_DIR"
: This command sets the permissions of theSECURE_DIR
directory to700
. This means: - The owner (root) has read, write, and execute permissions.
- No permissions are granted to the group or others. This ensures the directory is secure and only accessible by the root user.
-
# Clear the log and password files
true > "$LOG_FILE"
true > "$PASSWORD_FILE"
chmod 600 "$PASSWORD_FILE"
-
Explanation:
-
true > "$LOG_FILE"
andtrue > "$PASSWORD_FILE"
: These commands clear the contents of the log file and the password file, respectively. Using>
truncates the file to zero length if it exists, effectively clearing it. If the file does not exist, it is created. Note the importance of thetrue
command as it acts as a no-op (no operation) command and always ensures that the redirection operator has a valid command associated with it, and the files will be truncated as intended. -
chmod 600 "$PASSWORD_FILE"
: This command sets the permissions of thePASSWORD_FILE
to600
. This means: - The owner (root) has read and write permissions.
- No permissions are granted to the group or others. This ensures that the password file is secure and only readable and writable by the root user.
-
Alternatives: One can use a logging framework or tool for more advanced logging capabilities and security measures.
C. Processing the Input File
The section reads an input file containing usernames and group assignments, and processes each line as needed.
# Read the input file line by line
while IFS=';' read -r username groups; do
# Remove any leading or trailing whitespace
username=$(echo "$username" | xargs)
groups=$(echo "$groups" | xargs)
# Skip empty lines
[ -z "$username" ] && continue
Explanation
1 Reading the Input File Line by Line:
while IFS=';' read -r username groups; do
-
while ...; do ... done
: This is a loop that continues to execute as long as theread
command successfully reads a line from the input. -
IFS=';'
:IFS
stands for Internal Field Separator. By setting it to;
, we are telling theread
command to use;
as the delimiter for splitting each line into fields. -
read -r username groups
: Theread
command reads a line from the input file, splits it into two parts using;
as the delimiter, and assigns the first part to theusername
variable and the second part to thegroups
variable. -
-r
: This option prevents backslashes from being interpreted as escape characters.
2 Removing Leading or Trailing Whitespace:
username=$(echo "$username" | xargs)
groups=$(echo "$groups" | xargs)
-
$(...)
: This is command substitution, which captures the output of the command inside the parentheses. -
echo "$username" | xargs
:-
echo "$username"
: Prints the value of theusername
variable. - The output of
echo
is piped toxargs
. -
xargs
: This command trims any leading or trailing whitespace from its input.
-
- The same process is applied to the
groups
variable to remove any leading or trailing whitespace.
3 Skipping Empty Lines:
[ -z "$username" ] && continue
-
[ -z "$username" ]
: This is a test condition that checks if theusername
variable is empty.-z
returns true if the string is of zero length. -
&& continue
: If theusername
variable is empty, thecontinue
statement is executed. This causes the loop to skip the current iteration and proceed to the next line of input. This effectively ignores empty lines in the input file.
Alternatives: Use more sophisticated parsing techniques or external libraries for handling input files.
D. User and Group Management
Creating users, assigning them to groups, and setting permissions are the core functionalities of the script.
Checking and Creating User
if id "$username" &>/dev/null; then
echo "User $username already exists, skipping..." | tee -a "$LOG_FILE"
else
useradd -m -s /bin/bash -G "$username" "$username"
echo "Created user $username with personal group $username" | tee -a "$LOG_FILE"
fi
-
if id "$username" &>/dev/null; then
:- The
id
command checks if a user exists. It returns 0 if the user exists and non-zero if the user does not exist. -
&>/dev/null
redirects both stdout and stderr to/dev/null
, effectively discarding any output from theid
command. - If the user exists, the script proceeds to the
then
block; otherwise, it goes to theelse
block.
- The
-
echo "User $username already exists, skipping..." | tee -a "$LOG_FILE"
:- If the user exists, this message is logged. The
tee
command outputs the message to both the terminal and the log file ($LOG_FILE
).
- If the user exists, this message is logged. The
-
useradd -m -s /bin/bash -G "$username" "$username"
:- If the user does not exist, the
useradd
command creates the user. -
-m
: Creates a home directory for the user. -
-s /bin/bash
: Sets the user's default shell to/bin/bash
. -
-G "$username"
: Creates a personal group for the user with the same name as the username and adds the user to this group. - The username is specified twice because the first instance specifies the group and the second specifies the username.
- If the user does not exist, the
-
echo "Created user $username with personal group $username" | tee -a "$LOG_FILE"
:- Logs the creation of the new user and their personal group.
Adding User to Additional Groups
if [ -n "$groups" ]; then
IFS=',' read -ra ADDR <<<"$groups"
for group in "${ADDR[@]}"; do
group=$(echo "$group" | xargs) # Remove whitespace
if ! getent group "$group" >/dev/null; then
groupadd "$group"
echo "Created group $group" | tee -a "$LOG_FILE"
fi
usermod -aG "$group" "$username"
echo "Added user $username to group $group" | tee -a "$LOG_FILE"
done
fi
-
if [ -n "$groups" ]; then
:- Checks if the
groups
variable is non-empty.-n
returns true if the length of the string is non-zero.
- Checks if the
-
IFS=',' read -ra ADDR <<<"$groups"
:- Sets the Internal Field Separator (IFS) to
,
to split thegroups
string into an array (ADDR
) using commas as delimiters. -
<<<"$groups"
: This is a here-string, which feeds the value ofgroups
into theread
command.
- Sets the Internal Field Separator (IFS) to
-
for group in "${ADDR[@]}"; do
:- Iterates over each group name in the
ADDR
array.
- Iterates over each group name in the
-
group=$(echo "$group" | xargs)
:- Removes any leading or trailing whitespace from the group name using
xargs
.
- Removes any leading or trailing whitespace from the group name using
-
if ! getent group "$group" >/dev/null; then
:- The
getent group "$group"
command checks if the group exists. -
!
negates the result, so the condition is true if the group does not exist. -
>/dev/null
discards the output of thegetent
command.
- The
-
groupadd "$group"
:- Creates the group if it does not exist.
-
echo "Created group $group" | tee -a "$LOG_FILE"
:- Logs the creation of the new group.
-
usermod -aG "$group" "$username"
:- Adds the user to the specified group using the
usermod
command. -
-aG
: Appends the user to the supplementary group(s) without removing them from other groups.
- Adds the user to the specified group using the
-
echo "Added user $username to group $group" | tee -a "$LOG_FILE"
:- Logs the addition of the user to the group.
Alternatives: One can use more advanced user management tools or scripts for handling user and group assignments.
E. Setting Permissions and Ownership for the home directory
Setting Permissions for the Home Directory
chmod 700 "/home/$username"
-
chmod 700 "/home/$username"
:-
chmod
is a command used to change the file mode (permissions) of a file or directory. -
700
sets the permissions of the directory to: -
7
(read, write, and execute) for the owner (username
). -
0
(no permissions) for the group. -
0
(no permissions) for others. -
"/home/$username"
specifies the path of the home directory for the user. The$username
variable is replaced with the actual username. - This ensures that only the user can access their home directory, providing security and privacy.
-
Changing Ownership of the Home Directory
chown "$username":"$username" "/home/$username"
-
chown "$username":"$username" "/home/$username"
:-
chown
is a command used to change the ownership of a file or directory. -
"$username":"$username"
sets both the user owner and the group owner to the specified username. -
"/home/$username"
specifies the path of the home directory for the user. - This ensures that the user owns their home directory and can manage its contents.
-
Logging the Action
echo "Set permissions for /home/$username" | tee -a "$LOG_FILE"
-
echo "Set permissions for /home/$username"
:- Prints the message
"Set permissions for /home/$username"
to the terminal. - The
$username
variable is replaced with the actual username, providing a specific log entry for each user.
- Prints the message
-
| tee -a "$LOG_FILE"
:- The
tee
command reads from standard input and writes to both standard output and the specified file. -
-a
option tellstee
to append to the file rather than overwrite it. -
"$LOG_FILE"
specifies the path of the log file where the message will be recorded. - This logs the action of setting permissions for the user's home directory, ensuring a record is kept of all actions performed by the script.
- The
F. Generating Random Passwords
Let's break down the function that generates random passwords:
# Function to generate random passwords
generate_password() {
local password_length=12
tr -dc A-Za-z0-9 </dev/urandom | head -c $password_length
}
Explanation
1 Function Definition:
generate_password() {
- This line defines a shell function named
generate_password
. Functions in bash allow you to encapsulate and reuse code.
2 Local Variable:
local password_length=12
-
local
is a keyword used to declare a variable with local scope within the function. This means thatpassword_length
is only accessible within thegenerate_password
function. -
password_length=12
sets the length of the generated password to 12 characters. You can adjust this value to generate passwords of different lengths.
3 Generating the Password:
tr -dc A-Za-z0-9 </dev/urandom | head -c $password_length
- This line generates the random password using a combination of
tr
,head
, and/dev/urandom
:-
/dev/urandom
: This is a special file that provides random data. It's commonly used for generating random numbers or strings. -
tr -dc A-Za-z0-9
: Thetr
command is used to translate or delete characters. In this case:-
-d
option deletes characters from the input that are not specified. -
-c
option complements the set of characters specified, effectively including only the characters in the specified set. -
A-Za-z0-9
specifies the set of characters to include: uppercase letters (A-Z
), lowercase letters (a-z
), and digits (0-9
).
-
- The output of
/dev/urandom
is piped (|
) totr
to filter out any characters not in the specified set. -
head -c $password_length
: Thehead
command outputs the first part of files. The-c
option specifies the number of bytes to output. Here, it outputs the first 12 characters ($password_length
) from the filtered random data.
-
The result is a secure, random password of the specified length.
G. Generating and Storing User Passwords
# Generate and store the user's password
password=$(generate_password)
echo "$username,$password" >> "$PASSWORD_FILE"
echo "Generated password for $username" | tee -a "$LOG_FILE"
echo "$username:$password" | chpasswd
Generating user password
password=$(generate_password)
-
password=$(generate_password)
:- This line calls the
generate_password
function (defined earlier) to generate a random password. -
$(...)
is command substitution, which captures the output of thegenerate_password
function and assigns it to thepassword
variable.
- This line calls the
Storing the Username and Password
echo "$username,$password" >> "$PASSWORD_FILE"
-
echo "$username,$password" >> "$PASSWORD_FILE"
:- This line appends the username and the generated password to the password file.
-
"$username,$password"
creates a comma-separated string containing the username and password. -
>>
is the append redirection operator, which appends the output to the specified file without overwriting its existing content. -
"$PASSWORD_FILE"
is the path to the file where the passwords are securely stored.
Logging the Password Generation
echo "Generated password for $username" | tee -a "$LOG_FILE"
-
echo "Generated password for $username"
:- Prints the message
"Generated password for $username"
to the terminal. - The
$username
variable is replaced with the actual username, providing a specific log entry for each user.
- Prints the message
-
| tee -a "$LOG_FILE"
:- The
tee
command reads from standard input and writes to both standard output and the specified file. -
-a
option tellstee
to append to the file rather than overwrite it. -
"$LOG_FILE"
is the path of the log file where the message will be recorded. - This logs the action of generating a password for the user, ensuring a record is kept of all actions performed by the script.
- The
Setting the User's Password
echo "$username:$password" | chpasswd
-
echo "$username:$password"
:- Creates a string in the format
username:password
, where$username
and$password
are replaced with the actual username and generated password.
- Creates a string in the format
-
| chpasswd
:- The
chpasswd
command reads a list of username:password pairs from standard input and updates the passwords for the specified users. - This sets the password for the user to the generated password.
- The
Alternatives: Use password management tools or integrate with centralized authentication systems for more secure password handling.
Conclusion
By breaking down the script into detailed steps, we demonstrated the importance of checking for root privileges, setting up logging and secure storage, processing input files, managing users and groups, and securely handling passwords. Each part of the script was explained with code examples and alternatives, showcasing the thought process and best practices behind the implementation.
As stated earlier, this project was undertaken as part of the HNG internship, a program designed to accelerate the learning and development of talented individuals in the tech industry. The HNG internship provides a platform for interns to work on real-world projects, enhancing their skills and preparing them for future careers.
For the complete source code and detailed documentation, you can visit the GitHub repository.
To learn more about the HNG internship and its opportunities, please visit https://hng.tech/internship or https://hng.tech/premium.
Thank you for reading.
Posted on July 3, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024
November 30, 2024