136

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?

3
  • 2
    Are you logging in with the same password or ssh keys? Did you check your shadow file to make sure its content is as expected? Also your password is not supposed to be in plain text but rather prehashed. Commented Oct 11, 2013 at 4:13
  • my password is plaintext, can I not use it that way? I don't understand how to make it encrypted, or really need to either. Commented Oct 11, 2013 at 9:00
  • Whichever password you set - it will be placed in /etc/shadow verbatim. Since Linux expects a hash you need to pass a hashed password to the user module. Commented Apr 27 at 8:01

25 Answers 25

213

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.

Sign up to request clarification or add additional context in comments.

7 Comments

To avoid that the item is always "changed", you can add a secret "salt" as 2nd parameter to password_hash.
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.
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.
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
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.
|
128

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=test 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.

8 Comments

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.
How do you synchronize the salt to be used with the OS?
@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
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>.
You should let it generate the salt - the salt should be random
|
65

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.

6 Comments

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.
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...
Have you tried {{ vaulted_password | password_hash('sha512') }}, where vaulted_password is a key to the value in vault?
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.
Idempotency. That's why we are using Ansible in the first place.
|
15

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"

2 Comments

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.
md5 should be avoided for password sha512 is recommended.
14

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
    ansible.builtin.user: 
      name: deployer 
      createhome: true
      state: present 
    register: newuser

  - name: Generate random password for user only on creation
    ansible.builtin.shell: |
      set -e -o pipefail
      /usr/bin/openssl rand -base64 32 | passwd --stdin deployer
    when: newuser.changed

2 Comments

Only one solution working for me with vault password. Thank you a lot.
at least recent Debian based distributions do not seem to support the long GNU option "--stdin" to the passwd binary.
9

The purpose of the role in this answer is to generate random password for new_user_name and expire the password immediately. The new_user_name is required to change the password on his/her first logon.

create_user.yml:

---
# create_user playbook

- hosts: your_host_group
  become: True
  user: ansible

  roles:
    - create_user

roles/create_user/tasks/main.yml:

---
# Generate random password for new_user_name and the new_user_name
# is required to change his/her password on first logon. 

- name: Generate password for new user
  shell: makepasswd --chars=20
  register: user_password

- name: Generate encrypted password
  shell: mkpasswd --method=SHA-512 {{ user_password.stdout }}
  register: encrypted_user_password

- name: Create user account
  user: name={{ new_user_name }}
        password={{ encrypted_user_password.stdout }}
        state=present
        append=yes
        shell="/bin/bash"
        update_password=always
  when: new_user_name is defined and new_user_name in uids
  register: user_created

- name: Force user to change password
  shell: chage -d 0 {{ new_user_name }}
  when: user_created.changed

- name: User created
  debug: msg="Password for {{ new_user_name }} is {{ user_password.stdout }}"
  when: user_created.changed

When you want to create a new user:

ansible-playbook -i hosts.ini create_user.yml --extra-vars "new_user_name=kelvin"

Comments

6

I tried many utilities including mkpasswd, Python, etc., but it seems like there is some compatibility issue with Ansible in reading HASH values generated by other tools. So finally it worked by Ansible # value itself.

ansible all -i localhost, -m debug -a "msg={{ 'yourpasswd' | password_hash('sha512', 'mysecretsalt') }}"

Playbook:

- name: User creation
  user: 
    name: username  
    uid: UID
    group: grpname
    shell: /bin/bash
    comment: "test user"
    password: "$6$mysecretsalt$1SMjoVXjYf.3sJR3a1WUxlDCmdJwC613.SUD4DOf40ASDFASJHASDFCDDDWERWEYbs8G00NHmOg29E0"

Comments

4

My solution is using lookup and generate password automatically.

