Vault covers the repository. no_log covers the run’s output. Between them
they close two of the fourteen hops in this part’s leak table, which is why
they belong together and why neither one finishes the job.
no_log is a boolean keyword settable on a task, a block or a play:
Read-only / Safethe three scopes
- name: One task
ansible.builtin.uri:
url: https://api.example.com/v2/deploy
headers:
Authorization: "Bearer {{ vault_api_token }}"
no_log: true
- name: A block of related tasks
block:
- name: Write the credential file
ansible.builtin.template:
src: credentials.j2
dest: /etc/app/credentials
mode: '0600'
no_log: true
# And at play level - which this lesson argues against.
There is also a configuration setting that turns it on globally:
Read-only / Safethe global switch, off by default— Executed on ansible-core 2.21.3 with no configuration file present.
$ ansible-config dump | grep DEFAULT_NO_LOG
DEFAULT_NO_LOG(default) = False
Its documented description is “Toggle Ansible’s display and logging of
task details, mainly used to avoid security disclosures.” Setting it
globally makes every run undiagnosable, which is why the rest of this
lesson is about scoping.
What it does suppress
Read-only / Safethe result is replaced, not redacted— Executed on ansible-core 2.21.3. The task is an assert whose condition is false, with no_log set.
$ ansible-playbook -i localhost, leak2.yml
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden
due to the fact that 'no_log: true' was specified for this result",
"changed": false}
The entire result dictionary is replaced by that one key. Not the secret
field — the whole thing.
And it holds under verbosity. Verified on 2.21.3 with a module carrying the
credential in its arguments:
Read-only / Safecounting occurrences of the secret across verbosity levels— Executed on 2.21.3. The task passes REPLACE_ME as a module argument with no_log set. Each number is a grep count over the full output.
$ for v in '' '-vvv'; do ansible-playbook -i localhost, nolog4.yml $v | grep -c REPLACE_ME; done
0
0
So no_log covers task arguments and task results, in normal output and in
-v through -vvv. That is a real and useful control.
The route it does not cover, measured
Now the part that is not in most treatments of this keyword.
Read-only / Safethree no_log tasks, all failing— Executed on ansible-core 2.21.3. Every task has no_log: true. The credential is the literal string REPLACE_ME.
TASK [fail module with the secret in the message] ******************
[ERROR]: Task failed: Action failed: could not connect using REPLACE_ME
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden ..."}
TASK [assert whose fail_msg names the secret] *********************
[ERROR]: Task failed: Action failed: bad credential REPLACE_ME
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden ..."}
TASK [assert whose fail_msg does NOT name the secret] *************
[ERROR]: Task failed: Action failed: credential check failed
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden ..."}
The documented ANSIBLE_DEBUG exception, reproduced
The upstream documentation states that no_log does not prevent data being
shown when debugging Ansible itself through ANSIBLE_DEBUG. Verified on
2.21.3, and the detail of when it applies is worth having:
Read-only / Safea real module carrying the secret in its arguments— Executed on 2.21.3. The task is ansible.builtin.stat with no_log set and the credential interpolated into its path argument. Each number is a grep count.
Note '_ansible_no_log': True sitting in the very structure that is being
printed. The flag is being carried to the module correctly and the
developer-level debug output ignores it, which is what “debugging Ansible
itself” means.
What it cannot cover at all
no_log controls what Ansible prints. Three things are outside that
entirely:
A task that prints the secret itself. A shell task that echoes a
credential, or a script that logs its own arguments, produces output that
Ansible faithfully suppresses in its result — while the program has already
written it to the target’s stdout, its own log file, or wherever else it
chose.
A file the task writes. If the point of the task is to place a
credential in /etc/app/credentials, the credential is in
/etc/app/credentials afterwards. no_log has no opinion about file
modes. Set mode: '0600' and an owner, and take the file-permissions
material from the Linux course seriously — this is the hop where most
credentials on a managed node actually sit.
The managed node’s own logging.sudo logs commands. auditd logs
what it is configured to log. The service you just configured may log its
connection string on startup. Ansible cannot reach any of that.
The cost, and how to keep it small
Read-only / Safewhat a failed no_log task tells you— Executed on 2.21.3. This is the complete diagnostic information available from the result.
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden
due to the fact that 'no_log: true' was specified for this result",
"changed": false}
A task name and the word censored. No return code, no stderr, no module
message, nothing about which host condition caused it.
Which is the argument against play-level no_log, and it is not a
theoretical one. A play with no_log: true at play level, failing in
production, gives the on-call operator a task name and nothing else. They
cannot tell a network failure from a permission failure from a genuinely
wrong credential, at three in the morning, on a system they may not have
seen before.
Knowledge check
Knowledge check · 4 questions
Q1. A task has no_log: true and an assert whose fail_msg interpolates the credential. The assertion fails. What appears in the output?
Q2. Which arrangement keeps a credential censored while leaving a failure diagnosable?
Q3. Which are outside what no_log can control? Select all that apply.
Q4. A module that declares a password parameter sensitive in its argument specification is preferable to task-level no_log, because it censors that parameter while leaving the rest of the result readable.
Passing score: 75%. Answers are checked in this browser.