Automation: Onboard New Engineers on Linux with Best Practice Bash/Shell Scripting.
Tags: bash, ubuntu, automation, linux
Published: 2024-07-02 19:28:27

## Synopsis
Your role is **SysOps or SysAdmin Engineer**, you are tasked with onboarding new engineers on most of the company's Linux servers. Users, groups, and home directories would be created. Access permissions for each user following the rule of less privilege should be observed. It would be inefficient to do so manually, looking at the number of servers and new engineers to be onboarded.
> *I have created a script that meets the basic requirements and some more.* 
>
>*It puts measures in place for errors while running the script, creates secure files to store user lists and passwords, creates files to debug and log processes, and finally sends notifications on both the terminal and Slack, all while following best practices.*

## Essentials:
- An Ubuntu server
- Basic Git Knowledge
- Basic Linux Knowledge
- A terminal user with sudo privileges.

## Procedures:
I would walk you through the logic flow of the script, lets take them in sections. 

### 1. Initiation Setup Section
```shell
#!/bin/bash
set -e
# Define log and secure password files
LOG_FILE="/var/log/user_management.log"
SECURE_PASSWORD_FILE="/var/secure/user_passwords.txt"
SECURE_PASSWORD_CSV="/var/secure/user_passwords.csv"
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/WandesDummySlack/webhook/url"

# Define custom exit codes
E_INVALID_INPUT=10
E_USER_CREATION_FAILED=20
E_GROUP_CREATION_FAILED=30
E_ADD_USER_TO_GROUP_FAILED=40

# Define resource limits
ulimit -t 60  # CPU time limit in seconds
ulimit -v 1000000  # Virtual memory limit in kilobytes

sudo mkdir -p /var/log
sudo mkdir -p /var/secure
sudo touch "$LOG_FILE"
sudo touch "$SECURE_PASSWORD_FILE"
sudo touch "$SECURE_PASSWORD_CSV"
sudo chmod 600 "$SECURE_PASSWORD_FILE"
sudo chmod 600 "$SECURE_PASSWORD_CSV"
```
Valid bash scripts begin with `#!/bin/bash`, its called a **shebang** or **hashbang** and it tells the OS what interpreter to use. `set -e` is a personal default line of mine for exiting a script the moment an error occurs. The next lines are **variables** and they store the value of the intended files and urls. I have 3 files that log every action, store user passwords, and my **slack webhook url for notifications** once a user has been onboarded successfully. Next lines are **exit codes**, **timeouts** and **resource limits** I defined in the script for easy debugging, hanging runs and resource optimization.
The last set of lines in this stage show all absolute file paths and their corresponding files, which would be created and properly secured with permissions. The 600 in `chmod 600` indicates that only the owner has read and write rights on the file; others have zero access.
### 2. Functions Section
```shell
# Function to log messages to the log file
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | sudo tee -a "$LOG_FILE" > /dev/null
}

# Function to send notifications to Slack
send_slack_notification() {
    local message=$1
    curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"${message}\"}" "$SLACK_WEBHOOK_URL"
}

# Function to generate a random password of length 12
generate_random_password() {
    < /dev/urandom tr -dc A-Za-z0-9 | head -c10
}

# Function to validate input format for usernames and groups
validate_input() {
    local username=$1
    local groups=$2

    if [[ -z "$username" || -z "$groups" ]]; then
        log "Error: Invalid input. Usernames and groups are required."
        send_slack_notification "Invalid input provided. Usernames and groups are required."
        exit $E_INVALID_INPUT
    fi
}
```
Function definitions are placed at the beginning to ensure they are available when called later in the script. As the comments on each function describes, these functions do different things. The first uses the stored message and webhook variables to make up the body of the `curl` it would make to **Slack Channel** in json format, the second generates a 10-character password piped from random 1–10 and A–Z alphanumeric characters; and the third uses the `if fi` statement to validate the username and group name from the text file input. It also logs an error for this step if it occurs.
```shell
# Function to create a user and set up their home directory
create_user() {
    local username=$1
    local password=$2

    # Check if the user already exists
    if id "$username" &>/dev/null; then
        log "User $username already exists. Skipping user creation."
    else
        log "Creating user $username."
        # Attempt to create the user with a timeout
        timeout 10 sudo useradd -m -s /bin/bash "$username" || {
            log "Failed to create user $username."
            send_slack_notification "Failed to create user $username."
            exit $E_USER_CREATION_FAILED
        }
        # Set the user's password and home directory permissions
        echo "$username:$password" | sudo chpasswd
        sudo chmod 700 "/home/$username"
        sudo chown "$username:$username" "/home/$username"
        log "User $username created successfully with password $password."
        echo "$username:$password" | sudo tee -a "$SECURE_PASSWORD_FILE" > /dev/null
        echo "$username,$password" | sudo tee -a "$SECURE_PASSWORD_CSV" > /dev/null
    fi
}

# Function to create a group
create_group() {
    local groupname=$1

    # Check if the group already exists
    if getent group "$groupname" &>/dev/null; then
        log "Group $groupname already exists."
    else
        log "Creating group $groupname."
        # Attempt to create the group with a timeout
        timeout 10 sudo groupadd "$groupname" || {
            log "Failed to create group $groupname."
            send_slack_notification "Failed to create group $groupname."
            exit $E_GROUP_CREATION_FAILED
        }
        log "Group $groupname created successfully."
    fi
}
```
The two functions above for creating users and groups use an `if, else, fi` statement to process their logic. The first has the username validated variable from prevoius function being used to  **verify existing user** before executing the add user command with a **timeout** to prevent lag, then it sets the user's password by piping to `chpasswd`. It then goes ahead to secure a `chmod 700` permission for restrictive access for the home directory. The second for the group is quite similar, just that it is a group being created this time, not a user. Both functions have a log line to log errors as well.
### 3. Onboarding Section
```shell
onboard_user() {
    local username=$1
    local groups=$2

    # Validate the input format
    validate_input "$username" "$groups"

    # Generate a random password for the user
    local password=$(generate_random_password)

    # Create the user with the generated password
    create_user "$username" "$password"

    # Create a personal group for the user
    create_group "$username"

    # Add the user to their personal group
    add_user_to_group "$username" "$username"

    # Process and add the user to the specified groups
    IFS=',' read -ra group_array <<< "$groups"
    for group in "${group_array[@]}"; do
        group=$(echo "$group" | xargs)  # Trim whitespace from group name
        create_group "$group"
        add_user_to_group "$username" "$group"
    done
    # Notify terminal that user has been successfully onboarded
    echo "User $username has been successfully onboarded with groups: $groups"
}
```
As clearly explained in the comments, this function effectively combines the functionalities of the previous functions, more like the definer. It loops through the Input validation function, password generation function, user creation function, personal group creation function, and adding user to group function. It particularly splits the groups into an array using a `comma delimeter (IFS=',')`, loops through again and **trims any trailing white space** before calling the functions to do their duty.
The last line of this section would send an **output message to the terminal** each time a user has been successfully onboarded. A slack message also goes to the defined **Slack Channel** for the same purpose.
### 4. Script Execution Section
```shell
# Check if the script argument is provided
if [[ $# -ne 1 ]]; then
    echo "Usage: $0 <users_file>"
    exit 10  # Invalid input exit code
fi

# Read from the provided text file
users_file="$1"

# Read from the input file and process each line
while IFS=';' read -r username groups; do
    # Remove leading and trailing whitespaces
    username=$(echo "$username" | xargs)
    groups=$(echo "$groups" | xargs)
    onboard_user "$username" "$groups"
done < "$users_file"
```
Now that we have ensured all functions and necessary configurations are defined and ready to be used when processing the input file, this last part checks if the script argument (input file) is provided. If not, it exits with an error message. It also validates the absence of white space and then processes the user data from a file. In summary, this section executes all previously prepared functions with the input data.
### 5. Usage:
- Save the entire script as a whole with the name `create_users.sh` or whatever name you like. The whole script can be found at my [github repository](https://github.com/wandexdev/Linux-User-Management-Automation-with-Bash-Scripting). 
- Assemble the input file; the argument formatted this way: usernames are differentiated by a semicolon, and groups are differentiated by a comma.
```txt
alice; admin,dev,qa
bob; prod
carol; test,dev,prod
tunde; pilot,prod,test,dev
tade; pilot,dev
```
- Save the file as `text.txt` or your preferred name
On the ubuntu terminal, run `chmod +x create_users.sh` to ensure the file is executable.
- Run script with `sudo` via `sudo bash create_users.sh text.txt` 
What we eventually get are success messages like those shown in Figure 1 below:
<figure>
  <img
    src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86trbixmrn049ltgbe8z.PNG"
    alt="Terminal window displaying a green success message"
  >
  <figcaption>Figure 1: Success Messages Displayed on Terminal</figcaption>
</figure>

### 6. Conclusion
In conclusion, this blog post has covered the automation of linux user mangement for new staff. By following these steps and leveraging bash scripting, you can effectively do the automations and improve efficiency by 100%. Now it's your turn! Try out these techniques and share your experiences in the comments below. Additionally, if you have any questions, feel free to leave a comment or reach out to me directly on [Twitter](https://twitter.com/wandexdev). 
Lastly, I would love to appreciate the team at [HNG internship](https://hng.tech/internship) for doing an awesome job at impacting knowledge. The [team](https://hng.tech/hire) is a home of talents. Kudos