Automating User Creation and Management with Bash Scripting: A Detailed Guide.
Tags: devops, sysops, bash, linux
Published: 2024-07-05 11:31:25

## Introduction:
Linux, being a multi-user system, empowers administrators to create users and groups, each with specific permissions. When users are assigned to groups, they acquire the group's permissions. Effective user and group management is essential for system administration, from small personal servers to extensive enterprise networks. Streamlining the user creation process is crucial for ensuring security, organization, and efficiency, and Bash scripting provides a powerful means of automating these tasks.

This guide will walk you through creating a Bash script for managing user accounts on a Linux system.

## The problem
Your company has employed many new developers. As a SysOps engineer, it's your responsibility to efficiently manage user accounts and their associated permissions. To streamline this process, you are tasked with writing a Bash script that automates the setup and configuration of new user accounts based on predefined criteria.

- 
**User and Group Definition:** Users and their groups are defined in a text file that will be supplied to the script as an argument.

- 
**Home Directory Creation:** A dedicated home directory should be created for each user
- 
**Secure Password Storage:** User passwords should be stored securely in a file with path `/car/secure/user_passwords.txt`
- 
**Logging Actions:** Logs of all actions should be logged to `/var/log/user_management.log` for auditing and troubleshooting purposes.
- 
**File Access Control:** Only the owner, in this case root, should be able to access the `user_password.txt file`, ensuring that unauthorised access is prevented.
- 
**Error Handling:** Errors should be gracefully handled

## Prerequisites
 
To begin this tutorial, ensure you have the following:

- 
A Linux system with administrative access.
- 
Basic familiarity with Linux commands.

## The Script:

The script, `create_user.sh`, automates user account creation, password encryption, group assignment, and logging activities on a Linux system. The goal is to streamline user management and ensure that all actions are securely recorded and auditable. Below is a detailed breakdown of each section of the script, including its purpose and functionality.

**The shebang.**

```
 #!/bin/bash
```

This specifies the type of interpreter script will be run with. Since it is a "bash" script, it should be run with the Bourne Again Shell (Bash) interpreter. Also, some commands in the script may not be interpreted correctly outside of Bash.

**Defining File Paths and Variables**
Next, create some environment variables that will hold the text_file.txt, the logs file path (var/log/user_management.log), and password file path (/var/secure/user_passwords.csv).
```
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE_DIRECTORY="/var/secure"
PASSWORD_FILE="/var/secure/user_passwords.txt"
PASSWORD_ENCRYPTION_KEY="secure-all-things"
USERS_FILE=$1
```
- 
`LOG_FILE`: specifies where the log entries will be stored. Logging is crucial for tracking actions and diagnosing issues.

- 
`PASSWORD_FILE_DIRECTORY`: defines the directory for storing user passwords securely.

- 
`PASSWORD_FILE`: is the file where encrypted passwords will be saved.

- 
`PASSWORD_ENCRYPTION_KEY`: is used to encrypt user passwords.

- 
`USERS_FILE`: takes the first script argument, which should be the path to a file containing user data.

Next, create this file  path and give them the right permission using the command below. 
```
touch $LOG_FILE
mkdir -p /var/secure
chmod 700 /var/secure
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE
```
Details of the command in the codeblock are:
`touch $LOG_FILE`

- 
`touch`: This command is used to create an empty file or update the access and modification times of an existing file.

- 
`$LOG_FILE`: This variable holds the path("/var/log/user_management.log") to the log file.

`mkdir -p /var/secure`

- 
`mkdir`: This command is used to create directories.

- 
`-p`: This option allows the creation of parent directories as needed. If the directory already exists, it will not return an error.

- 
`/var/secure`: This is the directory path to be created.

`chmod 700 /var/secure`

- 
`chmod`: This command is used to change the permissions of files or directories.

- 
`700`: This permission setting means:

- 
`7`: The owner has read, write, and execute permissions.

- 
`0`: The group has no permissions.

- 
`0`: Others have no permissions.

- 
`/var/secure`: The directory whose permissions are being modified.

` chmod 600 $PASSWORD_FILE`

- 
`chmod`: This command changes the permissions of a file.

- 
`600`: This permission setting means:

- 
`6`: The owner has read and write permissions.

- 
`0`: The group has no permissions.

- 
`0`: Others have no permissions.

