How to create a Linux User Using Ansible

In this guide we will explore how to automate the process of user creation using Ansible in LInux Systems.

Ansible is an open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code. It runs on many Unix-like systems, and can configure both Unix-like systems as well as Microsoft Windows.

Prerequisites

To follow along this guide you need the following:

  • python installed in the local system
  • Python pip installed
  • Ansible installed locally

Steps

  1. Install dependencies
  2. Generate the password using the passlib package
  3. Create the ansible playbook defining the tasks required
  4. The whole playbook

1. Install dependencies

For this to work, we need ansible and the passlib package.

Install the ansible passlib package:

sudo pip install passlib

Install ansible

sudo pip install ansible

2. Generate the password using the passlib package

Ansible will add the password as is for the user. The password is encrypted thus the default password will not work.

Get the encrypted password with this command:

python -c "from passlib.hash import sha512_crypt; import getpass; print(sha512_crypt.using(rounds=5000).hash(getpass.getpass()))"

Output:

➜ python -c "from passlib.hash import sha512_crypt; import getpass; print(sha512_crypt.using(rounds=5000).hash(getpass.getpass()))"

Password:
$6$8IAGh7Gu2ugAB.sF$zHldDAEBMoiFUPq2RsGpQ.TRwbPOz3/S5ATs5TbfWDLypYjLGfKN8SNxu1Sx5nNfzQgDJqBh37jT9ln7EIHcq0

3. Create the ansible playbook defining the tasks required

Now that all the dependencies are installed, lets define our playbook.

First you need to define the name, hosts and extra information like variables:

- name: Create user on a linux server
  hosts: remote-server
  become: yes
  gather_facts: false
  vars:
    - user: <username here>
    - password: <Password hash generated>

The next section is for tasks.

Ansible task to create a linux user

Since we want to create a user, we will use the ansible user module. Checkout more about the user module here.

This task will create a user with the supplied name and password which will be interpolated from the variables defined earlier:

- name: Create a login user
  user:
    name: "{{ user }}"
    password: "{{ password }}"
    groups:
      - wheel
    state: present

Optional: Ansible task to add public key to authorized_keys for user

To allow password less login to the server, we can add our public ssh key to the authorized keys in the server. The ansible authorized_key module is used to achieve this.

- name: Add public key to authorized_keys
  authorized_key:
    user: "{{ user }}"
    state: present
    key: "{{ lookup(&#039;file&#039;, &#039;~/.ssh/id_rsa.pub&#039;) }}"

Optional: Ansible task to deny root ssh access

The next step is to deny root login through ssh. It is a good practice to allow non root users to log in. We use the lineinfile module which will ensure that as specific line is present in the specified file. We use a regular expression to match the line.

- name: Deny root from logging in
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: &#039;^(#)?PermitRootLogin \w*$&#039;
    line: &#039;PermitRootLogin no&#039;
    state: present

Optional: Ansible task to add user to Allowed Users list

A good security practice is to allow only specified users to log in to the system. This task will do that.

- name: Allow specific users to log in
  ansible.builtin.lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: &#039;^AllowUsers&#039;
    line: &#039;AllowUsers {{ user }}&#039;
    state: present

Optional: Add user to sudoers file so they can run sudo commands:

If you want the created user to run sudo command without password prompt, include this task.

Here, we are using the lineinfile module to search a regular expression matching the user then validating that all is working as expected with the command visudo -cf %s:

- name: Add {{ user }} to sudoers file
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: &#039;^{{ user }}&#039;
    line: &#039;{{ user }} ALL=(ALL) NOPASSWD: ALL&#039;
    validate: &#039;visudo -cf %s&#039;

4. The whole playbook

This is how the whole playbook will look like. Save this content into a .yaml or .yml file. In my case create-user.yaml:

