1. Overview

Ansible is a powerful IT automation tool that simplifies the management of complex infrastructure, including user and password administration. When working with several servers, it can be very difficult and time-consuming to manually add new users and passwords. So, instead of logging into all servers by hand, we can leverage Ansible automation capability to automate these tasks on our behalf.

In this tutorial, we’ll see how to add users and passwords using Ansible.

2. Environment Setup

Before we get into various ways of managing users, let’s ensure we’ve got the necessary tools and prerequisites in place:

  • controller node with Ansible installation
  • client node as the remote node to test the create user functionality
  • inventory file configured with hosts
  • SSH connectivity between the control node and client nodes

With our setup ready, we can move forward with this tutorial.

3. The user Module

Ansible’s user module is the primary tool for managing users on target hosts. Specifically, it provides a comprehensive set of options:

  • name (required): the name of the user
  • state: defines the desired state of the user account
  • password: specifies the password for the user
  • home: the home directory of the user
  • shell: the user’s login shell
  • groups: list of supplementary groups for the user
  • ssh_key_bits: number of bits in the SSH key
  • ssh_key_file: the file path where the SSH key will be stored

In addition to the ones above, we can employ many other options with the user module:

  • seuser: sets the security context for the user account
  • update_password: specifies the password update criteria
  • profile: specifies the user’s profile

In general and in this tutorial, we use the user module for managing users on the client node.

4. Adding User

Particularly, there are two main methods for adding users using Ansible:

Let’s see the use cases for each of the methods.

4.1. Using Ad-Hoc Commands

Ad-hoc commands provide a rather quick and easy way to execute Ansible tasks directly from the command line.

For instance, let’s take an example of an ad-hoc command to create a user named Baeldung-CS:

$ ansible -i project_inventory.ini client1 -m user -a "name=Baeldung-CS state=present createhome=yes" -b
192.168.221.171 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1006,
    "home": "/home/Baeldung-CS",
    "name": "Baeldung-CS",
    "shell": "/bin/sh",
    "state": "present",
    "system": false,
    "uid": 1005
}

Let’s see a breakdown of the options used here:

  • project_inventory.ini specified with the -i option is our inventory file
  • -m invokes the user module
  • -b or –become causes the task to execute with elevated privileges

Further, the -a flag passes the entire string inside the quotes as a single argument to ansible. The user module uses certain options to create the user:

  • name: name of the user (Baeldung-CS)
  • state: state set to present (meaning Ansible should ensure the user exists)
  • createhome: create the home directory for the user account

Finally, the json format output of the command shows the new user’s details. For example, the user shell is /bin/bash, and the home directory is /home/Baeldung-CS. Also, the gid and uid are set to 1006 and 1005 respectively.

Furthermore, to verify whether the user was created with the given details, we switch to the client node. We then list the home directory contents with the ls command:

$ ls /home
vagrant  Baeldung-CS 

Thus, the target user is created on the client node.

4.2. Using Playbook

Playbooks are YAML files that define a sequence of tasks to be executed on target hosts.

Let’s now see how to use a playbook to create a user on the remote node. First, we compile a playbook to create a user named baeldung:

$ cat add_user.yml 
---
- name: Create a user 
  hosts: client1
  become: yes
  tasks:
  - name: Add user baeldung
    user:
      name: baeldung
      shell: /bin/bash
      home: /home/baeldung

Notably, the above playbook sets the shell and home directory for the user. Let’s run it:

$ ansible-playbook -i project_inventory.ini add_user.yml
PLAY [Create a user] ***********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.221.171]
TASK [Add user baeldung] *******************************************************
changed: [192.168.221.171]
PLAY RECAP *********************************************************************
192.168.221.171            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Moreover, to verify the user creation, we switch to the client node and list the home directory contents with ls:

$ ls /home/
vagrant baeldung

Finally, the user is created on the client with its home directory.

5. Adding a User to a Group

User management generally involves assigning users to specific groups. Ansible simplifies this process within the playbook.

Let’s start by creating a group and adding the above user, baeldung, to it:

$ cat add_grp.yml
---
- name: Create a group and add a user
  hosts: client1
  become: yes
  tasks:
    - name: Ensure the group exists
      group:
        name: developers
        state: present
    - name: Add user to the group
      user:
        name: baeldung
        groups: developers
        append: yes

In this playbook, the first task ensures the group developers exists. Then, the second task adds the user baeldung to the developers group using the user module.

Let’s run the playbook:

$ ansible-playbook -i project_inventory.ini add_grp.yml
PLAY [Create a group and add a user] *******************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.221.171]
TASK [Ensure the group exists] *************************************************
changed: [192.168.221.171]
TASK [Add user to the group] ***************************************************
changed: [192.168.221.171]
PLAY RECAP *********************************************************************
192.168.221.171            : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

To check if the given user is added to the new group, we first switch to the client1 node. Then, we run the groups command to see the groups associated with the user baeldung:

$ groups baeldung
baeldung : baeldung developers

Similarly, we can add a user to common groups like sudo.

6. Adding Password

Securing user accounts is paramount, and the Ansible user module provides straightforward methods for managing passwords.

6.1. Using mkpasswd

The password option enables us to specify the encrypted password directly in the Ansible task. Generally, encrypted passwords can be generated in different ways. For example, the mkpasswd utility is a common option for this.

The whois package provides the mkpasswd utility. Let’s install it using the apt package manager:

$ sudo apt install whois

Now, let’s see how to use the mkpasswd command.

6.2. Adding Password Using Ad-Hoc Command

We can use the mkpasswd utility directly with an Ansible ad-hoc command:

