Add Users and Groups using Ansible User Module

Ansible User Module

One of the fundamental tasks in server administration is managing user accounts and permissions. The Ansible user module is used to add user and group accounts on remote systems. It allows administrators to create, modify, and delete user accounts, set or change passwords, manage user group memberships, and configure various user-related settings.

This guide provides an in-depth look at how to use the Ansible user module to add users and groups efficiently.

Basic Syntax of Ansible User Module

Before diving into specific use cases, it’s essential to understand the basic syntax of the Ansible user module. The module has several parameters that allow you to define user attributes such as name, password, group, and state.

Here are the available options for the user module.

  • name: The username to manage.
  • state: The state of the user account. Possible values are present and absent.
  • comment: The GECOS field (usually full name).
  • createhome: Whether to create the user’s home directory. Default is yes.
  • home: The path to the user’s home directory.
  • shell: The user’s login shell.
  • system: Whether the user is a system account.
  • uid: The user’s UID.
  • gid: The user’s primary group.
  • groups: A list of groups the user belongs to.
  • append: Whether to append the user to the groups specified.
  • password: The user’s password (hashed).
  • update_password: Whether to update the password if the user exists. Possible values are always and on_create.
  • remove: Whether to remove the user’s home directory and mail spool when the user is removed.
  • move_home: Whether to move the user’s home directory.
  • force: Whether to remove directories associated with the user if they are not empty.
  • ssh_key: The path to the user’s SSH key.
  • profile: The user’s profile settings.

Example Usage

Here is a basic example of adding a user with several attributes:

- name: Manage user accounts
  user:
    name: exampleuser
    state: present
    comment: "Example User"
    createhome: yes
    home: "/home/exampleuser"
    shell: "/bin/bash"
    groups: "wheel,developers"
    append: yes
    password: "{{ 'password' | password_hash('sha512') }}"
    uid: 1050
    gid: 1050
    ssh_key: "/home/exampleuser/.ssh/id_rsa.pub"

This playbook creates a user account named exampleuser, sets a comment (“Example User”), creates a home directory at /home/exampleuser, assigns the Bash shell (/bin/bash), and adds the user to the wheel and developers groups.

The append: yes option adds these groups without removing existing ones. It also sets a hashed password, specifies the user ID (uid) and group ID (gid) as 1050, and associates an SSH key located at /home/exampleuser/.ssh/id_rsa.pub with the account.

Add a User Using Ad-hoc Command

Ad-hoc commands in Ansible are helpful for quick, one-off tasks. To add a user using an ad-hoc command, you can use the following syntax:

 # ansible all -m user -a "name=newuser state=present createhome=yes" -b

Output.

add user using ansible

This command adds a user named newuser to all the hosts in your inventory. The -b flag indicates that the command should be executed with root privileges on the remote host.

Create a User Using Playbook

For more complex and repeatable tasks, creating a playbook is the best approach. Below is an example playbook to add a user:

---
- name: Add a new user
  hosts: all
  tasks:
    - name: Add user 'newuser'
      user:
        name: newuser
        state: present

Create a User with a Specific UID and Home Directory

Sometimes, you need to create a user with a specific User ID (UID) and assign a custom home directory. Below is an example playbook that creates a user with a specific UID and assigns a custom home directory.

---
- name: Create a user with a specific UID and home directory
  hosts: all
  tasks:
    - name: Add user 'customuser' with a specific UID and home directory
      user:
        name: customuser
        state: present
        uid: 1500
        home: "/opt/customuser"
        createhome: yes
        shell: "/bin/bash"
        comment: "Custom User with specific UID"

The above playbook creates a customuser with UID 1500 and home directory /opt/customuser

Removing a User Using Playbook

To remove a user using an Ansible playbook, you can use the user module with the state parameter to absent.

---
- name: Remove a user
  hosts: all
  become: true
  tasks:
    - name: Remove the user
      user:
        name: newuser
        state: absent
        remove: yes

This playbook removes a user newuser with their home directory from all remote hosts specified in the inventory file.

Add Multiple Users Using Playbook

When managing multiple users, you can utilize a loop in your playbook to streamline the process. This approach ensures you can efficiently add, update, or manage several user accounts.

Here is an example playbook that creates multiple users on all remote hosts.

