Secure Multi-Host Ansible Deployment: How to Handle Per-Machine Sudo Passwords


2 views

When managing heterogeneous environments where each server has distinct sudo passwords, Ansible's default -K (--ask-become-pass) behavior falls short. The system attempts to reuse a single sudo password across all hosts, causing authentication failures when passwords differ.

Option 1: SSH Agent Forwarding with Sudo Password Caching

# In ~/.ansible.cfg
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = True

Option 2: Per-Host Vault Variables

# group_vars/all/vault.yml
ansible_become_pass: "{{ vault_sudo_pass }}"

# Command to encrypt:
ansible-vault encrypt group_vars/all/vault.yml

# To run:
ansible-playbook playbook.yml --ask-vault-pass

Create a custom vars plugin (requires Python):

# In ansible.cfg
vars_plugins = /path/to/plugins

# per_host_passwords.py
from ansible.plugins.vars import BaseVarsPlugin

class VarsModule(BaseVarsPlugin):
    def get_vars(self, loader, path, entities):
        sudo_passwords = {
            'host1': 'secret1',
            'host2': 'secret2'
        }
        return {'ansible_become_pass': sudo_passwords.get(entities[0].name)}

Always ensure:

  • Vault files are encrypted at rest
  • Custom plugins have strict file permissions (600)
  • Limit sudo privileges in /etc/sudoers
# inventory.ini
[web_servers]
host1 ansible_become_pass="{{ lookup('env', 'HOST1_PASS') }}"
host2 ansible_become_pass="{{ lookup('vault', 'host2_pass') }}"

# Execution:
HOST1_PASS=secret1 ansible-playbook -i inventory.ini deploy.yml

When managing heterogeneous environments with Ansible, the sudo password heterogeneity problem emerges as a significant operational hurdle. Unlike SSH key authentication which scales seamlessly, sudo password prompts create a bottleneck in parallel execution. Let's examine why the traditional -K approach fails:

# Typical failure scenario
ansible-playbook -i inventory.ini deploy.yml --ask-become-pass
# Host1 success | Host2-N failure due to password mismatch

1. Vault-Protected Host Variables

The most secure approach combines Ansible Vault with host variables:

# inventory.ini
[web_servers]
host1 ansible_become_pass="{{ vaulted_host1_pass }}"
host2 ansible_become_pass="{{ vaulted_host2_pass }}"

# Encrypt with vault
ansible-vault encrypt_string 's3cret1' --name 'vaulted_host1_pass'

2. Dynamic Password Retrieval

For environments with existing secret management:

# playbook.yml
- name: Retrieve sudo passwords
  hosts: all
  tasks:
    - name: Fetch per-host sudo creds
      set_fact:
        ansible_become_pass: "{{ lookup('hashi_vault', 
          'secret=sshcreds/data/{{ inventory_hostname }}:password') }}"

3. Privilege Escalation Proxy

Create a dedicated bastion for privilege escalation:

# ansible.cfg
[privilege_escalation]
become_method = enable
become_flags = '-u {{ vaulted_user }} -p {{ vaulted_pass }}'
become_plugin = teleport_proxy

4. SSH Certificate Authority

Transition to short-lived certificates:

# Issue host-specific certs
ssh-keygen -s ca_key -I host1 -n root host1.pub
# ansible.cfg
[ssh_connection]
ssh_args = -o CertificateFile=creds/{{ inventory_hostname }}-cert.pub

When evaluating these solutions, consider:

  • Audit trail requirements (vault provides clearest logging)
  • Rotation frequency (dynamic lookup most flexible)
  • Performance impact (certificates add ~200ms per host)

Combining vault with dynamic inventory:

# group_vars/all/vault.yml
host_passwords:
  host1: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          306132633632...
          
# tasks/main.yml
- name: Apply host-specific sudo
  ansible.builtin.raw: "echo {{ host_passwords[inventory_hostname] }} | sudo -S command"
  become: false