**Checking for Sudo Privileges** 
```
if [ "$(id -u)" != "0" ]; then  
    echo "This script must be run with sudo. Exiting..."   
    exit 1
 fi
```
This code ensures the script runs with root privileges by checking if the Effective User ID (EUID) is zero. In Linux, an EUID of `0` corresponds to the root user. If the script is not executed with administrative rights, it exits with an error message. This is because the script will perform actions that require elevated privileges, such as creating users and groups, setting passwords, creating files, etc.

**Validating Arguments**
Next to ensure that the script is properly executed with an input file provided as an argument, this conditional check terminates the script if no argument is detected. Using `$#`to represent the number of arguments passed when executing the script, it validates whether no argument ($# equals zero) or more than one argument ($# is greater than or equal to 2) is given. If either condition is met, an error message is displayed, halting the script's execution.
```
if [ $# -eq 0 ]; then   
   echo "No file path provided."   
   echo "Usage: $0 <user-data-file-path>"   
   exit 1 
fi
```
Also to verify if the provided file exists. If the file does not exist, the script exits with an error message.
```
if [ ! -e "$USERS_FILE" ]; then
  echo "The provided user's data file does not exist: $USERS_FILE"
  exit 1
fi
```
**Function Definitions**
Next to ensure reusability and modularity in your code, copy and paste the several utility functions to your code
**Check for Installed Package:**
```
is_package_installed() {
  dpkg -s "$1" >/dev/null 2>&1
}
```
This function checks if a package is installed on your machine  using `dpkg`. It suppresses output and returns a status code.
**Encrypt Password:**
```
encrypt_password() {
  echo "$1" | openssl enc -aes-256-cbc -pbkdf2 -base64 -pass pass:"$2"
}
```
The commnad uses openssl to encrypt a password. The password is encrypted using `AES-256-CBC` and a provided encryption key.
**Set Bash as Default Shell:**
```
set_bash_default_shell() {
  local user="$1"
  sudo chsh -s /bin/bash "$user"
}
```
This function sets Bash as the default shell for the specified user.
**Installing Required Packages:**
To ensure you have the necessary packages to run the script, add the code-block 
```
if ! is_package_installed openssl; then
  echo "openssl is not installed. Installing..."
  sudo apt-get update
  sudo apt-get install -y openssl
fi

if ! is_package_installed pwgen; then
  echo "pwgen is not installed. Installing..."
  sudo apt-get update
  sudo apt-get install -y pwgen
fi
```

with the command above you have defined variables for the file paths corresponding to the log file, password file, and input file. Additionally, you have ensured that the script is being executed with superuser privileges.

The subsequent step involves iterating through each line in the input text file, separating these lines by usernames and groups, and then using the Bash script to create the users and assign them to their respective groups.
To set up these users and groups, incorporate the following commands into your `create_users.sh` script:
```
for line in "${lines[@]}"; do
    # Remove leading and trailing whitespaces
    line=$(echo "$line" | xargs)
    
    # Split line by ';' and store the second part
    IFS=';' read -r user groups <<< "$line"
    
    # Remove leading and trailing whitespaces from the second part
    groups=$(echo "$groups" | xargs)

    # Create a variable groupsArray that is an array from spliting the groups of each user
    IFS=',' read -ra groupsArray <<< "$groups"

    # Check if user exists
    if id "$user" &>/dev/null; then
        echo "User $user already exists. Skipping creation."
        continue
    fi

    # Generate a 6-character password using pwgen
    password=$(pwgen -sBv1 6 1)

    # Encrypt the password before storing it
    encrypted_password=$(encrypt_password "$password" "$PASSWORD_ENCRYPTION_KEY")

    # Store the encrypted password in the file
    echo "$user:$encrypted_password" >> "$PASSWORD_FILE"

    # Create the user with the generated password
    sudo useradd -m -p $(openssl passwd -6 "$password") "$user"

    # Set Bash as the default shell
    set_bash_default_shell "$user"

    # loop over each group in the groups array
    for group in "${groupsArray[@]}"; do
        group=$(echo "$group" | xargs)
        
        # Check if group exists, if not, create it
        if ! grep -q "^$group:" /etc/group; then
            sudo groupadd "$group"
            echo "Created group $group"
        fi

        # Add user to the group
        sudo usermod -aG "$group" "$user"
        echo "Added $user to $group"
    done

    echo "User $user created and password stored securely"
done
```
The command above does the following:
**Iterating Through Lines in the File**
```
for line in "${lines[@]}"; do

```
This command initiates a loop that iterates over each line in the `lines` array. Each element of the array `lines`, representing a line from the input file, is processed one by one and assigned to the `line` variable.

**Removing Leading and Trailing Whitespace**
```
line=$(echo "$line" | xargs)
```
Here, `echo "$line" | xargs` removes any leading and trailing whitespace from the current line. This ensures that any extra spaces at the beginning or end of the line are trimmed off.

**Splitting the Line into User and Groups**
```
IFS=';' read -r user groups <<< "$line"
```
This line splits the `line `variable into two parts based on the ; delimiter. The first part is assigned to `user`, and the second part is assigned to` groups. IFS=';' `sets the Internal Field Separator to ;, which defines how the line is split.

**Trimming Whitespace from Groups**
```
groups=$(echo "$groups" | xargs)

```
This command removes any leading and trailing whitespace from the `groups `variable. This is necessary to ensure that the group names are clean and free from extra spaces.

**Splitting Groups into an Array**
```
IFS=',' read -ra groupsArray <<< "$groups"

```
Here, `IFS=','` sets the Internal Field Separator to `,`. The read `-ra` groupsArray splits the groups string into an array groupsArray based on commas, with each element representing a group.

**Checking if User Already Exists**
```
if id "$user" &>/dev/null; then
    echo "User $user already exists. Skipping creation."
    continue
fi
```
This block checks if a user with the given `user` name already exists. The `id "$user" &>/dev/null ` command attempts to fetch user details, and if successful (i.e., the user exists), it outputs nothing to `/dev/null`. If the user exists, a message is printed, and the `continue` command skips the rest of the loop for this user and moves to the next iteration.

**Generating a 6-Character Password**
```
password=$(pwgen -sBv1 6 1)
```
This command generates a secure, random 6-character password using the `pwgen ` tool. The `-s ` flag ensures a secure password, `-B ` avoids ambiguous characters, and -v1 specifies the length and quantity (1 password).
**Encrypting the Password**
```
encrypted_password=$(encrypt_password "$password" "$PASSWORD_ENCRYPTION_KEY")
```
This line calls the `encrypt_password` function to encrypt the generated password. The encrypted password is stored in the `encrypted_password `variable. The encryption key used is stored in the `PASSWORD_ENCRYPTION_KEY ` variable.

**Storing the Encrypted Password**
```
echo "$user:$encrypted_password" >> "$PASSWORD_FILE"

```
This command appends the username and encrypted password to the `PASSWORD_FILE.` The ` >>` operator ensures that the data is added to the end of the file without overwriting existing content.
**Creating the User with the Generated Password**
```
sudo useradd -m -p $(openssl passwd -6 "$password") "$user"
```
This line creates a new user with the username stored in user. The `-m` option creates the user's home directory. The password is hashed using `openssl passwd -6` with the generated password, ensuring it is securely stored.

**Setting Bash as the Default Shell**
```
set_bash_default_shell "$user"
```
This function call sets Bash as the default shell for the new user. The `set_bash_default_shell ` function is assumed to be defined elsewhere in the script to change the user's shell to Bash.

**Adding User to Specified Groups**
```
for group in "${groupsArray[@]}"; do
    group=$(echo "$group" | xargs)
```
This loop iterates through each group in the groupsArray array. Each group is cleaned of leading and trailing whitespace.
**Checking and Creating Group if Necessary**
```
if ! grep -q "^$group:" /etc/group; then
    sudo groupadd "$group"
    echo "Created group $group"
fi
```
For each group, this block checks if the group exists in the `/etc/group` file. If not, the `groupadd ` command creates the group, and a confirmation message is printed.

**Adding User to the Group**
```
sudo usermod -aG "$group" "$user"
echo "Added $user to $group"

```
This command adds the user to the specified group using `usermod -aG`. The `-aG` option appends the user to the supplementary group without affecting membership in other groups. A message is printed confirming the addition.

With this, you’ve successfully developed a script that efficiently handles user and group management on your system. You can find the complete script on the [Github Repo](https://github.com/kalio007/auto-user-management)

### Verifying Script Functionality
To verify that your script is functioning correctly, execute it in a terminal that supports Linux commands. Use the following command in your terminal to run the script:

Firstly, make your script executable, run the command below,
```
chmod +x create_users.sh
```
then, run the script,
```
./create_user.sh ./text_file.txt
```

## In Conclusion

This article details the procedure for automating the creation of users and groups through a script. It incorporates several assumptions and compromises to balance security with usability. As an administrator, you can now leverage this script to streamline user onboarding within your organisation.