How to Create a New User and Password with Ansible's user Module
Use the ansible.builtin.user module with a pre-hashed password and update_password: on_create to create users idempotently across Linux and Windows systems.
The ansible.builtin.user module is a core system-management component in the ansible/ansible repository that abstracts platform-specific user creation commands into a unified interface. When you need to create a new user and password with Ansible, the module expects a crypt(3)-compatible hash rather than plaintext, ensuring secure operations that delegate actual hashing to the controller side before reaching remote hosts.
Module Architecture and Entry Points
Core Implementation in lib/ansible/modules/user.py
The module execution begins in lib/ansible/modules/user.py where the main() function serves as the entry point. When Ansible parses a playbook containing ansible.builtin.user, the execution engine imports this file and invokes main(), which instantiates the AnsibleModule class using an argument_spec dictionary. This specification validates inputs for parameters including name, state, password, update_password, shell, and groups before any system changes occur.
Password Handling Mechanics
The password parameter strictly requires a crypt(3)-compatible hash, rejecting plaintext strings to prevent credential exposure over the network. The module implements this security boundary by delegating hashing to Jinja2 filters on the controller, typically using {{ 'plaintext' | password_hash('sha512') }}. Additionally, the update_password parameter accepts three values that control idempotency: always (rewrite hash every run), on_create (set only when creating the user), or never (ignore password changes).
State Management and Idempotence
The state parameter drives the control flow through four distinct modes:
present– Creates the user viauseraddor modifies existing accounts withusermodabsent– Removes the user usinguserdelwith optional home directory purginglocked– Disables login viapasswd -lwithout removing the accountenabled– Unlocks a previously locked account
After each operation, the module verifies current system state by calling pwd.getpwnam() or parsing /etc/passwd directly, comparing actual attributes against desired parameters to report changed: false when systems already match the declared configuration.
Platform Abstraction Layer
While the main logic resides in lib/ansible/modules/user.py, platform-specific implementations are delegated to lib/ansible/module_utils/users.py. This utility file provides helper functions that map generic module operations to OS-native commands: POSIX systems execute standard useradd/usermod binaries, while Windows targets invoke equivalent PowerShell cmdlets, enabling the same playbook syntax across heterogeneous environments.
Practical Implementation Examples
Creating a User with a Hashed Password
Define the password hash as a variable using the password_hash filter, then pass it to the module with update_password: on_create to prevent unnecessary password resets on subsequent runs:
- name: Ensure a local user exists with a password
hosts: all
become: true
vars:
user_password_hash: "{{ 'SuperSecret123' | password_hash('sha512') }}"
tasks:
- name: Create the user
ansible.builtin.user:
name: alice
comment: Alice Example User
shell: /bin/bash
groups: sudo
password: "{{ user_password_hash }}"
update_password: on_create
state: present
Ad-Hoc Command-Line Creation
For one-off operations, use the ansible command with the -b flag and inline Jinja2 hashing:
ansible all -b -m ansible.builtin.user -a "name=bob password={{ 'Passw0rd!' | password_hash('sha512') }} state=present"
Secure Vault Integration
Store pre-computed hashes in Ansible Vault to avoid exposing plaintext credentials in version control:
- name: Create service account from vaulted password hash
ansible.builtin.user:
name: svcacct
password: "{{ vault_svcacct_hash }}"
update_password: always
state: present
Summary
- The
usermodule entry point ismain()inlib/ansible/modules/user.py, which validates parameters through anargument_specdictionary before execution. - Passwords must be provided as crypt(3) hashes using the
password_hashJinja2 filter, never as plaintext strings. - The
update_passwordparameter controls idempotency behavior, withon_createrecommended for production environments to prevent account lockouts. - Platform abstraction is handled by
lib/ansible/module_utils/users.py, enabling Linuxuseradd/usermodand Windows PowerShell cmdlets from the same task syntax. - The module checks current system state via
pwd.getpwnam()and accurately reports change status, ensuring safe repeated playbook runs.
Frequently Asked Questions
What hash algorithms does the password parameter accept?
The password parameter accepts any hash format supported by the target system's crypt(3) implementation. For modern Linux distributions, SHA-512 is recommended and generated via {{ 'password' | password_hash('sha512') }}. Legacy systems may require MD5 or SHA-256, but SHA-512 provides the strongest security for contemporary deployments.
Should I use update_password: always or on_create?
Use on_create (the default) unless you specifically need to rotate passwords on every playbook execution. The always setting rewrites the password hash each run, which can lock users out if your hash source changes unintentionally. The on_create value ensures passwords are set only during initial account creation, preserving subsequent manual password changes.
Where does the module handle different operating systems?
Platform-specific logic resides in lib/ansible/module_utils/users.py, which the main module imports to handle OS differences. POSIX systems execute standard binaries like useradd and usermod, while Windows nodes receive PowerShell commands, all transparent to the playbook author using standard ansible.builtin.user parameters.
Why does the module require a hashed password instead of plaintext?
This design prevents credential exposure across the network and in process listings. By requiring crypt(3)
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md