Creating a new user and password with Ansible

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?

288898 次浏览

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.

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

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

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"

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

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

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/

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

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

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

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)

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}}

Mxx's answer is correct but you 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.'

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.

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"

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

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
}

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.

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

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 }}'

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') }}"

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.

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"

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.

- 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

@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