---
- name: Create user on a linux server
  hosts: remote-server
  become: yes
  gather_facts: false
  vars:
    - user: user1
    - password: $6$8IAGh7Gu2ugAB.sF$zHldDAEBMoiFUPq2RsGpQ.TRwbPOz3/S5ATs5TbfWDLypYjLGfKN8SNxu1Sx5nNfzQgDJqBh37jT9ln7EIHcq0
  tasks:
      - name: Create a login user
        user:
          name: "{{ user }}"
          password: "{{ password }}"
          groups:
            - wheel
          state: present

      - name: Add public key to authorized_keys
        authorized_key:
          user: "{{ user }}"
          state: present
          key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

      - name: Deny root from logging in
        ansible.builtin.lineinfile:
          dest: /etc/ssh/sshd_config
          regexp: '^(#)?PermitRootLogin \w*$'
          line: 'PermitRootLogin no'
          state: present

      - name: Allow specific users to log in
        ansible.builtin.lineinfile:
          dest: /etc/ssh/sshd_config
          regexp: '^AllowUsers'
          line: 'AllowUsers {{ user }}'
          state: present

      - name: Add {{ user }} to sudoers file
        ansible.builtin.lineinfile:
          path: /etc/sudoers
          regexp: '^{{ user }}'
          line: '{{ user }} ALL=(ALL) NOPASSWD: ALL'
          validate: 'visudo -cf %s'

5. Running the playbook

Now that we have the playbook created in the file create-user.yaml in the current directory, we need to provide a hosts file that will define connection to our server. From the playbook definition, we define our server as remote-server. Now let’s add that to the hosts file.

Create a file hosts.yaml with the following content defining the remote-server:

all:
  hosts:
    remote-server:
      ansible_ssh_host: 138.68.150.24
      ansible_ssh_user: root

Now we need to invoke the ansible-playbook command like this:

ansible-playbook -i hosts.yaml create-user.yaml -vv

In the above command, the -i specifies the inventory file. The -vv enables verbosity.

This is the output from my server:

&#x279C; ansible-playbook -i hosts.yaml create-user.yaml -vv
ansible-playbook [core 2.11.1]
  config file = None
  configured module search path = [&#039;/Users/citizix/.ansible/plugins/modules&#039;, &#039;/usr/share/ansible/plugins/modules&#039;]
  ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
  ansible collection location = /Users/citizix/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible-playbook
  python version = 3.9.5 (default, May  4 2021, 03:36:27) [Clang 12.0.0 (clang-1200.0.32.29)]
  jinja version = 2.11.3
  libyaml = False
No config file found; using defaults
redirecting (type: modules) ansible.builtin.authorized_key to ansible.posix.authorized_key
Skipping callback &#039;default&#039;, as we already have a stdout callback.
Skipping callback &#039;minimal&#039;, as we already have a stdout callback.
Skipping callback &#039;oneline&#039;, as we already have a stdout callback.

PLAYBOOK: create-user.yaml *********************************************************************************************************************************************************************************
1 plays in create-user.yaml

PLAY [Create user on a linux server] ***********************************************************************************************************************************************************************
META: ran handlers

TASK [Create a login user] *********************************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:9
changed: [remote-server] => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": true, "comment": "", "create_home": true, "group": 1000, "groups": "wheel", "home": "/home/rocky", "name": "rocky", "password": "NOT_LOGGING_PASSWORD", "shell": "/bin/bash", "state": "present", "system": false, "uid": 1000}

TASK [Add public key to authorized_keys] *******************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:17
redirecting (type: modules) ansible.builtin.authorized_key to ansible.posix.authorized_key
changed: [remote-server] => {"changed": true, "comment": null, "exclusive": false, "follow": false, "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6gM14zM0+L+ERQFVFYbHBAjtyghKKx/N+qsFXxrcJqVqsyn1lp/erPqg3g6WSu/bwiAB+E22RyG9icS7Td8ssWi9vDE73DHgC5NGLm4KeP2FospEFjY6v8XVjkwQZJ+8YyCfXJ4E5cm0FGKZREakDlYeaonTaIjxkXlsZB3Yl93+KZvZ0g1WiBOU6N6NWpEQVvxYccWK4+EuQiCryELL0o4dCNrwLaYOyv/NbSYQ09m3+mvN0VRnTzo7qSOqy1U6oCVA9bhd+tRyoUsUqp3Up8jdfzEGfWr/Pqskjtl8YXySPHLEROXX/Om4AyT62EQxcPMzedPJ6HGLHnlk4EO9cBLawymdWO7AlghujksVBu9S+alOkAmJkkPzeq76WOjCTmoNxlQmEDlucukiujfWKl4hACdNVtARptvuc5+4uMYA4j4Ql+XtQ964UQa4HiGiNpoiDegzDq9GMEsQW4W5frRuOIm4R7thYGatRBkNFw+uemE5HclF8LXOuPkShhFqpDPgI1oH99covroXggV8/ovEf9ZSoshNLMHX5kXWGAWF983Cn2N5RpmqN8rfcGVq6C93njExvHDfl7bHkhT10axOLV/V4vX4lSktWVV4//vq2wMQLi5F1l7ai8scA3eYeSaWnDaJj2jnz6V5JBOPIOH/3lf7qq4oquZhmuWq3w== citizix", "key_options": null, "keyfile": "/home/rocky/.ssh/authorized_keys", "manage_dir": true, "path": null, "state": "present", "user": "rocky", "validate_certs": true}

TASK [Deny root from logging in] ***************************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:23
changed: [remote-server] => {"backup": "", "changed": true, "msg": "line replaced"}

TASK [Allow specific users to log in] **********************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:30
changed: [remote-server] => {"backup": "", "changed": true, "msg": "line added"}

TASK [Add my pub key to authorized_keys] *******************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:37
ok: [remote-server] => {"changed": false, "comment": null, "exclusive": false, "follow": false, "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6gM14zM0+L+ERQFVFYbHBAjcsTGZKKx/N+qsFXxrcJqVqsyn1lp/erPqg3g6WSu/bwiAB+E22RyG9icS7Td8ssWi9vDE73DHgC5NGLm4KeP2FospEFjY6v8XVjkwQZJ+8YyCfXJ4E5cm0FGKZREakDlYeaonTaIjxkXlsZB3Yl93+KZvZ0g1WiBOU6N6NWpEQVvxYccWK4+EuQiCryELL0o4dCNrwLaYOyv/NbSYQ09m3+mvN0VRnTzo7qSOqy1U6oCVA9bhd+tRyoUsUqp3Up8jdfzEGfWr/Pqskjtl8YXySPHLEROXX/Om4AyT62EQxcPMzedPJ6HGLHnlk4EO9cBLawymdWO7AlghujksVBu9S+alOkAmJkkPzeq76WOjCTmoNxlQmEDlucukiujfWKl4hACdNVtARptvuc5+4uMYA4j4Ql+XtQ964UQa4HiGiNpoiDegzDq9GMEsQW4W5frRuOIm4R7thYGatRBkNFw+uemE5HclF8LXOuPkShhFqpDPgI1oH99covroXggV8/ovEf9ZSoshNLMHX5kXWGAWF983Cn2N5RpmqN8rfcGVq6C93njExvHDfl7bHkhT10axOLV/V4vX4lSktWVV4//vq2wMQLi5F1l7ai8scA3eYeSaWnDaJj2jnz6V5JBOPIOH/3lf7qq4oquZhmuWq3w== etowett@tuxm1or.local", "key_options": null, "keyfile": "/home/rocky/.ssh/authorized_keys", "manage_dir": true, "path": null, "state": "present", "user": "rocky", "validate_certs": true}

TASK [Add rocky to sudoers file] ***************************************************************************************************************************************************************************
task path: /Users/citizix/projects/ansible/create-user.yaml:43
changed: [remote-server] => {"backup": "", "changed": true, "msg": "line replaced"}
META: ran handlers
META: ran handlers

PLAY RECAP *************************************************************************************************************************************************************************************************
remote-server              : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy