Bash Script: A Demo of User Account and Group Creation.
Tags: linux, bash, automation
Published: 2024-07-06 07:19:54

As a SysOps engineer, part of your job will be to manage user accounts and groups effectively for least privilege on the company's systems/servers to maintain security and access control.
Using Bash Script can greatly enhance your performance in that regard and also management and configuration of the systems.
 
**Table of Contents**
- Prerequisite
- What is Bash
- What is Bash Scripting 
- Demo
   - Solution
- Conclusion
 

**Prerequisite**

- A Linux system. (Ubuntu, CentOS, etc) with bash installed.
- Basic Knowledge of Linux Commands.
- Basic Knowledge of Bash Scripting.
 
**What is Bash**
Bash means Bourne again shell. It is the shell that Linux environment uses to communicate with the kernel to make requests and receive feedback.

**What is Bash Scripting?**
Bash scripting involves writing scripts using the Bash (Bourne Again Shell) command language to automate tasks on Unix-like operating systems. These scripts can range from simple commands executed sequentially to complex programs with control structures and functions.

**Demo**
Your company has employed many new developers. As a SysOps engineer, write a bash script called create_users.sh that reads a text file containing the employee’s usernames and group names, where each line is formatted as user; groups.
The script should create users and groups as specified, set up home directories with appropriate permissions and ownership, generate random passwords for the users, and log all actions to /var/log/user_management.log. Additionally, stores the generated passwords securely in /var/secure/user_passwords.txt.

**Solution**
First is to create a directory with the command `mkdir HNG_Tasks`
Then inside the directory, create a script file with the command `touch create_users.sh`
Next is to open the script with a text editor by running the command `sudo vi create_users.sh`
Then populate the script with the code below 

```
#!/bin/bash

# Log file and password file creation
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE="/var/secure/user_passwords.txt"

# Ensure script is run as root/sudo
if [ "$EUID" -ne 0 ]; then
    echo "Please run as root or use sudo."
    exit 1
fi

# Create directories and files if they don't exist, and set permissions
mkdir -p /var/log
mkdir -p /var/secure
touch $LOG_FILE
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE

# Function to create log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# Function to generate a random password
generate_password() {
    echo $(openssl rand -base64 12)
}

# Check if the input file is provided
if [ -z "$1" ]; then
    echo "Usage: sudo bash $0 <name-of-text-file>"
    exit 1
fi

# Read the input file
INPUT_FILE=$1

# Process each line in the input file
while IFS=';' read -r username groups; do
    # Ignore empty lines and lines that start with a hash (#)
    if [[ -z "$username" || "$username" == \#* ]]; then
        continue
    fi

    # Removing all whitespace from username and groups
    username=$(echo "$username" | xargs)
    groups=$(echo "$groups" | xargs)

    # Check if user already exists
    if id "$username" &>/dev/null; then
        log_message "User '$username' already exists."
    else
        # Create user with home directory and bash shell
        useradd -m -s /bin/bash "$username"
        if [ $? -eq 0 ]; then
            log_message "Created user '$username'."
        else
            log_message "Failed to create user '$username'."
            continue
        fi

        # Create personal group with the same name as the user
        usermod -g "$username" "$username"
        if [ $? -eq 0 ]; then
            log_message "Created personal group for user '$username'."
        else
            log_message "Failed to create personal group for user '$username'."
        fi
        
        # Generate and set a random password for the user
        password=$(generate_password)
        echo "$username:$password" | chpasswd
        if [ $? -eq 0 ]; then
            log_message "Set password for user '$username'."
        else
            log_message "Failed to set password for user '$username'."
        fi
        
        # Save the password to the secure file
        echo "$username,$password" >> $PASSWORD_FILE
    fi

    # Add user to specified groups if there are any
    if [ -n "$groups" ]; then
        # Split groups by comma and loop through each group
        IFS=',' read -r groups_list <<< "$groups"
        for group in ${groups_list//,/ }; do
            group=$(echo "$group" | xargs) # Trim whitespace
            if getent group "$group" &>/dev/null; then # Checking if group exist
                usermod -aG "$group" "$username"
                if [ $? -eq 0 ]; then
                    log_message "Added user '$username' to group '$group'."
                else
                    log_message "Failed to add user '$username' to group '$group'."
                fi
            else
                groupadd "$group"
                if [ $? -eq 0 ]; then
                    usermod -aG "$group" "$username"
                    log_message "Created and added user '$username' to group '$group'."
                else
                    log_message "Failed to create group '$group' or add user '$username' to group."
                fi
            fi
        done
    fi

done < "$INPUT_FILE"

log_message "User creation and group assignment completed."

```
**Code Breakdown**

