Automating User Management and Permissions on Linux using Bash Scripting
Tags: linux, authorization
Published: 2024-07-02 06:04:17

Linux is a multi-user operating system and as such, an administrator can create users and groups for different purposes. Both users and groups have their permissions. When a user is added to a group, it inherits the permissions of that groups. In this article, you will learn how to work with permissions for users and groups by creating different users and adding them to different groups. The process will be automated using a bash script.

## Requirements

1. Users and their groups are defined in a text file that will be supplied to the script as an argument
1. A corresponding home directory will be created for each user
1. User passwords should be stored securely in a file with path /car/secure/user_passwords.txt
1. Logs of all actions should be logged to /var/log/user_management.log
1. Only the owner, in this case root, should be able to access the user_password.txt file
1. Errors should be gracefully handled

## Creating users

To create a user, you can use the `useradd` command. This command can be set to create a user, create their home directory, and set their password. If you simply wish to add a user to the linux system, you can run:

```
sudo useradd <username>
```

`<username>` here is the name of the user you wish to add. However, if you wish to create a home directory and add a password, you can run this instead:

```
sudo useradd -m -p $(openssl passwd -6 "$password") <username>
```

This command uses the `-m` flag to add a home directory and the `-p` flag to add a password (encrypted using openssl) for the user.

## Adding users to groups

By default, when a user is created, a personal group with their username is also created. This means you won't need to explicitly create this. However, to add a user to group, say sudo, you must use the usermod command. Find the basic command structure below:

```
sudo usermod -aG "<group>" "<user>"
```

The -a flag is used to append the user to the new group without removing them from existing groups. The -G flag on the other hand specifies the group that the user will be added to, in this case, <group>.

### Creating groups

When a group doesn't exist, it should be created before a user is added to it. Groups are typically created using the `groupadd` command. Here's an example of the command in action:

```
sudo groupadd "<group>"
```

## Combining user creation, group creation, and group addition

You can command user creation, group creation, and adding a user to a group in a script. Say you have your users and groups defined in a semi-colon delimited script like this:

```
user1; group1, group2
user2; group3,group6
user3;group2,group3
```

You can write a script that loops through the file, extracts the relevant information, and creates the users and groups. 

```sh
USERS_FILE=$1

mapfile -t lines < "$USERS_FILE"

# loop over each line in the array
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"

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

    # Create the user with the generated password
    sudo useradd -m -p $(openssl passwd -6 "$password") "$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 added to appropriate groups"
done
```

Now, the script above does the following:
1. It takes in a single argument, expressed using `$1`. It then sets this argument as the variable, `$USERS_FILE`.
1. It uses the mapfile command to load the content of the `$USERS_FILE` into an array called lines.
1. It loops through each lines of `lines` and extracts the user and groups using the Internal Field Separator (IFS) shell command.
1. It generates a 6-character password using `pwgen`. `pwgen` is linux package that allows you to create passwords to your exact specification.
1. It loops over the groups, after splitting each group into groups using IFS, creates the group if it doesn't exist, and adds the user to the group.

## Securing the script: Hashing passwords with openssl

While the script above performs all the operations needed to create users and groups, and then add the users to groups, it does not consider security. The major security issue is that the generated passwords are added to users in plaintext format. To solve this problem, you can utilize openssl to hash the password. You can simply run `openssl passwd -6 (generated_password)` to achieve hashing. This command uses the SHA 512 algorithm for hashing. It's security is comparable to SHA 256 which is the most prominent hashing algorithm on the internet. 

### Encrypting and storing the passwords

Since this script creates users, it is wise to capture the generated passwords in a file. But to do that securely, the passwords must be encrypted. Password encryption can also be done using openssl. But it'll require and encryption key. You can use the command below to generate, encrypt, and store a password.

```sh
    # 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"
```

The `$PASSWORD_ENCRYPTION_KEY` and `$PASSWORD_FILE` must be defined for this operation to complete successfully. 

### A look at the secure script

Here's the updated script with the password encryption and password hashing functionalities:

