I’m just the messenger; don’t kill me. The user who asked the question and I both well know the security of logging in via SSH can greatly be improved upon by using SSH keys instead of passwords.
Be that as it may, there was what I thought an interesting case, in that an Ansible installation with a few hundred hosts has the requirement to change root passwords whenever an admin departs the organization. In order for this to be possible, passwords obtained from a password storage will be used to login as root, and they are to then be changed. I was asked how the first part could be accomplished.
In order to ensure I get a fresh SSH connection at each attempt, I begin by disabling fancy stuff:
[ssh_connection]
ssh_args = -C
My first idea was to leverage SSH_ASKPASS
, but that didn’t work because Ansible populates that.
I then decided a vars plugin would do the trick, and that actually worked well with the disadvantage that the variable ansible_password
became known throughout the play, something which doesn’t occur with ansible_password
in the inventory or in host-/group_vars
for example. Hmm. Not nice.
After asking the Fediverse for inspiration, both Roger and I appeared to have arrived at the same solution within seconds of each other. His idea
$ ansible ... -e ansible_password=`cat /etc/ansible.pass`
which suffers from a possible exposure of the password in the process list. I arrived at something like this, which exposes the lookup but not the secret. However, as Tony rightly points out, this is dangerous in that the secret is exposed in some unixes:
$ read -p "Password: " -s pw; export pw
$ ansible ... -e ansible_password='{{ lookup("env", "pw") }}'
Using a pipe
lookup, the password can be read directly from a password manager API program which emits a single-line password to stdout. Not super elegant, but workable to the point of being usable directly in an inventory file, and the variable is not visible in the play.
[webservers]
www13 ansible_password='{{ lookup("pipe", "./pw-manager.sh jane") }}'
Evgeni pushed me in direction of the possibly cleanest solution in the form of connection-password-file
which takes a path or -
to indicate stdin. If I configure a file it should contain the password, if I specify an executable program that shall print the password to stdout. In all cases Ansible strips \r\n
from the value.
[defaults]
nocows = 1
connection_password_file = ./pw-manager.sh
It might be interesting to know that the connection_password_file
is read once per playbook, irrespective of the number of tasks therein.
So, let’s test this. The current password is read by ./pw-manager.sh
, and I wish to set a new root password:
$ python3 -c 'import secrets; print(secrets.token_urlsafe(20))' | tee pw
GuphPMbQOAPnvp_NziFnBtn4DM0
$ cat root.yml
---
- hosts: www13
remote_user: root
gather_facts: no
tasks:
- name: "Alter root user"
user:
name: "root"
password: "{{ password | password_hash('sha512')}}"
$ ansible-playbook root.yml -e password="$(cat pw)"
PLAY [www13] ***********************************************************************************************
TASK [Alter root user] *************************************************************************************
changed: [www13]
PLAY RECAP *************************************************************************************************
www13 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# can I now login with the new password?
$ sshpass -f pw ssh -l root w00 whoami
root
The missing bits and pieces (accessing password store, etc.) are left as an exercise to … You get the point. :)
Further reading
- Passphrases for Humans, video presentation from BSidesLV 2016