Synopsis
As a 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
#!/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
# 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.
# 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
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
# 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. - Get your input file, which is the argument formatted this way: usernames are differentiated by a semicolon, and groups are differentiated by a comma.
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
viasudo bash create_users.sh text.txt
What we eventually get are success messages like those shown in Figure 1 below:
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.
Lastly, I would love to appreciate the team at HNG internship for doing an awesome job at impacting knowledge. The team is a home of talents. Kudos
Discover more from Coursity
Subscribe to get the latest posts sent to your email.