46

I am setting up a MySQL server and want Ansible to set the mysql-root password during installation.

With the help of the internet I came up with this solution:

- name: Set MySQL root password before installing
  debconf: name='mysql-server' question='mysql-server/root_password' value='{{mysql_root_pwd | quote}}' vtype='password'
- name: Confirm MySQL root password before installing
  debconf: name='mysql-server' question='mysql-server/root_password_again' value='{{mysql_root_pwd | quote}}' vtype='password'
- name: Install Mysql
  apt: pkg=mysql-server state=latest

mysql_root_pwd is a variable loaded from the Ansible Vault. This runs fine, but now on the server there are many lines in the log:

Apr 10 14:39:59 servername ansible-debconf: Invoked with value=THEPASSWORD vtype=password question=mysql-server/root_password name=mysql-server unseen=None
Apr 10 14:39:59 servername ansible-debconf: Invoked with value=THEPASSWORD vtype=password question=mysql-server/root_password_again name=mysql-server unseen=None

How can I stop Ansible from writing clear text passwords to the logfiles?

Mxx
  • 2,390
claus
  • 681

9 Answers9

58

To prevent a task with confidential information from being logged, in syslog or other, set no_log: true on the task:

- name: secret stuff
  command: "echo {{secret_root_password}} | sudo su -"
  no_log: true

The running of the task will still be logged, but with little details. Also, the module used has to support no_log, so test custom modules.

See Ansible FAQ for further details. It can be applied to an entire playbook, however the output gets a little nasty with "censored!" messages.

Murage
  • 107
10

The observed behaviour seems to be a bug in the debconf module. I filed a bug report.

The user bcoca at github pointed out that one can use the no_log: true directive in tasks, that set passwords, to prevent logging. This is a workaround, that works for me until the bug is fixed.

claus
  • 681
6

There is a better way than just no_log: True

- name: write in string variables login and password
  set_fact:
    temp_user: "{{ USER_VAR }}"
    temp_pass: "{{ PASSWORD_VAR }}"
  • name: Your operation with password in output shell: '/opt/hello.sh' ignore_errors: True no_log: True register: myregister

  • debug: msg: '{{ myregister.stderr | regex_replace(temp_user) | regex_replace(temp_pass) }}' when: myregister.stderr != ""

  • debug: msg: '{{ myregister.stdout | regex_replace(temp_user) | regex_replace(temp_pass) }}' when: myregister.stdout != ""

  • fail: msg: "error shell /opt/hello.sh" when: myregister.stderr != ""

As you can see, you need to add:

ignore_errors: true
no_log: true

And then make the output of the result of the command with regex_replace, where:

USER_VAR - login variable

PASSWORD_VAR - password variable

With this approach, you will not only hide the passwords and logins, but also get the output of your operation

TheDESTROS
  • 415
  • 1
  • 4
  • 11
1

As per Ansible docs:

log_path

If present and configured in ansible.cfg, Ansible will log information about executions at the designated location. Be sure the user running Ansible has permissions on the logfile:

log_path=/var/log/ansible.log 

This behavior is not on by default. Note that ansible will, without this setting, record module arguments called to the syslog of managed machines. Password arguments are excluded.

Sounds like setting log_path on your control node will result in not having logs on destination nodes.

chicks
  • 3,915
  • 10
  • 29
  • 37
Droopy4096
  • 710
  • 4
  • 8
1

I solved by upgrading Ansible version to 1.6.1

sudo pip install ansible==1.6.1
0x3bfc
  • 127
1

This is an additon to the answer of TheDESTROS from this thread:

  1. write a template, which wraps the command with a secret:

wrapper-script.sh.j2

echo {{ secret_eg_from_ansible_vault }} | su - "ls -l"
  1. Invoke the wrapper script and remove it at once:
- name: create template
  template:
    src: wrapper-script.sh.j2
    dest: /tmp/wrapper-script.sh
    mode: 0700
  no_log: True
- name: invoke command with secret and remove it
  shell: /tmp/wrapper-script.sh; rm -f /tmp/wrapper-script.sh

You need a little bit less code and can thee stdout of the commands in your logs. There is only one caveeat, if a secret is in the commands stdout. If you want to avoid the external template, the copy module with the parameter content might help to write a small wrapper script in the fly.

1

The no_log: true approach is to be used as last-resort if other attempts fail because it will make the task execution totally opaque and you will have no clue when it fail.

Security practices recommend giving credentials from stdin or when not possible using credential files (or even executables).

Here is an example on how to perform a secure podman login by avoiding exposing the password:

- name: secured login
  become: true
  command: >
    podman login --username={{ user }} --password-stdin ...
  args:
    stdin: "{{ secret }}"
  register: result

With this, the secret will not be exposed, in result but you will still be able to see the output of the command.

Most of tools needing login do implement one of the more secured approaches mentioned. Using credentials on CLI in code is like have 123456 as your bank password.

sorin
  • 8,454
0

The environment argument in the playbook is suitable for keeping a secret hidden. The secret is not printed even with -vvvv. It is available for the whole play.

