Creating a new user and password with Ansible

259,060

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
Share:
259,060
raphael_turtle
Author by

raphael_turtle

Updated on January 09, 2022

Comments

  • raphael_turtle
    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
    Brendan Wood over 10 years
    Thanks 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
    Mark over 9 years
    Isn't this generating a md5 hashed password? Isn't that insecure?
  • bbaassssiiee
    bbaassssiiee over 9 years
    shell: will always cause a change to be reported.
  • Gunnar
    Gunnar almost 9 years
    I 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
    Phill Pafford almost 9 years
    @datasmid you could add the no_log: True option docs.ansible.com/ansible/…
  • Michael Wyraz
    Michael Wyraz over 8 years
    To avoid that the item is always "changed", you can add a secret "salt" as 2nd parameter to password_hash.
  • user85461
    user85461 over 8 years
    As 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 with upassword too, keeping both out of the tasks.yml.
  • madhead
    madhead about 8 years
    I'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
    leesei about 8 years
    I 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
    Breedly over 7 years
    How do you synchronize the salt to be used with the OS?
  • Benjamin Dale
    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
    texnic over 6 years
    I 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
    dokaspar about 6 years
    Using 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 using passwd <user>.
  • dokaspar
    dokaspar about 6 years
    I 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
    madhead about 6 years
    Have you tried {{ vaulted_password | password_hash('sha512') }}, where vaulted_password is a key to the value in vault?
  • alexakarpov
    alexakarpov almost 6 years
    thanks mate, I was specifically looking for the adhoc version, and had this very same quote problem you've mentioned
  • Denis Savenko
    Denis Savenko almost 6 years
    Only one solution working for me with vault password. Thank you a lot.
  • XXL
    XXL almost 6 years
    at least recent Debian based distributions do not seem to support the long GNU option "--stdin" to the passwd binary.
  • Diti
    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
    Michael Aicher over 4 years
    Thanks 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 message secret must be unicode or bytes, not ansible.parsing.yaml.objects.AnsibleVaultEncryptedUnicode
  • Janus
    Janus over 4 years
    The file is not on GitHub anymore.
  • openCivilisation
    openCivilisation almost 4 years
    despite 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
    openCivilisation almost 4 years
    Ahh 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
    Brian about 3 years
    I've changed the github link to the correct one.
  • Luis Lavaire.
    Luis Lavaire. over 2 years
    Idempotency. That's why we are using Ansible in the first place.
  • shellwhale
    shellwhale over 2 years
    Is it possible to include salt with this method?
  • Et7f3XIV
    Et7f3XIV about 2 years
    md5 should be avoided for password sha512 is recommended.