$ ansible -i project_inventory.ini client1 -m user -a "name=Baeldung-CS password=$(mkpasswd --method=sha-512 '123')" --become
192.168.221.171 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "append": false,
    "changed": true,
    "comment": "",
    "group": 1006,
    "home": "/home/Baeldung-CS",
    "move_home": false,
    "name": "Baeldung-CS",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/sh",
    "state": "present",
    "uid": 1005
}

Here, in the above example, we set the password of user Baeldung-CS on client1 to 123. Moreover, to verify the set password, we can try logging in with the new password:

$ su - Baeldung-CS
Password: 
$ 

At this point, the new password should be in effect.

6.3. Adding Password Using Playbook

If we wish to use mkpasswd with a playbook, we can use it to first generate the hash of a password on the command line:

$ mkpasswd --method=sha-512
Password:
$6$r6pnvoHc$gpG9kpqIbQfGL2o/NsTD/uN04OsZ15zAydVFPYkflnbSWCTTUBn9yC6IJb7MoRvzQqmadKuf.GEYv8ldQrlZO1

We can then place the obtained hashed password inside the playbook:

$ cat play_pass1.yml
---
- name: Create a password for user
  hosts: client1
  become: yes
  tasks:
    - name: Create a password for user baeldung
      user:
        name: baeldung
        password: '$6$2OCdN3heBd$5swl1SfNkeiKd7n/WwZl/FpvhH4VJANl4u9j8kEA9gEjfl5lrqqQibiH2JU2rxW./Za3sp3BS2FabhTuTEAOQ.'

Let’s run the above playbook:

$ ansible-playbook -i project_inventory.ini play_pass1.yml
PLAY [Using Playbook] *****************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.221.171]
TASK [Create a password for user] **********************************************
changed: [192.168.221.171]
PLAY RECAP *********************************************************************
192.168.221.171            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Afterward, we should be able to log in with the new password on client1:

$ su - Baeldung-linux
Password:
$ pwd
/home/Baeldung-linux
$

Also, we can directly use the password_hash command to encrypt the password in the playbook:

$ cat play_pass2.yml
---
- name: Create a password for user
  hosts: client1
  become: yes
  tasks:
    - name: Create a password for user baeldung
      user:
        name: baeldung
        password: "{{ '1234' | password_hash('sha512') }}"

Let’s again run the playbook:

$ ansible-playbook -i project_inventory.ini pass_playbook1.yml 
PLAY [Create a password for user] **********************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.221.171]
TASK [Create a password for user baeldung] *************************************
changed: [192.168.221.171]
PLAY RECAP *********************************************************************
192.168.221.171            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Thus, both approaches enable us to log in with the new password.

7. Adding Remote Access Capability

Ansible simplifies the setup of remote access for users over SSH. For example, we can set passwordless access for our remote user on client1.

First, we create a playbook:

$ cat user_ssh.yml
---
- name: Create user, SSH directory, and transfer SSH keys
  hosts: client1
  become: yes  # Use become to run tasks as a privileged user (e.g., sudo)
  tasks:
    - name: Create SSH directory for baeldung
      file:
        path: /home/baeldung/.ssh
        state: directory
        owner: baeldung
        group: baeldung
        mode: 0700
    - name: Generate SSH key for baeldung
      user:
        name: baeldung
        generate_ssh_key: yes
        ssh_key_type: rsa
        ssh_key_bits: 4096
        ssh_key_file: /home/baeldung/.ssh/id_rsa  # Full path is needed here
    - name: Transfer public key to the target host
      authorized_key:
        user: baeldung
        key: "{{ lookup('file', '/home/vagrant/.ssh/id_rsa.pub') }}" 

Now, let’s break down the first play:

  • file module creates the /home/baeldung/.ssh directory
  • /home/baeldung/.ssh is set to be owned by baeldung with restricted permissions (mode: 0700)

Similarly, the second play performs two main tasks:

  • generates an SSH key for the baeldung user using the user module
  • the key is stored at /home/baeldung/.ssh/id_rsa

Finally, the third play has the final action:

  • uses the authorized_key module to transfer the public key to the target host
  • public key is sourced from the local machine, specifically from the file /home/vagrant/.ssh/id_rsa.pub

To modify the authorized_keys file of the remote user, baeldung, we run the above playbook:

$ ansible-playbook -i project_inventory.ini ssh_user.yml 
PLAY [Create user, SSH directory, and transfer SSH keys] ***********************************************
TASK [Gathering Facts] *********************************************************************************
ok: [192.168.221.171]
TASK [Create SSH directory for baeldung] ***************************************************************
changed: [192.168.221.171]
TASK [Generate SSH key for baeldung] *******************************************************************
changed: [192.168.221.171]
TASK [Transfer public key to the target host] **********************************************************
changed: [192.168.221.171]
PLAY RECAP *********************************************************************************************
192.168.221.171            : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Let’s now try to log in to the user baeldung from the controller node:

$ ssh [email protected]
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-110-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
This system is built by the Bento project by Chef Software
More information can be found at https://github.com/chef/bento
Last login: Tue Nov 21 06:56:07 2023 from 192.168.221.163
baeldung@client1:~$ 

Consequently, this time we don’t need a password to log in.

8. Conclusion

In this article, we discussed many ways to work with user management in Ansible.

First, we saw how to add a user on the remote machine using ad-hoc commands as well as with the playbook way. Further, we added the created user to a new group. Then, we secure the remote user by appending it with an encrypted password. Finally, we set up passwordless SSH access for the remote user from the controller node.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.