```
#!/bin/bash

# Log file and password file creation
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE="/var/secure/user_passwords.txt"

# Ensure script is run as root/sudo
if [ "$EUID" -ne 0 ]; then
    echo "Please run as root or use sudo."
    exit 1
fi
```
The block of code above starts with the Shebang line, defines the log file and the password file as variables to their paths, and makes sure the script is ran as a root user or with sudo to avoid any permission issues. (`if [ "$EUID" -ne 0 ]; then:` This condition checks if the effective user ID (EUID) is not equal to 0. In Unix-like systems, the root user has an EUID of 0.)

```
mkdir -p /var/log
mkdir -p /var/secure
touch $LOG_FILE
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE

# Function to create log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}
```
The code above creates directories for the log and password files if those directories don't exist already. The `-p` flag makes sure no error is thrown if the directories already exist.
Using the variable declaration in the first block of code, creates a log file and a password file.
It changes the permission of the password file to '600' for the owner to have read and write permission of the file, to restrict access to the password file. 
The next line of code creates a function called `log_message`. Tis function captures the current date and time and prints it alongside the echo command which is pipped (|) to the `tee` command. The `tee` command writes the output to both the standard output (so it appears in the terminal) and appends it (with the -a flag) to the log file specified by LOG_FILE.

```
# Function to generate a random password
generate_password() {
    echo $(openssl rand -base64 12)
}

# Check if the input file is provided
if [ -z "$1" ]; then
    echo "Usage: sudo bash $0 <name-of-text-file>"
    exit 1
fi
```
The Code above has a function that generates random passwords with openssl rand -base64 12: The openssl command is used to generate random data. The rand subcommand generates random bytes, and the -base64 option encodes the output in Base64. The 12 specifies the number of random bytes to generate, which after Base64 encoding results in a 16-character string. The 'echo $' captures and prints the random password.
The code also checks if the script is run with an argument. The if condition checks if the first parameter/argument ($1) is empty with the '-z' flag and terminates with an exit status of 1 if no argument is run with the script. 

```
# Read the input file
INPUT_FILE=$1

# Process each line in the input file
while IFS=';' read -r username groups; do
    # Ignore empty lines and lines that start with a hash (#)
    if [[ -z "$username" || "$username" == \#* ]]; then
        continue
    fi

    # Removing all whitespace from username and groups
    username=$(echo "$username" | xargs)
    groups=$(echo "$groups" | xargs)
```

 The next block of code assigns the variable "INPUT_FILE" to the value of the first argument "$1", and uses a while loop to read the input file line by line and with the `IFS=';'` splits each line into fields based on the semicolon, reads each line and splits it into two variables: username and groups. With an if statement checks if the username is empty or if it's a comment and skips to the next condition if they are both true and uses the `xargs` command to remove leading and trailing whitespace from username and groups.