---
- hosts: 'all'
  remote_user: root
  gather_facts: no
  vars:
    deploy_user: deploy
    deploy_password: "{{ lookup('password', '/tmp/password chars=ascii_letters') }}"

  tasks:
    - name: Create deploy user
      user:
        name: "{{ deploy_user }}"
        password: "{{ deploy_password | password_hash('sha512') }}"

Comments

3

This is the easy way:

---
- name: Create user
  user: name=user shell=/bin/bash home=/srv/user groups=admin,sudo generate_ssh_key=yes ssh_key_bits=2048
- name: Set password to user
  shell: echo user:plain_text_password | sudo chpasswd
  no_log: True

2 Comments

shell: will always cause a change to be reported.
@datasmid you could add the no_log: True option docs.ansible.com/ansible/…
3

This is how it worked for me

- hosts: main
  vars:
  # created with:
  #  python -c "from passlib.hash import sha512_crypt; print sha512_crypt.encrypt('<password>')"
  # above command requires the PassLib library: sudo pip install passlib
  - password: '$6$rounds=100000$H/83rErWaObIruDw$DEX.DgAuZuuF.wOyCjGHnVqIetVt3qRDnTUvLJHBFKdYr29uVYbfXJeHg.IacaEQ08WaHo9xCsJQgfgZjqGZI0'

tasks:

- user: name=spree password={{password}} groups=sudo,www-data shell=/bin/bash append=yes
  sudo: yes

Comments

3

You can use ansible-vault for using secret keys in playbooks. Define your password in yml.

ex. pass: secret or

user:
  pass: secret
  name: fake

encrypt your secrets file with :

ansible-vault encrypt /path/to/credential.yml

ansible will ask a password for encrypt it. (i will explain how to use that pass)

And then you can use your variables where you want. No one can read them without vault-key.

Vault key usage:

via passing argument when running playbook.

--ask-vault-pass: secret

or you can save into file like password.txt and hide somewhere. (useful for CI users)

--vault-password-file=/path/to/file.txt

In your case : include vars yml and use your variables.

- include_vars: /path/credential.yml

  - name: Add deployment user
    action: user name={{user.name}} password={{user.pass}}

1 Comment

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.
3

Just for completeness I will post the ad-hoc command using ansible since there is a catch there as well.

First try generating an encrypted password using the mkpasswd utility that is available on most Linux systems:

mkpasswd --method=SHA-512

Then try the ansible ad-hock command:

ansible all -m user -a 'name=testuser shell=/bin/bash \
     comment="Test User" password=$6$XXXX' -k -u admin --sudo

But make sure:

  1. The command is in single quotes and NOT double otherwise your password will never work
  2. You run it with --sudo or you end up with an error like (useradd: cannot lock /etc/passwd; try again later)

1 Comment

thanks mate, I was specifically looking for the adhoc version, and had this very same quote problem you've mentioned
3

Generating random password for user

first need to define users variable then follow below

tasks:

- name: Generate Passwords
  become: no
  local_action: command pwgen -N 1 8
  with_items: '{{ users }}'
  register: user_passwords

- name: Update User Passwords
  user:
    name: '{{ item.item }}'
    password: "{{ item.stdout | password_hash('sha512')}}"
    update_password: on_create
  with_items: '{{ user_passwords.results }}'

- name: Save Passwords Locally
  become: no
  local_action: copy content={{ item.stdout }} dest=./{{ item.item }}.txt
  with_items: '{{ user_passwords.results }}'

Comments

2

The task definition for the user module should be different in the latest Ansible version.

tasks:
  - user: name=test password={{ password }} state=present

Comments

2

Combining a few solutions from above, I created a playbook that automatically generates correct password hashes based on plaintext passwords stored in an encrypted, local ansible vault file:

---
- hosts: [your hosts]
  tasks:
  - include_vars: [path to your encrypted vault file]
  - local_action: "command openssl passwd -salt '{{password_salt}}' -1 '{{password}}'"
    register: password_hash
  - user: >
        name=[your username]
        state=present
        password="{{password_hash.stdout}}"

