When managing multiple users in Ansible, a common challenge is assigning unique passwords to each user. The basic approach of using a list variable with a single password variable leads to all users inheriting the same credentials, which is both insecure and impractical.
Ansible's dictionary variables (YAML's associative arrays) provide the perfect solution. Here's how to structure your user data:
vars:
users:
user1:
password: $6$rounds=656000$saltstring$encryptedhash1
groups: sudo,adm
user2:
password: $6$rounds=656000$saltstring$encryptedhash2
groups: developers
user3:
password: $6$rounds=656000$saltstring$encryptedhash3
groups: docker,sudo
Here's the complete playbook implementation:
- hosts: all
become: yes
vars:
users:
webadmin:
password: $6$J7s8K9l0$encryptedhashXYZ123
groups: www-data,sudo
dbadmin:
password: $6$A1b2C3d4$encryptedhashABC456
groups: postgres,adm
devuser:
password: $6$X5y6Z7w8$encryptedhashDEF789
groups: docker,developers
tasks:
- name: Create users with individual passwords
ansible.builtin.user:
name: "{{ item.key }}"
password: "{{ item.value.password }}"
groups: "{{ item.value.groups | default(omit) }}"
shell: /bin/bash
append: yes
loop: "{{ users | dict2items }}"
For more complex scenarios, consider these patterns:
# Using when conditions
- name: Create admin users
ansible.builtin.user:
name: "{{ item.key }}"
password: "{{ item.value.password }}"
groups: "{{ item.value.groups }}"
loop: "{{ users | dict2items }}"
when: "'sudo' in item.value.groups"
# Combining with vault for security
- name: Load encrypted passwords
ansible.builtin.include_vars:
file: passwords.yml
name: user_passwords
- name: Create users with vault-protected passwords
ansible.builtin.user:
name: "{{ item.key }}"
password: "{{ user_passwords[item.key] }}"
loop: "{{ users | dict2items }}"
To create the encrypted passwords for your playbook:
# On Linux systems
mkpasswd --method=SHA-512 --rounds=656000
# Ansible alternative
ansible localhost -m debug -a "msg={{ 'plaintext' | password_hash('sha512', 'salt') }}"
1. Store sensitive passwords in Ansible Vault
2. Use consistent naming conventions
3. Document your user structure in playbook comments
4. Consider using ansible-galaxy roles for reusable user management
When automating user creation across multiple servers, a common pain point emerges: how to assign unique encrypted passwords to each user while maintaining clean playbook structure. The initial approach of using parallel lists falls short when dealing with user-specific attributes.
Ansible handles this elegantly through YAML dictionaries (associative arrays). Here's the proper implementation:
vars:
users:
myuser1:
password: $6$rounds=656000$saltstring$hashedvalue1
groups: sudo,adm
myuser2:
password: $6$rounds=656000$saltstring$hashedvalue2
groups: developers
shell: /bin/zsh
The corresponding task adapts to handle dictionary iteration:
tasks:
- name: Create users with individual passwords
ansible.builtin.user:
name: "{{ item.key }}"
password: "{{ item.value.password }}"
groups: "{{ item.value.groups | default('users') }}"
shell: "{{ item.value.shell | default('/bin/bash') }}"
loop: "{{ users | dict2items }}"
For production environments, consider generating passwords programmatically:
- name: Generate encrypted passwords
set_fact:
user_passwords: |
{% set result = {} %}
{% for user in ['dev1','qa2','ops3'] %}
{% set salt = 12345|random(seed=user) %}
{% set pass = 'defaultPass' ~ salt %}
{% set encrypted = pass | password_hash('sha512', salt|string) %}
{% set _ = result.update({user: encrypted}) %}
{% endfor %}
{{ result }}
For sensitive deployments, integrate Ansible Vault:
vars_files:
- secrets/users_encrypted.yml # encrypted with ansible-vault
Where users_encrypted.yml contains:
users:
admin:
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
623133653966623430613934643...
For heterogeneous environments, add OS-specific logic:
tasks:
- name: Set Windows password policy
win_user_right:
name: SeNetworkLogonRight
users: "{{ users | dict2items | selectattr('value.groups', 'search', 'remote') | map(attribute='key') | list }}"
when: ansible_os_family == 'Windows'