Automating User and Group Management Using Bash Script
Tags: bash, devops, sysadmin, cloud
Published: 2024-07-03 21:33:59

Bash, short for Bourne Again Shell, is a Unix shell and command language that has been a fundamental part of system administration and development for many years. As the default shell on many Unix-like operating systems, including Linux and macOS, Bash is renowned for its powerful capabilities in scripting and automation.

Imagine having to create user accounts, assign them to appropriate groups, set up home directories, and generate secure passwords for each new developer joining your team. Doing this manually can be tedious and error-prone. This is where Bash comes in. You can automate these repetitive tasks with Bash scripting, ensuring consistency and saving valuable time.

In this article, I used a Bash script to automate the onboarding process for new developers. This script reads a text file containing usernames and their respective groups, creates users, assigns them to groups, sets up their home directories, generates random passwords, and logs all actions. 

## Prerequisites

* A Unix-based system (Linux or macOS)
* Basic knowledge of Unix commands and Bash scripting

## Objective
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, store the generated passwords securely in /var/secure/user_passwords.txt

## My create_users.sh Script
```
#!/bin/bash

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

# Ensure the /var/secure directory exists
if [ ! -d "/var/secure" ]; then
    mkdir -p /var/secure
    chmod 700 /var/secure
fi

# Ensure the log file and password file exist and have correct permissions
touch $LOG_FILE
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE

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

# Check if the file is supplied
if [ $# -ne 1 ]; then
    log_message "ERROR: No input file supplied"
    echo "Usage: $0 <name-of-text-file>"
    exit 1
fi

INPUT_FILE=$1

# Check if input file exists
if [ ! -f $INPUT_FILE ]; then
    log_message "ERROR: Input file does not exist"
    echo "ERROR: Input file does not exist"
    exit 1
fi

# Read the input file line by line
while IFS=';' read -r username groups; do
    # Trim whitespace
    username=$(echo $username | xargs)
    groups=$(echo $groups | xargs)

    # Skip empty lines
    if [ -z "$username" ]; then
        continue
    fi

    # Create a personal group for the user
    if ! getent group $username >/dev/null; then
        groupadd $username
        log_message "Group $username created."
    else
        log_message "Group $username already exists."
    fi

    # Create the user with the personal group
    if ! id -u $username >/dev/null 2>&1; then
        useradd -m -g $username $username
        log_message "User $username created."
    else
        log_message "User $username already exists."
    fi

    # Assign the user to additional groups
    IFS=',' read -ra ADDR <<< "$groups"
    for group in "${ADDR[@]}"; do
        group=$(echo $group | xargs)
        if [ ! -z "$group" ]; then
            if ! getent group $group >/dev/null; then
                groupadd $group
                log_message "Group $group created."
            fi
            usermod -aG $group $username
            log_message "User $username added to group $group."
        fi
    done

    # Generate a random password
    password=$(openssl rand -base64 12)

    # Set the user's password
    echo "$username:$password" | chpasswd
    log_message "Password set for user $username."

    # Store the password securely
    echo "$username,$password" >> $PASSWORD_FILE
done < "$INPUT_FILE"

# Set the correct permissions on the password file
chmod 600 $PASSWORD_FILE

log_message "User creation process completed."

echo "User creation process completed. Check $LOG_FILE for details."
```
## A detailed breakdown of the Script
Here is what this script does:

* Shebang
The `#!/bin/bash` known as the shebang indicates that the script should be run on the BASH shell.

* Path to Log file and Password file
```
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE="/var/secure/user_passwords.txt"
```
The `LOG_FILE` and `PASSWORD_FILE` are variables that store the path to the log file and password file respectively.

* Create /var/secure directory and set permission if it doesn't exist
```
if [ ! -d "/var/secure" ]; then
    mkdir -p /var/secure
    chmod 700 /var/secure
fi
```

* Create Log file and Password file if they don't exist and set permission
```
touch $LOG_FILE
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE
```
`touch $LOG_FILE` and `touch $PASSWORD_FILE` creates the log and password files if they do not already exist.
`chmod 600 $PASSWORD_FILE` sets the permissions for the password file so that only the file owner can read and write to it.

