Update to per-host passwords with Ansible
I recently discovered that my previous configuration for per-host sudo passwords, where I set ansible_become_pass
to a lookup based on inventory_hostname
, fails for delegated tasks. This is because inventory_hostname
remains set to the original host, not the delegated host where the command is run, so the wrong password is used.
The original configuration was to set the password as an inventory variable:
ansible_become_password: "{{ lookup('community.hashi_vault.vault_read', 'kv/hosts/' + inventory_hostname + '/users/ansible').data.password }}"
To get around this, I changed to a task that sets the variable using ansible.builtin.set_fact
on the host after looking up the password (delegated to localhost, as that should be able to do the vault lookup). This little test playbook demonstrates it working (and also demonstrates only setting it if the password is in the vault, which neatly deals with my current issue of some hosts being setup this way and some not):
---
- hosts: all
gather_facts: true
tasks:
- name: Ansible sudo password is retrieved from vault, if known
delegate_to: localhost
community.hashi_vault.vault_read:
# So many things can determine the remote username (
# ansible_user variable, SSH_DEFAULT_USER environment
# variable, .ssh/config, etc. etc.) it's safer to user the
# discovered fact.
path: kv/hosts/{{ inventory_hostname }}/users/{{ ansible_facts.user_id }}
register: sudo_pass
# No password in vault is fine - will just not set it.
failed_when: false
- name: sudo password is set for host, if found in the vault
ansible.builtin.set_fact:
ansible_become_password: '{{ sudo_pass.data.data.password }}'
when: "'data' in sudo_pass"
- become: true
ansible.builtin.command: whoami
register: whoami_output
- ansible.builtin.debug: var=whoami_output
...
To use, I just copied the relevant (first two) tasks into my main site.yaml
file - I added it to my existing play (which already uses facts) that groups hosts by domain:
- hosts: all:!dummy
tags: always # Always add extra groups and lookup sudo password
tasks:
- name: Group hosts by domain (mainly for environment detection)
ansible.builtin.group_by:
key: domain_{{ ansible_facts.domain | replace('.', '_') }}
- name: Ansible sudo password is retrieved from vault, if known
delegate_to: localhost
community.hashi_vault.vault_read:
# So many things can determine the remote username (
# ansible_user variable, SSH_DEFAULT_USER environment
# variable, .ssh/config, etc. etc.) it's safer to user the
# discovered fact.
path: kv/hosts/{{ inventory_hostname }}/users/{{ ansible_facts.user_id }}
register: sudo_pass
# No password in vault is fine - will just not set it.
failed_when: false
- name: sudo password is set for host, if found in the vault
ansible.builtin.set_fact:
ansible_become_password: '{{ sudo_pass.data.data.password }}'
when: "'data' in sudo_pass"
I also added it to my bootstrap, reinstall and install updates playbooks. The tasks are identical except in boostrap.yaml
, which is hardcoded to the ansible
user as the play it is in is hardcoded to use root
and su
due to the user still being setup. New passwords are set on remote host
and Ansible user can sudo
are existing tasks in bootstrap.yaml
:
- name: New passwords are set on remote host
become: true
ansible.builtin.user:
name: "{{ item }}"
password: "{{ lookup('community.hashi_vault.vault_read', 'kv/hosts/' + inventory_hostname + '/users/' + item).data.password | ansible.builtin.password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}"
loop:
- root
- ansible
- name: New ansible user sudo password is retrieved from vault
delegate_to: localhost
community.hashi_vault.vault_read:
path: kv/hosts/{{ inventory_hostname }}/users/ansible
register: sudo_pass
- name: New sudo password is set for host
ansible.builtin.set_fact:
ansible_become_password: '{{ sudo_pass.data.data.password }}'
- name: Ansible user can sudo
become: true
ansible.builtin.user:
name: ansible
append: true
groups:
- sudo