Create a hashed password with 'debug'

as per advise from the documentation of the user module I try to create a hased password like

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

that does not seem to process the ! character well.

$ ansible all -i localhost, -m debug -a "msg={{ 'Secret-1234!' | password_hash('sha512' , 'somesalt') }}"
-bash: !': event not found

Now, I want a ! in the password but when I try to escape that with \! the password I am effectively getting in the end is:

  • Secret-1234\! and not
  • Secret-1234!

how can I solve this?

Quoting is handled at so many levels. The first trick is to get the right combination of characters past bash, so lets skip the password_hash() bit and get the bash quoting right. Bash is trying to turn ! into a reference to part of your history. It does this when ! is unquoted, or when inside double-quoted strings, but not when inside single-quoted strings.

utoddl@tarna2:~$ ansible all -i localhost, -m debug -a "msg={{ 'Start-1234!' }}"
-bash: !': event not found
# ' <-That single quote is to fix syntax highlighting.

utoddl@tarna2:~$ ansible all -i localhost, -m debug -a "msg={{ 'Start-1234\!' }}"
localhost | SUCCESS => {
    "msg": "Start-1234\\!"
}
utoddl@tarna2:~$ ansible all -i localhost, -m debug -a "msg={{ 'Start-1234\\!' }}"
localhost | SUCCESS => {
    "msg": "Start-1234\\!"
}
utoddl@tarna2:~$ ansible all -i localhost, -m debug -a "msg={{ 'Start-1234"'!'"' }}"
localhost | SUCCESS => {
    "msg": "Start-1234!"
}

That last one does the trick. Bash concatenates adjacent strings after quoting, and that expression takes the double-quoted string

"msg={{ 'Start-1234"

and concatenates it with the single-quoted string

'!'

and the following double-quoted string

" }}"

Now you can insert your

| password_hash('sha512' , 'somesalt')

bit into that last double-quoted string component and have the Right Thing™ seen by ansible.

3 Likes

thx. Here for anybody who, like me, needs another 15 minutes to puzzle the above together again :slight_smile:

ansible all -i localhost, -m debug -a "msg={{ 'Start-1234"'!'"' | password_hash('sha512' , 'somesalt') }}"
1 Like

I know this is marked solved already, but wanted to add an alternative solution, which is a habit of mine specifically because of this problem in bash.

Instead of escaping the quotes or the exclamation mark in order to make sure bash passes everything along correctly from cli to ansible, I just invert the order I use my quotes. I.e. start with single quotes and use double quotes for substrings. Bash will treat single-quoted strings more literally, and thus won’t evaluate the exclamation mark.

ansible localhost -i /dev/null -m debug -a 'msg={{ "Start-1234!" | password_hash("sha512", "somesalt") }}'
2 Likes

that works just alike. thnks!

With the inventory being /dev/null we kinda cheat ourselves around the bash issue here somehow?

Nah, that’s irrelevant. I just prefer ansible localhost -i /dev/null vs ansible all -i localhost, because the former uses ansible_connection: local while the latter uses ansible_connection: ssh.

Edit: This uses Ansible’s implicit localhost:

ansible localhost -i /dev/null -m debug -a 'var=ansible_connection'
localhost | SUCCESS => {
    "ansible_connection": "local"
}

Edit: This uses an explicit/literal localhost (which generally resolves to 127.0.0.1 or ::1 or whatever might be in your /etc/hosts file, but also obeys any ssh config rules that apply to localhost as well).

ansible all -i localhost, -m debug -a 'var=ansible_connection'
localhost | SUCCESS => {
    "ansible_connection": "ssh"
}

2 Likes