* Log message function
```
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
```
The `log_message` function takes a message as an argument and appends it to the log file with a timestamp.

* Check if input file is provided
```
if [ $# -ne 1 ]; then
    log_message "ERROR: No input file supplied"
    echo "Usage: $0 <name-of-text-file>"
    exit 1
fi
```
This checks if `[ $# -ne 1 ]; then` checks if exactly one argument (the input file) is supplied to the script.
If not, it logs an error, prints a usage message, and exits the script.

* `INPUT_FILE=$1` assigns the first argument (input file) to the variable INPUT_FILE

* Check if the input file exists

```
if [ ! -f $INPUT_FILE ]; then
    log_message "ERROR: Input file does not exist"
    echo "ERROR: Input file does not exist"
    exit 1
fi
```
The `if [ ! -f $INPUT_FILE ]; then` checks if the input file exists.
If not, it logs an error, prints an error message, and exits the script.Cre

* Read and process the input file

The `while IFS=';' read -r username groups; do` starts a loop to read the input file line by line, expecting each line to contain a username and groups separated by a semicolon (;).

* The command below trims whitespace from the username and groups variables if any.
```
    username=$(echo $username | xargs)
    groups=$(echo $groups | xargs)
```
While 
```
    if [ -z "$username" ]; then
        continue
    fi
```
Skips empty lines (where username is empty).

* Create a group for user
```
    if ! getent group $username >/dev/null; then
        groupadd $username
        log_message "Group $username created."
    else
        log_message "Group $username already exists."
    fi
```
This creates a personal group for the user if it doesn't exist and logs the action.

* Creates user for group
```
 if ! id -u $username >/dev/null 2>&1; then
        useradd -m -g $username $username
        log_message "User $username created."
    else
        log_message "User $username already exists."
    fi
```
This creates the user with their group if they don't already exist and logs the action.

* Creates additional groups for users.
```
IFS=',' read -ra ADDR <<< "$groups"
    for group in "${ADDR[@]}"; do
        group=$(echo $group | xargs)
        if [ ! -z "$group" ]; then
            if ! getent group $group >/dev/null; then
                groupadd $group
                log_message "Group $group created."
            fi
            usermod -aG $group $username
            log_message "User $username added to group $group."
        fi
    done
```
This command splits the group's string into an array, iterates over it, checks if the group exists (creating it if necessary) and adds the user to the group, logging each action.

* Generate a random password
The `password=$(openssl rand -base64 12)` generates a random password using the OpenSSL

* Set the user's password
```
    echo "$username:$password" | chpasswd
    log_message "Password set for user $username."
    echo "$username,$password" >> $PASSWORD_FILE
done < "$INPUT_FILE"
```

The code above sets the user's password and then appends the username and password to the PASSWORD_FILE 

* This `done < "$INPUT_FILE" ` code ends the loop that reads from the INPUT FILE

* The `chmod 600 $PASSWORD_FILE` code ensures the password file's permissions are secure after all passwords have been added.

* Log completion of the user creation process
`log_message "User creation process completed."`

## Test the Script

To test this script, let's create a `users.txt` file
`nano users.txt`

In the text file, enter the usernames and groups
`wendy; engineering,webteam
florenceokoli; admins, dev_team
chi; support`

## Execute the Script

`chmod create_users.sh

## Run the Script
`sudo ./create_users.sh users.txt`

## Output

* Management Log
![Management Log File](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8n8tuyvsuqe1ew8se087.png)

* Password.txt File
![Password.txt file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w94og8h91738x2k3e20u.png)

## Conclusion
Bash is a powerful scripting tool used to automate various tasks on Unix-like operating systems. This script is designed to read a text file containing usernames and their respective groups, create users and their personal groups, assign users to additional groups, set up home directories, generate random passwords, and log all these actions for auditing purposes. Additionally, it stores the generated passwords securely in a dedicated file.

This project is a stage 1 task in the Devops HNG-11 Internship. For more information about the HNG Internship and its various opportunities, visit [HNG Internship](https://hng.tech/) and [HNG Hire](https://hng.tech/hire).