```sh
#!/bin/bash

PASSWORD_FILE_DIRECTORY="/var/secure"
PASSWORD_FILE="/var/secure/user_passwords.txt"
PASSWORD_ENCRYPTION_KEY="secure-all-things"
USERS_FILE=$1

# Function to encrypt password
encrypt_password() {
    echo "$1" | openssl enc -aes-256-cbc -pbkdf2 -base64 -pass pass:"$2"
}

# Create the directory where the user's password file will be stored
sudo mkdir -p "$PASSWORD_FILE_DIRECTORY"
sudo touch "$PASSWORD_FILE"
sudo chmod 600 "$PASSWORD_FILE" # Set read permission for only the owner of the file
sudo chown root:root "$PASSWORD_FILE" # Set the owner as the root user

# load the content of the users.txt file into an array: lines
mapfile -t lines < "$USERS_FILE"

# loop over each line in the array
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"


    # 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"

    # 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

# remove the created password from the current shell session
unset password
```

The script above includes the additional functionality as preventing non-root users from accessing the password storage file and also removing the password variable using `unset password` from the shell where it is run. 

## Adding logging to the script

The script can be further improved by logging the commands to a log file. This file can be defined as a variable at the top of the script then a redirection command can be added to redirect logs from the script to the log file. We can also direct errors that might occur to  the std out, that's the normal output you see when you run commands without errors. Both log types will ultimately be sent to the log file. The command below illustrates this:


```sh
# Redirect stdout and stderr to log file
exec > >(tee -a "$LOG_FILE") 2>&1 
echo "Executing script... (note that this line will be logged twice)" | tee -a $LOG_FILE 
```

The echo "Executing script..." command is added so that the normal console shows the logs too. It's not wise to run a script without seeing an output. The addition of this line will ultimately mean it gets shown in the log file twice, but this is the compromise that has to be made.

## Adding Error Handling

Errors can be handled and prevented using exception handling. We can add functions that check that both openssl and pwgen are installed, otherwise installs them. When can also add handlers that check if arguments are not passed to the script and if the argument passed for the user's file is a valid file. Here's a snippet with these exception handlers:

```sh
#!/bin/bash

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

# Function to display usage information
usage() {
    echo "Usage: $0 <user-data-file-path>"
    echo "    <user-data-file-path>: Path to the file containing user data."
    echo
    echo "The user data file should contain lines in the following format:"
    echo "    username;group1,group2,..."
    echo
    echo "Example:"
    echo "    light; dev,sudo"
    echo "    mayowa; www-data, admin"
    exit 1
}

# Check if script is run with sudo
if [ "$(id -u)" != "0" ]; then
    echo "This script must be run with sudo. Exiting..."
    exit 1
fi


# Check if an argument was provided
if [ $# -eq 0 ]; then
    echo "Error: No file path provided."
    usage
fi

# Check if the user's data file exists
if [ ! -e "$USERS_FILE" ]; then
    echo "Error: The provided user's data file does not exist: $USERS_FILE"
    usage
fi

# Function to check if a package is installed
is_package_installed() {
    dpkg -s "$1" >/dev/null 2>&1
}

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

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

# Check if the file exists
if [ ! -f "$USERS_FILE" ]; then
    echo "Error: $USERS_FILE not found."
    exit 1
fi
``` 

An exception is also added that checks if the script was run using the sudo command. This is because sudo is required to perform useradd and groupadd operations.

## Conclusion

This article outlines the process of creating users and groups in an automated manner using a script. It makes various assumptions and trade-offs to ensure that the script is secure while being usable. Now as an administrator, you can use this script to automate user additions for your organization.

You can find the full script in this [Github repo](https://github.com/vicradon/users-and-groups-linux). Shoutout to [HNG](https://hng.tech) for this opportunity to learn non-trivial bash scripting using a real-world example. You can join an upcoming HNG internship by regularly checking [their internship page](https://hng.tech/internship). You can also hire elite talent for your project from the HNG network by visiting [HNG hire](https://hng.tech/hire)