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
- Install dependencies
- Generate the password using the passlib package
- Create the ansible playbook defining the tasks required
- 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('file', '~/.ssh/id_rsa.pub') }}"
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: '^(#)?PermitRootLogin \w*$'
line: 'PermitRootLogin no'
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: '^AllowUsers'
line: 'AllowUsers {{ user }}'
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: '^{{ user }}'
line: '{{ user }} ALL=(ALL) NOPASSWD: ALL'
validate: 'visudo -cf %s'
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:
➜ ansible-playbook -i hosts.yaml create-user.yaml -vv
ansible-playbook [core 2.11.1]
config file = None
configured module search path = ['/Users/citizix/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
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 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', 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