---
- name: Add multiple users
  hosts: all
  vars:
    users:
      - { name: 'user1', password: '{{ "password1" | password_hash("sha512") }}', groups: 'developers' }
      - { name: 'user2', password: '{{ "password2" | password_hash("sha512") }}', groups: 'developers' }
      - { name: 'user3', password: '{{ "password3" | password_hash("sha512") }}', groups: 'developers' }
  tasks:
    - name: Add users
      user:
        name: "{{ item.name }}"
        password: "{{ item.password }}"
        groups: "{{ item.groups }}"
        append: yes
      loop: "{{ users }}"

Explanation:

  • vars: Defines a list of users with their respective attributes.
  • loop: Iterates over each user in the users list to add them to the system.

Create a Group

To create a group, you can use the ansible.builtin.group module.

---
- name: Create a group on the target host
  hosts: all
  become: true
  tasks:
    - name: Create a group named "devops"
      ansible.builtin.group:
        name: developers
        state: present

Explanation:

  • name: developers specifies the name of the group.
  • state: present ensures the group is created if it doesn’t exist.

Add a User to a Group

Often, users need to be part of specific groups to have the necessary permissions. The following example demonstrates how to add a user to a group:

---
- name: Add a user to a group
  hosts: all
  tasks:
    - name: Add user 'newuser' to 'developers' group
      user:
        name: newuser
        groups: developers
        append: yes

This Ansible playbook adds the user newuser to the developers group on all target hosts defined in the inventory. The append: yes parameter ensures that the user is added to the specified group without removing them from other groups.

Add User to Multiple Groups

In some cases, users need to belong to multiple groups to have the necessary permissions to access different resources on a server. In this case, you can specify multiple groups as a comma-separated list.

Below is an example playbook that adds a user named multigroupuser to multiple groups named developers, admins, andqa.

---
- name: Add user to multiple groups
  hosts: all
  tasks:
    - name: Add user 'multigroupuser' to multiple groups
      user:
        name: multigroupuser
        state: present
        groups: "developers,admins,qa"
        append: yes

Add a User to Sudoers

To grant a user administrative privileges, you can add them to the sudoers file. This can be achieved by adding the user to the sudo group:

---
- name: Add a user to sudoers
  hosts: all
  tasks:
    - name: Add user 'newuser' to 'sudo' group
      user:
        name: newuser
        groups: sudo
        append: yes

This Ansible playbook adds the user newuser to the sudo group on all target hosts specified in the inventory.

Add a Password Using Ad-Hoc Command

To set a password for a user using an ad-hoc command, you need to hash the password using a supported hash algorithm. This ensures that the password is stored securely.

First, generate a hashed password using Python or an online tool:

 # python3 -c "import crypt; print(crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)))"

This will output a hashed password string.

$6$CG0/PrF24h.YsF7M$aeUdWeWUdSyH8ZmfV5nHobt5fzoPUD65d6jc4uk4G8UHE3gL/0KKkmccSsfA2ZdQFYYKRQMdmAFDk3lI47v270

Now, use the above password in the ad-hoc command to set it for newuser.

 # ansible all -m user -a "name=newuser password='$6$CG0/PrF24h.YsF7M$aeUdWeWUdSyH8ZmfV5nHobt5fzoPUD65d6jc4uk4G8UHE3gL/0KKkmccSsfA2ZdQFYYKRQMdmAFDk3lI47v270'"

Add a Password Using Playbook

When using a playbook to set a user’s password, you can use the password parameter with a hashed password:

The following playbook sets a password for newuser.

---
- name: Set password for user
  hosts: all
  tasks:
    - name: Set password for 'newuser'
      user:
        name: newuser
        password: "{{ 'password' | password_hash('sha512') }}"

Set a Passwordless Access for Remote User

For scenarios where passwordless SSH access is required, you can use the authorized_key module to copy the public key to the user’s ~/.ssh/authorized_keys file. This allows the user to log in without needing to enter a password.

---
- name: Set up passwordless SSH access
  hosts: all
  tasks:
    - name: Ensure the user 'newuser' exists
      user:
        name: newuser
        state: present

    - name: Create .ssh directory if it doesn't exist
      file:
        path: /home/newuser/.ssh
        state: directory
        mode: '0700'
        owner: newuser
        group: newuser

    - name: Generate SSH key for newuser
      user:
        name: newuser
        generate_ssh_key: yes
        ssh_key_type: rsa
        ssh_key_bits: 4096
        ssh_key_file: /home/newuser/.ssh/id_rsa

    - name: Add authorized key for 'newuser'
      authorized_key:
        user: newuser
        state: present
        key: "{{ lookup('file', '/home/newuser/.ssh/id_rsa.pub') }}"

    - name: Ensure correct permissions on authorized_keys
      file:
        path: /home/newuser/.ssh/authorized_keys
        state: file
        mode: '0600'
        owner: newuser
        group: newuser

Explanation:

  • Ensure the user ‘newuser’ exists: This task ensures that the user newuser is present on the system.
  • Create .ssh directory if it doesn’t exist: This task creates the .ssh directory in the user’s home directory with the appropriate permissions.
  • Generate SSH key for newuser: This task generates an SSH key pair for newuser using the Ansible user module. The key type is RSA, and the key length is 4096 bits.
  • Add authorized key for ‘newuser’: This task adds the public key to the user’s authorized_keys file, enabling passwordless SSH login.
  • Ensure correct permissions on authorized_keys: This task ensures that the authorized_keys file has the correct permissions and ownership.

Real-World Use Case

You are a DevOps engineer in a large organization with multiple environments (development, staging, and production). Each environment has specific requirements for user accounts, permissions, and SSH access. You need to manage user accounts across all these environments, ensuring that each environment adheres to its security and operational policies. Additionally, you need to enforce different access levels and permissions for different teams and environments.

Objectives:

  • Add and remove users based on team membership.
  • Assign appropriate groups and permissions for each environment.
  • Set up SSH key-based authentication for secure access.
  • Ensure different levels of sudo access for different environments.
  • Automate user deprovisioning when a user leaves the team.

Step 1: Define the Users and Environments

Create a YAML file named users.yml to define users, and their SSH keys

users:
  - name: alice
    teams: ['dev', 'qa']
    ssh_key: "ssh-rsa AAAAB3Nza... alice@example.com"
  - name: bob
    teams: ['dev']
    ssh_key: "ssh-rsa AAAAB3Nza... bob@example.com"
  - name: charlie
    teams: ['qa']
    ssh_key: "ssh-rsa AAAAB3Nza... charlie@example.com"
  - name: dave
    teams: ['dev', 'prod']
    ssh_key: "ssh-rsa AAAAB3Nza... dave@example.com"

Create another YAML file named environments.yml for environment-specific settings.

environments:
  - name: development
    users:
      dev: ['alice', 'bob', 'dave']
      qa: ['alice', 'charlie']
    sudo: ['alice', 'dave']
  - name: staging
    users:
      qa: ['alice', 'charlie']
    sudo: ['alice']
  - name: production
    users:
      prod: ['dave']
    sudo: ['dave']

Step 2: Create the Playbook

Create an Ansible playbook named manage_users.yml to manage users based on the defined YAML files.

---
- name: Manage user accounts across multiple environments
  hosts: all
  vars_files:
    - users.yml
    - environments.yml
  tasks:
    - name: Ensure users exist
      user:
        name: "{{ item.name }}"
        state: present
        generate_ssh_key: yes
        ssh_key_type: rsa
        ssh_key_bits: 4096
      loop: "{{ users }}"
      notify:
        - Add SSH key for user

    - name: Ensure user is part of required groups
      user:
        name: "{{ item }}"
        groups: "{{ groups }}"
        append: yes
      loop: "{{ environment_users }}"
      when: environment_users is defined

    - name: Add users to sudoers if required
      lineinfile:
        path: /etc/sudoers.d/{{ item }}
        state: "{{ 'present' if item in sudo_users else 'absent' }}"
        create: yes
        line: "{{ item }} ALL=(ALL) NOPASSWD:ALL"
        validate: 'visudo -cf %s'
      loop: "{{ environment_users }}"
      when: sudo_users is defined

    - name: Remove users not in the current environment
      user:
        name: "{{ item }}"
        state: absent
        remove: yes
      loop: "{{ removed_users }}"
      when: removed_users is defined

  handlers:
    - name: Add SSH key for user
      authorized_key:
        user: "{{ item.name }}"
        state: present
        key: "{{ item.ssh_key }}"
      loop: "{{ users }}"
  vars:
    environment_users: "{{ environments | selectattr('name', 'equalto', ansible_hostname) | map(attribute='users') | first }}"
    sudo_users: "{{ environments | selectattr('name', 'equalto', ansible_hostname) | map(attribute='sudo') | first }}"
    removed_users: "{{ users | map(attribute='name') | difference(environment_users | map(attribute='dev') | list | union(environment_users | map(attribute='qa') | list | union(environment_users | map(attribute='prod') | list))) }}"