```
 # Check if user already exists
    if id "$username" &>/dev/null; then
        log_message "User '$username' already exists."
    else
        # Create user with home directory and bash shell
        useradd -m -s /bin/bash "$username"
        if [ $? -eq 0 ]; then
            log_message "Created user '$username'."
        else
            log_message "Failed to create user '$username'."
            continue
        fi

        # Create personal group with the same name as the user
        usermod -g "$username" "$username"
        if [ $? -eq 0 ]; then
            log_message "Created personal group for user '$username'."
        else
            log_message "Failed to create personal group for user '$username'."
        fi
```
This block of code uses an if condition to check if a user already exists, with the `id "$username"` attempts to retrieve user information for username, `&>/dev/null` redirects both standard output and standard error to /dev/null, effectively silencing any output from the id command. And if the user doesn't exist, with the `useradd -m -s /bin/bash "$username"` it creates the user, a home directory for the user with the `-m` flag and  sets the user default shell to bash with the `-s /bin/bash`. After creating a user, it creates a personal group for the user with the code `usermod -g "$username" "$username"` where `usermod` modifies a user account, `-g "$username"` sets the primary group of the user to a group with the same name as the user and the `$username` specifies the username to be modified. It checks the exit status of the `usermod` command, If the group modification is successful, it logs a message indicating the personal group was created and logs the failure message if it fails. 

```
   # Generate and set a random password for the user
        password=$(generate_password)
        echo "$username:$password" | chpasswd
        if [ $? -eq 0 ]; then
            log_message "Set password for user '$username'."
        else
            log_message "Failed to set password for user '$username'."
        fi
        
        # Save the password to the secure file
        echo "$username,$password" >> $PASSWORD_FILE
    fi
```

This part of the script handles generating and setting a random password for the newly created user, and then saving the password to a secure file. Let's go through it step-by-step:
The first line calls the function `generate_password`, and stores the value of the function in the variable. The `echo "$username:$password" | chpasswd` prints the username and password in the format required by `chpasswd` and pipes this input to the `chpasswd` command, which updates the user's password in the system. It checks the exit status of the password if it set the password for the user or not and appends the username and password to a file specified by the variable PASSWORD_FILE.

```
 if [ -n "$groups" ]; then
        # Split groups by comma and loop through each group
        IFS=',' read -r groups_list <<< "$groups"
        for group in ${groups_list//,/ }; do
            group=$(echo "$group" | xargs) # Trim whitespace
            if getent group "$group" &>/dev/null; then # Checking if group exist
                usermod -aG "$group" "$username"
                if [ $? -eq 0 ]; then
                    log_message "Added user '$username' to group '$group'."
                else
                    log_message "Failed to add user '$username' to group '$group'."
                fi
            else
                groupadd "$group"
                if [ $? -eq 0 ]; then
                    usermod -aG "$group" "$username"
                    log_message "Created and added user '$username' to group '$group'."
                else
                    log_message "Failed to create group '$group' or add user '$username' to group."
                fi
            fi
        done
    fi

done < "$INPUT_FILE"

log_message "User creation and group assignment completed."
```
This section of the script assigns newly created users to specific groups. It checks if the groups exist and creates them if they don't exist. It checks if the variable is empty with the'-n' flag, sets the field separator to a comma (,), and reads the comma-separated groups into group_list. It starts a for loop to go over the group_list, replacing the comma(,) with a space, trims white space from the group variable, it checks if the group exists. If the group exists, it adds the user to the group, but if it does not, it creates the group and then adds the user. It logs the final message indicating the user creation and group assignment. 

The next step is to save the script, exit the text editor, and make the script executable by running the command `chmod +x create_users.sh`

Then create a users.txt file with the command `touch users.txt` for the script to read from. Open with a code editor with the command `sudo vi users.txt` and populate with users and groups of your choice. Eg

![Image of users and groups](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jz711ftiosqaoepfjx5u.PNG)
 
Then run the script with the command `sudo ./create_users.sh users.txt`
The output the script gives is in the picture below 


![Image of script outpu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vo3fm21z1ep8cn4b0hfn.PNG)

**Conclusion**
At the end of this article, you have learned what bash and bash scripting is, why we use bash script, and understand the code used in the demo. This is a task for the [HNG](https://hng.tech/internship) internship stage one program. You can check out available roles at HNG with this [link](https://hng.tech/hire)