Creating a new user and password with Ansible
Solution 1
If you read Ansible's manual for user
module, it'll direct you to the Ansible-examples github repo for details how to use password
parameter.
There you'll see that your password must be hashed.
- hosts: all
user: root
vars:
# created with:
# python -c 'import crypt; print crypt.crypt("This is my Password", "$1$SomeSalt$")'
password: $1$SomeSalt$UqddPX3r4kH3UL5jq5/ZI.
tasks:
- user: name=tset password={{password}}
If your playbook or ansible command line has your password as-is in plain text, this means your password hash recorded in your shadow file is wrong. That means when you try to authenticate with your password its hash will never match.
Additionally, see Ansible FAQ regarding some nuances of password parameter and how to correctly use it.
Solution 2
I may be too late to reply this but recently I figured out that jinja2 filters have the capability to handle the generation of encrypted passwords. In my main.yml
I'm generating the encrypted password as:
- name: Creating user "{{ uusername }}" with admin access
user:
name: {{ uusername }}
password: {{ upassword | password_hash('sha512') }}
groups: admin append=yes
when: assigned_role == "yes"
- name: Creating users "{{ uusername }}" without admin access
user:
name: {{ uusername }}
password: {{ upassword | password_hash('sha512') }}
when: assigned_role == "no"
- name: Expiring password for user "{{ uusername }}"
shell: chage -d 0 "{{ uusername }}"
"uusername " and "upassword " are passed as --extra-vars
to the playbook and notice I have used jinja2 filter here to encrypt the passed password.
I have added below tutorial related to this to my blog
Solution 3
I want to propose yet another solution:
- name: Create madhead user
user:
name: madhead
password: "{{ 'password' | password_hash('sha512') }}"
shell: /bin/zsh
update_password: on_create
register: madhead
- name: Force madhead to change password
shell: chage -d 0 madhead
when: madhead.changed
Why it is better? Like already has been noted here, Ansible plays should be idempotent. You should think of them not as a sequence of actions in imperative style, but like a desired state, declarative style. As a result you should be able to run it multiple times and get the same result, the same server state.
This all sounds great, but there are some nuances. One of them is managing users. "Desired state" means that every time you run a play that creates a user he will be updated to match exactly that state. By "updated" I mean that his password will be changed too. But most probably it is not what you need. Usually, you need to create user, set and expire his password only once, further play runs shouldn't update his password.
Fortunately, Ansible has update_password
attribute in user
module that solves this issue. Mixing this with registered variables you can also expire his password only when the user is actually updated.
Note that if you change user's shell manually (suppose, you don't like the shell that evil admin forced in his play) the user will be updated, thus his password will be expired.
Also note how you can easily use plain text initial passwords in plays. No need to encode them somewhere else and paste hashes, you can use Jinja2 filter for that. However, this can be a security flaw if someone happens to login before you initially do.
Solution 4
try like this
vars_prompt:
- name: "user_password"
prompt: "Enter a password for the user"
private: yes
encrypt: "md5_crypt" #need to have python-passlib installed in local machine before we can use it
confirm: yes
salt_size: 7
- name: "add new user" user: name="{{user_name}}" comment="{{description_user}}" password="{{user_password}}" home="{{home_dir}}" shell="/bin/bash"
Solution 5
The Ansible 'user' module manages users, in the idempotent way. In the playbook below the first task declares state=present for the user. Note that 'register: newuser' in the first action helps the second action to determine if the user is new (newuser.changed==True) or existing (newuser.changed==False
), to only generate the password once.
The Ansible playbook has:
tasks:
- name: create deployment user
user:
name: deployer
createhome: yes
state: present
register: newuser
- name: generate random password for user only on creation
shell: /usr/bin/openssl rand -base64 32 | passwd --stdin deployer
when: newuser.changed
raphael_turtle
Updated on January 09, 2022Comments
-
raphael_turtle over 2 years
I have an ansible task which creates a new user on ubuntu 12.04;
- name: Add deployment user action: user name=deployer password=mypassword
it completes as expected but when I login as that user and try to sudo with the password I set it always says it's incorrect. What am I doing wrong?
-
Brendan Wood over 10 yearsThanks for the tip. I just wanted to point out that the user module documentation linked above recommends using the
openssl passwd -salt <salt> -1 <plaintext>
to generate the password hash, rather than the Python one-liner you have above. I had some trouble getting correct output from Python, probably due to my own incompetence and the openssl command worked better. -
Mark over 9 yearsIsn't this generating a md5 hashed password? Isn't that insecure?
-
bbaassssiiee over 9 yearsshell: will always cause a change to be reported.
-
Gunnar almost 9 yearsI like this solution given that it allows to type in a password when the script runs. Nice solution for a bootstrap script which should not be run every time. For Ubuntu I used "sha512_crypt", though.
-
Phill Pafford almost 9 years@datasmid you could add the no_log: True option docs.ansible.com/ansible/…
-
Michael Wyraz over 8 yearsTo avoid that the item is always "changed", you can add a secret "salt" as 2nd parameter to password_hash.
-
user85461 over 8 yearsAs per @MichaelWyraz's suggestion: adding a 2nd "salt" param avoids "changed". You can set this via a variable, e.g.
password={{upassword|password_hash('sha512', upassword_salt)}}
. That lets you put salt in a variables vault, as you presumably would withupassword
too, keeping both out of the tasks.yml. -
madhead about 8 yearsI'd also apply @bbaassssiiee's state-checking and add
update_password: on_create
in user module to this answer to prevent expiring passwords for already created users. -
leesei about 8 yearsI couldn't get the other solution working for me. Yet you answer is simple and worked. I would like to give you 5 upvotes, if I could.
-
Breedly over 7 yearsHow do you synchronize the salt to be used with the OS?
-
Benjamin Dale over 7 years@Breedly: there is no need - the salt is always stored as part of the password ($1$thesalt$thepasswordhash) meaning it's portable between OSs using the same hash function
-
texnic over 6 yearsI have tried exactly this, and it doesn't work. I believe, it's because
password={{user.pass}}
will expand to include the actual password, while ansible expects the hash there. -
dokaspar about 6 yearsUsing this python command to generate a hash did not work for me. But I found the hash in
/etc/shadow
after setting the password manually usingpasswd <user>
. -
dokaspar about 6 yearsI don't want my password to be hard-coded like that :( Is it possible to get it out of the ansible-vault and inject it here? Nesting like
"{{ '{{vaulted_password}}' | password_hash('sha512') }}"
doesn't seem to work... -
madhead about 6 yearsHave you tried
{{ vaulted_password | password_hash('sha512') }}
, wherevaulted_password
is a key to the value in vault? -
alexakarpov almost 6 yearsthanks mate, I was specifically looking for the adhoc version, and had this very same quote problem you've mentioned
-
Denis Savenko almost 6 yearsOnly one solution working for me with vault password. Thank you a lot.
-
XXL almost 6 yearsat least recent Debian based distributions do not seem to support the long GNU option "--stdin" to the passwd binary.
-
Diti over 5 years
update_password: on_create
doesn’t seem to work (there is an open bug about it from 2017), so passwords will change any time there is a state change on a user. -
Michael Aicher over 4 yearsThanks for your great example this brought me on the right track. Nevertheless i had to do some more things to get it work on a mac with ansible version 2.8.2. First of all on a mac it is not possible to use crypt therefore it is required to install the passlib library with
pip install passlib
. Then to be able to use an inline vault encrypted string i had to reformat with the following addition:password: {{ upassword | string | password_hash('sha512') }}
. This avoids the error messagesecret must be unicode or bytes, not ansible.parsing.yaml.objects.AnsibleVaultEncryptedUnicode
-
Janus over 4 yearsThe file is not on GitHub anymore.
-
openCivilisation almost 4 yearsdespite using this method to set a password: "{{ my_password | string | password_hash('sha512') }}" I will still get - [WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.
-
openCivilisation almost 4 yearsAhh so I figured the problem - while you may encrypt a string to generate the pass, if the input var is defined at the playbook level as unencrypted, this was not working. since I was mapping another password at vars to be used in the task, the jinja encryption expression needed to live there - where the first var is defined. This would be handy to add to the answer. It actually prevented me from figuring this out a year ago, glad it works now!
-
Brian about 3 yearsI've changed the github link to the correct one.
-
Luis Lavaire. over 2 yearsIdempotency. That's why we are using Ansible in the first place.
-
shellwhale over 2 yearsIs it possible to include salt with this method?
-
Et7f3XIV about 2 yearsmd5 should be avoided for password sha512 is recommended.