taking this a step further by incorporating the hash generation into a playbook/tasks file

  • introducing a stepuser_pw variable in `vars/myuser_pw.yml’

  • those 2 tasks

- name: create pw_hash for stepuser
  delegate_to: localhost
  ansible.builtin.debug:
    msg: "{{ stepuser_pw | password_hash('sha512', 'somesalt') }}"
  register: pw_hash

- name: setting password & pw expiry to -1 for 'stepuser'
  ansible.builtin.user:
    name:     "{{ user_item.name }}"
    comment:  "{{ user_item.comment }}"
    shell:    /bin/bash
    home:     "{{ user_item.home }}"
    password: "{{ user_item.password }}"
    password_expire_max: -1
  become: true
  loop:
    - { name: "stepuser" , home: '/home/stepuser' , comment: "sudo user for step" , password: "{{ pw_hash }}" }
  loop_control:
    loop_var: user_item

this fails however with

[WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.

and more concrete:

failed: [hostname] (item={'name': 'stepuser', 'home': '/home/stepuser', 'comment': 'sudo user for step', 'password': {'msg': '$6$somesalt$kP1JGowfJHOYHfpJTqC8qSnjkPfFUKGfEF34VIXTziiaINo72ISD7WqNn05d0oBZlNP4oBgK4PrObT4O1aHM61', 'failed': False, 'changed': False}}) => {"ansible_loop_var": "pw_item", "changed": false, "msg": "usermod: The password field cannot contain a colon character.\n", "name": "stepuser", "pw_item": {"comment": "sudo user for step", "home": "/home/stepuser", "name": "stepuser", "password": {"changed": false, "failed": false, "msg": "$6$somesalt$kP1JGowfJHOYHfpJTqC8qSnjkPfFUKGfEF34VIXTziiaINo72ISD7WqNn05d0oBZlNP4oBgK4PrObT4O1aHM61"}}, "rc": 1}

'password': {'msg': '$6$somesalt$kP1JGowfJHOYHfpJTqC8qSnjkPfFUKGfEF34VIXTziiaINo72ISD7WqNn05d0oBZlNP4oBgK4PrObT4O1aHM61',

so it looks like I can’t get the mere hash $6$somesalt$kP1JGowfJHOYHfpJTqC8qSnjkPfFUKGfEF34VIXTziiaINo72ISD7WqNn05d0oBZlNP4oBgK4PrObT4O1aHM61 accross via the pw_hash variable, but only including the 'msg:' prefix

any idea how to get over this?

I suggest you print (via debug) the value you have in pw_hash. Registered results of a debug task aren’t what you think.

If you’re trying to create a pw_hash variable/fact, you should be using set_fact, not debug.

1 Like

Also, debug tasks always run on the controller anyway. No need to delegate_to.

1 Like

I too recommend using set_fact instead of debug to create your pw_hash variable.

However, I want to point out that the hash does exist in your registered debug variable. When you registered the debug task, it created a dictionary of {'msg': 'string', 'failed': boolean, 'changed': boolean}, where msg is the printed output. To use the hash in the debug registered variable, you’d reference the sub key msg, i.e. {{ pw_hash.msg }}. You would similarly have this problem anytime you use the registered variable of any task. You will get a dictionary of various sub properties that contain all the data you may possibly need.

In this case though, using set_fact to generate your pw_hash would make plenty of sense here. I don’t know how you’re creating your ‘somesalt’ value, but I wanted to also share how I generate salts. The following example will create a unique salt and password hash per host, while also making the hash idempotent so that the hash does not constantly change every time the play is run even though the password is unchanged.

- name: create pw_hash for stepuser
  vars:
    pw_salt: "{{ lookup('ansible.builtin.password', '/dev/null length=22 chars=ascii_letters,digits seed=' ~ inventory_hostname) }}"
  ansible.builtin.set_fact:
    pw_hash: "{{ stepuser_pw | ansible.builtin.password_hash('sha512', pw_salt) }}"

- name: setting password & pw expiry to -1 for 'stepuser'
  ansible.builtin.user:
    name:     "{{ user_item.name }}"
    comment:  "{{ user_item.comment }}"
    shell:    /bin/bash
    home:     "{{ user_item.home }}"
    password: "{{ user_item.password }}"
    password_expire_max: -1
  become: true
  loop:
    - { name: "stepuser" , home: '/home/stepuser' , comment: "sudo user for step" , password: "{{ pw_hash }}" }
  loop_control:
    loop_var: user_item
1 Like