Run this command using "--ask-vault-pass" option to decrypt your vault file (see ansible-vault for info on how to manage an encrypted vault).

Comments

2

How to create encrypted password for passing to password var to Ansible user task (from @Brendan Wood's comment):

openssl passwd -salt 'some_plain_salt' -1 'some_plain_pass'

The result will look like:

$1$some_pla$lmVKJwdV3Baf.o.F0OOy71

Example of user task:

- name: Create user
  user: name="my_user" password="$1$some_pla$lmVKJwdV3Baf.o.F0OOy71"

UPD: crypt using SHA-512 see here and here:

Python

$ python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$saltsalt\$')"

$6$saltsalt$qFmFH.bQmmtXzyBY0s9v7Oicd2z4XSIecDzlB5KiA2/jctKu9YterLp8wwnSq.qc.eoxqOmSuNp2xS0ktL3nh/

Perl

$ perl -e 'print crypt("password","\$6\$saltsalt\$") . "\n"'

$6$saltsalt$qFmFH.bQmmtXzyBY0s9v7Oicd2z4XSIecDzlB5KiA2/jctKu9YterLp8wwnSq.qc.eoxqOmSuNp2xS0ktL3nh/

Ruby

$ ruby -e 'puts "password".crypt("$6$saltsalt$")'

$6$saltsalt$qFmFH.bQmmtXzyBY0s9v7Oicd2z4XSIecDzlB5KiA2/jctKu9YterLp8wwnSq.qc.eoxqOmSuNp2xS0ktL3nh/

1 Comment

Isn't this generating a md5 hashed password? Isn't that insecure?
2

If you'd like to accomplish this as a Ansible ad-hoc command you can do the following:

$ password='SomethingSecret!'
$ ansible 192.168.1.10 -i some_inventory -b -m user -a "name=joe_user \
       update_password=always password=\"{{ \"$password\" | password_hash('sha512') }}\""

Output from above command:

192.168.1.10 | SUCCESS => {
    "append": false,
    "changed": true,
    "comment": "Joe User",
    "group": 999,
    "home": "/home/joe_user",
    "move_home": false,
    "name": "joe_user",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/bash",
    "state": "present",
    "uid": 999
}

Comments

2

@madhead 's answer is not perfect.

You really cannot rely on item's changed state, that's not precise. But this problem could be solved:

- name: get all existing users info
  getent:
    database: passwd

# ---- @madhead 's answer ----
- 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
  # origin:
  # when: madhead.changed
  # improved:
  when: 'madhead' not in getent_passwd and madhead.changed

Comments

2

Mxx's answer is correct but the python crypt.crypt() method is not safe when different operating systems are involved (related to glibc hash algorithm used on your system.)

For example, It won't work if your generate your hash from MacOS and run a playbook on linux. In such case , You can use passlib (pip install passlib to install locally).

from passlib.hash import md5_crypt
python -c 'import crypt; print md5_crypt.encrypt("This is my Password,salt="SomeSalt")'
'$1$SomeSalt$UqddPX3r4kH3UL5jq5/ZI.'

Comments

1

Neither of the solutions worked directly on my Mac controlling Ubuntu. So for others' sake, combining Mxx and JoelB answers, here is the current Python 3 solution:

pip3 install passlib

python3 -c 'from passlib.hash import md5_crypt; \
      print(md5_crypt.encrypt("This is my Password", salt="SomeSalt"))'

The result will be $1$SomeSalt$UqddPX3r4kH3UL5jq5/ZI., as in Mxx' answer.

Better still, use SHA512 instead of MD5:

python3 -c 'from passlib.hash import sha512_crypt; \
      print(sha512_crypt.encrypt("This is my Password", salt="SomeSalt"))' 

Result:

$6$rounds=656000$SomeSalt$oYpmnpZahIsvn5FK8g4bDFEAmGpEN114Fe6Ko4HvinzFaz5Rq2UXQxoJZ9ZQyQoi9zaBo3gBH/FEAov3FHv48

Comments

1

I have created an ansible-playbook that allows you to create a linux account that allows password authentication.

See AnsibleLinuxAccountCreator.

The hashed password is generated using mkpasswd command. I've provided the ways to install mkpasswd on different operating systems.

Here are the steps required to use my script:

  1. Replace <your_user_name> and <your_password> inside run.sh with your desired user name and password.

  2. Change the connection information in inventory so that ansible can connect to the machine to create a user.

  3. Run ./run.sh to execute the script.

2 Comments

The file is not on GitHub anymore.
I've changed the github link to the correct one.
1

I couldn't get any answers here to work though with vaulted variables. So, for anyone who is using "vaulted" passwords and getting this error:

"msg": "Unexpected templating type error occurred on ({{ jinja template stuff }}): expected string or bytes-like object"

You need to pipe '|' the vault encrypted variable through "string" to get it to work.

- name: Creating user "{{ uusername }}" with admin access
  user: 
    name: {{ uusername }}
    password: "{{ upassword | string | password_hash('sha512', (upassword_salt | string)) }}"
    groups: admin append=yes
  when:  assigned_role  == "yes"

Based on Ansible solution for GitHub issue #24425

Building off code from an answer by thinkingmonster and comment by michael-aicher.

Comments

0

I know that I'm late to the party, but there is another solution that I'm using. It might be handy for distros that don't have --stdin in passwd binary.

- hosts: localhost
  become: True
  tasks:
    - name: Change user password
      shell: "yes '{{ item.pass }}' | passwd {{ item.user }}"
      loop:
       - { pass: 123123, user: foo }
       - { pass: asdf, user: bar }
      loop_control:
        label: "{{ item.user }}"

Label in loop_control is responsible for printing only username. The whole playbook or just user variables (you can use vars_files:) should be encrypted with ansible-vault.

Comments

0
- name: Create user
  hosts: all
  become: true
  tasks:
  
- name: "Creating Users and set password"    
  user:
    name: <username>
    update_password: always
    password:  password_hash('sha512')
    groups: sudo

Comments

-1

Well I'am totally late to party :) I had the need for ansible play that creates multiple local users with randoms passwords. This what I came up with, used some of examples from top and put them together with some changes.

create-user-with-password.yml

---
# create_user playbook

- hosts: all
  become: True
  user: root
  vars:
#Create following user
   users:
    - test24
    - test25
#with group
   group: wheel
  roles:
    - create-user-with-password

/roles/create-user-with-password/tasks/main.yml

- name: Generate password for new user
  local_action: shell pwgen -s -N 1 20
  register: user_password
  with_items: "{{ users }}"
  run_once: true

- name: Generate encrypted password
  local_action: shell python -c 'import crypt; print(crypt.crypt( "{{ item.stdout }}", crypt.mksalt(crypt.METHOD_SHA512)))'
  register: encrypted_user_password
  with_items: "{{ user_password.results }}"
  run_once: true

- name: Create new user with group
  user:
    name: "{{ item }}"
    groups: "{{ group }}"
    shell: /bin/bash
    append: yes
    createhome: yes
    comment: 'Created with ansible'
  with_items:
    - "{{ users }}"
  register: user_created

- name: Update user Passwords
  user:
    name: '{{ item.0 }}'
    password: '{{ item.1.stdout }}'
  with_together:
    - "{{ users }}"
    - "{{ encrypted_user_password.results }}"
  when: user_created.changed

- name: Force user to change the password at first login
  shell: chage -d 0 "{{ item }}"
  with_items:
    - "{{ users }}"
  when: user_created.changed

- name: Save Passwords Locally
  become: no
  local_action: copy content={{ item.stdout }} dest=./{{ item.item }}.txt
  with_items: "{{ user_password.results }}"
  when: user_created.changed

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.