Creating users is typically straightforward as the documentation for the required Ansible modules is comprehensive and easy to navigate. However, working with dictionaries instead of lists can introduce some additional complexity.
For example, let's assume the following dictionary structure is given:
usergroups:
group1:
gid: 10001
name: group1
group2:
gid: 10002
name: group2
group3:
gid: 10003
name: group3
group4:
gid: 10004
name: group4
users:
user1:
uid: 1985
name: user1
groups:
- group1
- group2
sshkeys:
- ssh-ed25519 AAAA0
- ssh-ed25519 AAAA1
user2:
uid: 1986
name: user2
groups:
- group4
- group1
sshkeys:
- ecdsa-sha2-nistp384 AAAA0
Ansible provides the dict2items filter which transforms a dictionary into a list of key-value pairs. This transformation allows you to iterate over a list created from the dictionary and access all attributes for use within your Ansible modules:
- name: create usergroups
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.builtin.group:
gid: "{{ item['value']['uid'] }}"
name: "{{ item['value']['name'] }}"
state: present
system: false
Same goes for the groups:
- name: create groups
with_items: "{{ usergroups | ansible.builtin.dict2items }}"
ansible.builtin.group:
gid: "{{ item['value']['gid'] }}"
name: "{{ item['value']['name'] }}"
state: present
system: false
For user creation, additional logic is required to generate and hash strong passwords. Each user will be assigned a unique password with the following characteristics:
- Length: 96 characters
- Character set: A combination of uppercase letters, lowercase letters, and digits
- name: create users
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.builtin.user:
uid: "{{ item['value']['uid'] }}"
name: "{{ item['value']['name'] }}"
comment: "{{ item['value']['full_name'] | default(item['value']['name']) | capitalize }}"
append: false
create_home: true
generate_ssh_key: false
group: "{{ item['value']['name'] }}"
groups: "{{ item['value']['groups'] }}"
home: "/home/{{ item['value']['name'] }}"
move_home: false
password: "{{ lookup('ansible.builtin.password', '/dev/null', length=96, chars=['ascii_lowercase','ascii_uppercase','digits']) | password_hash('sha512_crypt') }}"
update_password: on_create
shell: "{{ item['value']['shell'] | default('/bin/bash') }}"
state: present
system: false
You might have noticed that passwords are generated for each user, but not returned to the user as they're returned to '/dev/null'. Instead of relying on passwords for authentication, the SSH keys specified in each user's 'sshkeys' attribute will be used to configure passwordless SSH access using the 'authorized_key' module from the 'ansible.posix' collection:
- name: configure ssh keys
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.posix.authorized_key:
user: "{{ item['value']['name'] }}"
exclusive: true
state: present
key: "{{ item['value']['sshkeys'] | join('\n') }}"
The authorized_key module requires the key attribute to be a string. Therefore, we'll need to combine the list of SSH keys specified for each user into a single string using the join() filter before applying them to the user's authorized_keys file.
The completed playbook looks like this:
---
- name: create users on test hosts
hosts: test
become: true
gather_facts: true
vars:
usergroups:
group1:
gid: 10001
name: group1
group2:
gid: 10002
name: group2
group3:
gid: 10003
name: group3
group4:
gid: 10004
name: group4
users:
user1:
uid: 1985
name: user1
groups:
- group1
- group2
sshkeys:
- ssh-ed25519 AAAA0
- ssh-ed25519 AAAA1
user2:
uid: 1986
name: user2
groups:
- group4
- group1
sshkeys:
- ecdsa-sha2-nistp384 AAAA0
collections:
- ansible.builtin
- ansible.posix
tasks:
- name: create usergroups
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.builtin.group:
gid: "{{ item['value']['uid'] }}"
name: "{{ item['value']['name'] }}"
state: present
system: false
- name: create groups
with_items: "{{ usergroups | ansible.builtin.dict2items }}"
ansible.builtin.group:
gid: "{{ item['value']['gid'] }}"
name: "{{ item['value']['name'] }}"
state: present
system: false
- name: create users
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.builtin.user:
uid: "{{ item['value']['uid'] }}"
name: "{{ item['value']['name'] }}"
comment: "{{ item['value']['full_name'] | default(item['value']['name']) | capitalize }}"
append: false
create_home: true
generate_ssh_key: false
group: "{{ item['value']['name'] }}"
groups: "{{ item['value']['groups'] }}"
home: "/home/{{ item['value']['name'] }}"
move_home: false
password: "{{ lookup('ansible.builtin.password', '/dev/null', length=96, chars=['ascii_lowercase','ascii_uppercase','digits']) | password_hash('sha512_crypt') }}"
update_password: on_create
shell: "{{ item['value']['shell'] | default('/bin/bash') }}"
state: present
system: false
- name: configure ssh keys
with_items: "{{ users | ansible.builtin.dict2items }}"
ansible.posix.authorized_key:
user: "{{ item['value']['name'] }}"
exclusive: true
state: present
key: "{{ item['value']['sshkeys'] | join('\n') }}"
...
If possible, using a list of users, each containing the relevant attributes (username, uid, groups, SSH keys, etc.), would significantly simplify the playbook and improve its readability.
Comments
Post a Comment