Securing Ansible: Preventing Password Leakage to Syslog When Using lineinfile/blockinfile Modules


3 views

When managing sensitive data like passwords with Ansible's file modification modules, the default logging behavior can create security vulnerabilities. The issue manifests when using:

ansible localhost -m blockinfile -a 'dest=/tmp/config create=yes block="DB_PASSWORD = {{db_pass}}"' -e 'db_pass=S3cr3tP@ss'

This results in full credential exposure in syslog:

ansible-blockinfile: Invoked with [...] block=DB_PASSWORD = S3cr3tP@ss

1. Using no_log Parameter

The most effective solution is the no_log: true parameter in playbooks:

- name: Secure password injection
  blockinfile:
    path: /etc/app.conf
    block: |
      [database]
      password = {{ vaulted_db_password }}
    marker: "# {mark} ANSIBLE MANAGED BLOCK"
  no_log: true

2. Combining with Ansible Vault

For maximum security, combine with encrypted variables:

# In vars/main.yml
db_creds: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          323864656538633165343939...

# In tasks/main.yml
- lineinfile:
    path: /etc/secrets.conf
    line: "API_KEY={{ db_creds.api_key }}"
  no_log: true

Template Files with Restricted Permissions

Instead of inline modification:

- template:
    src: secrets.conf.j2
    dest: /etc/secrets.conf
    mode: 0600
    owner: root
    group: root
  no_log: true

Using the copy Module with Content Filtering

- name: Secure credential deployment
  copy:
    content: "{{ lookup('template', 'credentials.j2') }}"
    dest: /secure/credentials.cfg
    mode: 0400
  no_log: true

When troubleshooting is needed:

- debug:
    msg: "Configuration was updated"
  when: config_change.changed
  no_log: true
  • Configure rsyslog to filter sensitive patterns
  • Implement separate logging for Ansible operations
  • Use syslog-ng's message content filtering

When working with sensitive data in Ansible (particularly credentials), the default logging behavior can inadvertently expose passwords in system logs. This occurs because Ansible's modules log their parameters by default, which becomes problematic when using modules like lineinfile or blockinfile to manage credential files.

Here's a typical scenario where credentials get logged:

ansible localhost -m blockinfile -a 'dest=/tmp/ansible_password_leak create=yes block="Password = {{password}}"' -e 'password=secret'

The syslog entry contains all module parameters, including our sensitive data:

ansible-blockinfile: Invoked with ... block=Password = secret ...

1. Using no_log Parameter

The most straightforward solution is using the no_log: true parameter in your playbook:

- name: Secure credential file modification
  blockinfile:
    path: /etc/application/credentials.conf
    block: |
      username: appuser
      password: {{ db_password }}
    marker: "# {mark} ANSIBLE MANAGED BLOCK"
  no_log: true

2. Environment Variable Control

For system-wide control, set these environment variables:

export ANSIBLE_NO_LOG=True
export ANSIBLE_LOG_PATH=/dev/null

3. Using Ansible Vault

For a more secure approach, encrypt sensitive data with Ansible Vault:

ansible-vault encrypt_string 'secretpassword' --name 'vaulted_password'

Then reference it in your playbook:

- name: Modify config with vaulted password
  lineinfile:
    path: /etc/config.cfg
    line: "DB_PASSWORD={{ vaulted_password }}"
    regexp: "^DB_PASSWORD="
  no_log: true
  • Always use no_log when handling sensitive data
  • Consider implementing a custom callback plugin to filter sensitive data
  • For enterprise deployments, evaluate the log_filter plugin in ansible.cfg
  • Regularly audit your logs for accidental credential exposure

For fine-grained control, create a custom callback plugin (callback_plugins/sanitize_log.py):

from ansible.plugins.callback.default import CallbackModule

class CallbackModule(CallbackModule):
    def v2_runner_on_ok(self, result):
        if 'no_log' in result._task_fields and result._task_fields['no_log']:
            result._result = {'censored': 'the output has been hidden...'}
        super(CallbackModule, self).v2_runner_on_ok(result)

Then configure ansible.cfg:

[defaults]
callback_whitelist = sanitize_log