- hosts: control_host
  gather_facts: no
  become: no
  environment:
    PASSWORD: "{{ password }}"
  tasks:

   - local_action: "shell ./foobar.sh ${PASSWORD}"

   - local_action: "shell echo ${PASSWORD}"

output:

ansible-playbook -i ... playbooks/demo.yml -v

PLAY [control_host] *********************************************************************************

TASK [shell] *********************************************************************************
changed: [localhost -> localhost] => {
    "changed": true,
    "cmd": "./foobar.sh ${PASSWORD}",
    "delta": "0:00:00.013467",
    "end": "2020-04-03 17:42:03.950534",
    "rc": 0,
    "start": "2020-04-03 17:42:03.937067"
}

TASK [shell] *********************************************************************************
changed: [localhost -> localhost] => {
    "changed": true,
    "cmd": "echo ${PASSWORD}",
    "delta": "0:00:00.005925",
    "end": "2020-04-03 17:42:04.319085",
    "rc": 0,
    "start": "2020-04-03 17:42:04.313160"
}

STDOUT:

my_secret_password
selle
  • 101
0

As I needed something similar, I created a test.yml playbook that hides things coming from a .secret file.

---
  • hosts: localhost

    vars: hello_var: "{{ lookup('ini', 'hello_var type=properties file=config.secret') }}" output_file: "/test.txt"

    dirty_lines: ['cmd'] dirty_values: ['hello_env', 'hello_var'] dirty_word: "SECRET"

    tasks:

    • name: Write output file (has dirty content possibly, not showing output) shell: "echo "{{ hello_var }}" > {{ output_file }}"
      register: output_raw ignore_errors: true

      don't forget

      no_log: true

    #- debug:

    var: output_raw

    • name: Read secrets for 'Write output file' shell: ansible-vault view --vault-password-file ./vault.secret ./config.secret register: ansible_secret_content

      don't forget

      no_log: true

    • name: Filter passwords from the secrets for 'Write output file' set_fact: secrets: "{{ secrets|default([]) + [ item.split('=')[1] ] }}" when: item.split('=')[0] in dirty_values with_items: "{{ ansible_secret_content.stdout_lines | list }} "

      don't forget

      no_log: true

    #- debug:

    var: secrets

    • name: Init cleaned output from 'Write output file' set_fact: output_cleaned: "{{ output_cleaned|default({}) | combine({item.key : item.value}) }}" when: item.key not in dirty_lines with_dict: "{{ output_raw }}"

      don't forget

      no_log: true

    #- debug:

    var: output_cleaned

    • name: Init dirty output from 'Write output file' set_fact: output_dirty: "{{ output_dirty|default({}) | combine({item.key : item.value}) }}" when: item.key in dirty_lines with_dict: "{{ output_raw }}"

      don't forget

      no_log: true

    #- debug:

    var: output_dirty

    • name: Clean the dirt for 'Write output file' set_fact: output_dirty: "{{ output_dirty|default({}) | combine({item[0] : output_dirty[item[0]].replace(item[1], dirty_word)}) }}" loop: "{{ dirty_lines | product(secrets) | list }}"

      loops over the carthesian product of dirty_lines with dirty_values, providing a sort of nested loop

      don't forget

      no_log: true

    #- debug:

    var: output_dirty

    • name: Combine output for 'Write output file' set_fact: output: "{{ output|default({}) | combine(output_cleaned) | combine(output_dirty) }}"

      don't forget

      no_log: true

    • name: Cleaned output for 'Write output file' (will show as failed if the original command failed also) debug: var: output

      Picking up the original error, if any

      failed_when:
      output.stderr != ''

#---

output:

PLAY [localhost] ***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* ok: [localhost]

TASK [Write output file (has dirty content possibly, not showing output)] ****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** fatal: [localhost]: FAILED! => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": true} ...ignoring

TASK [Read secrets for 'Write output file'] ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************ changed: [localhost]

TASK [Filter passwords from the secrets for 'Write output file'] *************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) ok: [localhost]

TASK [Init cleaned output from 'Write output file'] **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** skipping: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost]

TASK [Init dirty output from 'Write output file'] ****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** ok: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) skipping: [localhost] => (item=None) ok: [localhost]

TASK [Clean the dirt for 'Write output file'] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** ok: [localhost] => (item=None) ok: [localhost] => (item=None) ok: [localhost]

TASK [Combine output for 'Write output file'] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** ok: [localhost]

TASK [Cleaned output for 'Write output file' (will show as failed if the original command failed also)] ************************************************************************************************************************************************************************************************************************************************************************************************************************************ fatal: [localhost]: FAILED! => { "failed_when_result": true, "output": { "changed": true, "cmd": "echo "SECRET" > /test.txt", "delta": "0:00:00.001262", "end": "2021-03-18 14:36:20.248332", "failed": true, "msg": "non-zero return code", "rc": 2, "start": "2021-03-18 14:36:20.247070", "stderr": "/bin/sh: 1: cannot create /test.txt: Permission denied", "stderr_lines": [ "/bin/sh: 1: cannot create /test.txt: Permission denied" ], "stdout": "", "stdout_lines": [] } }

PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* localhost : ok=8 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=1