Bash Script - Ansible User Setup
Info
To get my existing Linux servers ready for Ansible, I put together a bash script that creates a specific Ansible user with sudo privileges and adds the SSH public key from the user on the Ansible control node to the target systems, which are going to be my managed nodes.
I run the script on my workstation, which already has the SSH auth keys I need to connect to the remote machines. The target systems are running Debian or Ubuntu. By default, Debian doesn’t use sudo, but I had it already installed. Here’s a quick overview of what the script is doing:
- It checks the hostname and SSH public key passed when the script is invoked.
- The first parameter is for the SSH connection. I just need to enter the alias name I have defined in my local SSH config.
- The second parameter is the public SSH key, which I want to add to the Ansible users
authorized_keys
file.
- The “Define Remote Commands” block contains the commands to be executed on the remote system.
- Creates a new user called
ansible
. - Creates a
.ssh
directory and adds the public key to the authorized keys file. - Creates a
ansible
sudoers file withvisudo
and adds the defined permissions.
- Creates a new user called
- Connects to the remote host and executes remote commands.
- In general, the script checks whether the user, directories or files exist and creates them if necessary.
The created Ansible user has no password and can only connect via ssh with the provided auth key. For my lab environment I added full sudo permissions, ansible ALL=(ALL) NOPASSWD: ALL
, for testing purposes only and this should not be used in productive environments. The Ansible user should only have as many permissions as needed - Principle of least privilege.
Restricted permissions for basic file, package and service management could look like this:
ansible ALL=(ALL) NOPASSWD: /bin/cp, /bin/mv, /bin/rm, \
/bin/mkdir, /usr/bin/chmod, \
/usr/bin/chown, /usr/bin/apt, \
/usr/bin/apt-get, /usr/bin/apt-cache, \
/usr/bin/systemctl, /bin/systemctl
Then, just make it more granular for specific roles, directories, files, services and packages.
The sudoers file is created with visudo
, which checks the syntax and makes sure everything’s correct before it saves the changes. It also locks the file so that you can’t make changes at the same time. On addition to that, the scripts also create a specific sudoers file, rather than using the default one. This is another way of making sure you can’t break existing sudo configurations and accidentally lock yourself out of your systems. It’s also a more structured way to handle sudo permissions, which is especially useful when you have multiple sudo users on the system.
I don’t use this script for new machines. Instead, I will use a preseed (Debian) or autoinstall (Ubuntu) file, as well as a nice Packer configuration and a deployment setup with Terraform. Once that’s done, the machines are ready to be configured with Ansible.
Usage
- Save the script as
setup-ansible-user.sh
- Make the script executable:
chmod +x setup-ansible-user.sh
- Run the script:
./setup-ansible-user.sh <hostname> <public_key_file>
Resources
Bash script
#!/bin/bash
# --------------------------------------------------------------------------------
# Script Name: setup-ansible-user.sh
# Author: m0x2A
# Date: 2024-08-19
# Version: 1.0
# Description: This script creates a user called 'ansible' on a remote host
# with sudo privileges. It also adds a specified public key
# to the ansible user's SSH authorized keys.
# License: This script is open-source and distributed under the MIT License.
# Usage: ./setup-ansible-user.sh <hostname> <public_key_file>
# --------------------------------------------------------------------------------
# Trap to handle script interruption
trap "echo 'Script interrupted'; exit 1" SIGINT SIGTERM
# Check if a hostname is passed as the first parameter
if [ -z "$1" ]; then
echo "Error: No hostname provided."
echo "Usage: $0 <hostname> <public_key_file>"
exit 1
fi
# Check if a public key file is passed as the second parameter
if [ -z "$2" ]; then
echo "Error: No public key file provided."
echo "Usage: $0 <hostname> <public_key_file>"
exit 1
fi
# Extract the hostname and public key file from the parameters
HOSTNAME="$1"
PUBLIC_KEY_FILE="$2"
# Set variables
USERNAME="ansible"
SUDOERS_FILE="/etc/sudoers.d/$USERNAME"
SUDO_PERMISSIONS="$USERNAME ALL=(ALL) NOPASSWD: ALL"
# Ensure the public key file exists
if [ ! -f "$PUBLIC_KEY_FILE" ]; then
echo "Error: Public key file '$PUBLIC_KEY_FILE' not found."
exit 1
fi
# Read the public key from the file
PUBLIC_KEY=$(cat "$PUBLIC_KEY_FILE")
# Define the commands to be executed on the remote host
REMOTE_COMMANDS=$(cat << EOF
#!/bin/bash
# Create a new user, add SSH public key and give it sudo permissions
# Check if the user already exists
if id -u "$USERNAME" &>/dev/null; then
echo "User '$USERNAME' already exists."
else
echo "Creating the user '$USERNAME'..."
# Create the user with a home directory and bash shell
sudo useradd -m -s /bin/bash "$USERNAME"
# Create .ssh directory and set correct permissions
sudo mkdir -p /home/$USERNAME/.ssh
sudo chmod 700 /home/$USERNAME/.ssh
sudo chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
echo "User '$USERNAME' and .ssh directory successfully created."
fi
# Ensure .ssh directory exists
if [ ! -d /home/$USERNAME/.ssh ]; then
sudo mkdir -p /home/$USERNAME/.ssh
sudo chmod 700 /home/$USERNAME/.ssh
sudo chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
fi
# Check if authorized_keys file exists, create if it doesn't
if [ ! -f /home/$USERNAME/.ssh/authorized_keys ]; then
sudo touch /home/$USERNAME/.ssh/authorized_keys
sudo chmod 600 /home/$USERNAME/.ssh/authorized_keys
sudo chown $USERNAME:$USERNAME /home/$USERNAME/.ssh/authorized_keys
fi
# Check if the public key is already in the authorized_keys file else add it
if sudo grep -qxF "$PUBLIC_KEY" /home/$USERNAME/.ssh/authorized_keys; then
echo "Public key is already present in /home/$USERNAME/.ssh/authorized_keys"
else
echo "$PUBLIC_KEY" | sudo tee -a /home/$USERNAME/.ssh/authorized_keys > /dev/null
echo "Public key added to /home/$USERNAME/.ssh/authorized_keys."
fi
# Create a sudoers file for the 'ansible' user permissions
if [ ! -f "$SUDOERS_FILE" ]; then
echo "Creating sudoers file for '$USERNAME'..."
# Use visudo to safely add the sudo rule
echo "$SUDO_PERMISSIONS" | sudo EDITOR='tee' visudo -f "$SUDOERS_FILE" > /dev/null
echo "Sudoers file created and permissions set."
else
echo "Sudoers file for '$USERNAME' already exists."
fi
EOF
)
# Print the remote commands for debugging
#echo "Executing remote commands:"
#echo "$REMOTE_COMMANDS"
# Execute the commands on the remote host
ssh -t "$HOSTNAME" "$REMOTE_COMMANDS"
# Check if the SSH connection and command execution were successful
if [ $? -ne 0 ]; then
echo "Error: Connection to $HOSTNAME failed or script could not be executed."
exit 1
fi
echo "Script executed successfully on $HOSTNAME."