Explanation:

  • Ensure Users Exist: This task ensures that all users defined in users.yml exist on the system, with SSH keys generated.
  • Ensure User is Part of Required Groups: This task assigns users to the appropriate groups for the current environment (e.g., dev, qa, prod).
  • Add Users to Sudoers if Required: This task adds users to the sudoers file if they require sudo privileges in the current environment.
  • Remove Users Not in the Current Environment: This task removes users who are not part of the current environment, ensuring no unauthorized access.
  • Add SSH Key for User: This handler adds the SSH public key for each user, enabling secure, passwordless login.

Step 3: Running the Playbook

Execute the playbook with the following commands, specifying the environment as an extra variable:

 # ansible-playbook -i inventory manage_users.yml -e "environment=development"
 # ansible-playbook -i inventory manage_users.yml -e "environment=staging"
 # ansible-playbook -i inventory manage_users.yml -e "environment=production"

Output.

PLAY [Manage user accounts across multiple environments] **********************

TASK [Gathering Facts] ********************************************************
ok: [server1]

TASK [Ensure users exist] *****************************************************
changed: [server1] => (item={'name': 'alice', 'teams': ['dev', 'qa'], 'ssh_key': 'ssh-rsa AAAAB3Nza... alice@example.com'})
changed: [server1] => (item={'name': 'bob', 'teams': ['dev'], 'ssh_key': 'ssh-rsa AAAAB3Nza... bob@example.com'})
changed: [server1] => (item={'name': 'charlie', 'teams': ['qa'], 'ssh_key': 'ssh-rsa AAAAB3Nza... charlie@example.com'})
changed: [server1] => (item={'name': 'dave', 'teams': ['dev', 'prod'], 'ssh_key': 'ssh-rsa AAAAB3Nza... dave@example.com'})

TASK [Ensure user is part of required groups] *********************************
changed: [server1] => (item=alice)
changed: [server1] => (item=bob)
changed: [server1] => (item=dave)

TASK [Add users to sudoers if required] ***************************************
changed: [server1] => (item=alice)
changed: [server1] => (item=dave)

TASK [Remove users not in the current environment] ****************************
changed: [server1] => (item=charlie)

RUNNING HANDLER [Add SSH key for user] ****************************************
changed: [server1] => (item={'name': 'alice', 'teams': ['dev', 'qa'], 'ssh_key': 'ssh-rsa AAAAB3Nza... alice@example.com'})
changed: [server1] => (item={'name': 'bob', 'teams': ['dev'], 'ssh_key': 'ssh-rsa AAAAB3Nza... bob@example.com'})
changed: [server1] => (item={'name': 'charlie', 'teams': ['qa'], 'ssh_key': 'ssh-rsa AAAAB3Nza... charlie@example.com'})
changed: [server1] => (item={'name': 'dave', 'teams': ['dev', 'prod'], 'ssh_key': 'ssh-rsa AAAAB3Nza... dave@example.com'})

PLAY RECAP ********************************************************************
server1                   : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Conclusion

Managing users and passwords is a critical task in system administration, and Ansible’s user module provides a robust and flexible way to handle it. Whether you’re adding users, setting passwords, or configuring permissions, Ansible makes these tasks simple and repeatable. By leveraging both ad-hoc commands and playbooks, you can ensure that your user management processes are efficient and automated.

FAQs

1. Can I create a user with a specific shell using the Ansible user module?

Yes, use the shell parameter to define the default shell for the user, such as /bin/bash or /bin/zsh.

2. How do I manage a user's SSH keys using the user module?

Use the ssh_key parameter to add SSH public keys to the user's ~/.ssh/authorized_keys file, providing easy access to the system.

3. How can I lock a user account using the Ansible user module?

Use the password_lock: yes parameter to lock the user's account, preventing them from logging in.

About Hitesh Jethva

I am Hitesh Jethva, Founder and Author at Code2DevOps.com. With over 15 years of experience in DevOps and open source technologies, I am passionate about empowering teams through automation, continuous integration, and scalable solutions.

View all posts by Hitesh Jethva