New Ansible 2.22 Feature - Secret Masking
A new feature has just been merged in devel for ansible-core, a secret registration and masking system. The initial implementation is in ansible/ansible#87457 and the documentation is in review at ansible/ansible-documentation#3934. We would love feedback from anyone who can try it on devel before release, especially collection maintainers with custom modules or callback plugins.
Big shout out to @pkingstonxyz and @mkrizek for bringing this feature over the time.
What it is
Ansible keeps a registry of secret values for the life of the process. Any text that leaves Ansible through display output (screen and log_path), callback plugin results, or module logging on the managed node has registered secrets replaced with $REDACTED$.
Unlike the old VALUE_SPECIFIED_IN_NO_LOG_PARAMETER on module values with no_log=True, masking is now non-destructive. Variables, module arguments, and task results keep the real value which can be used in registered variables. Only the rendered output through callbacks of display calls will redact those value.
Values are registered automatically from various sources; vault content and passwords, no_log module options, vars_prompt and --ask-* prompts, connection and become passwords, and the password/unvault lookups and vault/unvault filters. Secrets can also be manually registered with the new register_secret filter plugin.
What it looks like
Two new filters, register_secret and mask_secrets, cover anything Ansible does not register for you:
- name: Generate a database password
ansible.builtin.command: openssl rand -base64 32
register:
db_password: _task.result.stdout | register_secret
password_result: _task.result
no_log: true
- name: Usable, but masked in output
ansible.builtin.debug:
msg: "Setting database password to {{ db_password }}"
The last task prints Setting database password to $REDACTED$, and db_password still holds the real value for later tasks. Use mask_secrets when writing output somewhere Ansible does not control, such as a file on a target.
What it means for end users
no_logmodule options no longer returnVALUE_SPECIFIED_IN_NO_LOG_PARAMETERin results. The real value is kept and masked only in output. Update any playbooks or tests that check for that placeholder.- The
no_logtask keyword is unchanged and still censors the whole task result. It does not register anything, so pair it withregister_secretif a later task uses the value. - Secrets shorter than 4 characters are never masked to avoid collisions in normal out, and 4 to 6 character secrets are only masked as whole words. Only exact matches are masked, so hashed or base64 copies are not.
- This is best effort and a safety net, not a replacement for Vault and
no_logif you encounter one of the known limitations
What it means for module and plugin developers
- A new public API in
ansible.module_utils.secretsworks in modules and all controller plugins:
from ansible.module_utils.secrets import register_secret, mask_secrets
token = register_secret(session['token']) # returned unchanged, masked in output
mask_secrets(f"API Token Result {token}") # API Token Result $REDACTED$
PowerShell modules get the same via the Ansible.Secrets C# util with [Ansible.Secrets.SecretMasker]::RegisterSecret() and ::MaskString().
- Plugin options can be marked
secret: trueso the value is registered regardless of which source set it. Use this for any password or token option. - Callback plugins should set
ANSIBLE_SUPPORTS_MASKING = Trueon the class and mask the results themselves. Callbacks that set it receive unmasked results and must mask anything written outsideDisplaywithmask_secrets(). Callbacks that do not set it keep receiving pre-masked results through a compatibility shim that will be deprecated and removed in a future release. heuristic_log_sanitize(),remove_values(), andsanitize_keys()are deprecated for removal in 2.25.AnsibleModule.log()andrun_command()no longer apply the old password heuristics, so register any secret passed on a command line that is not ano_logoption.
Full details, including the length rules and limitations, are in the documentation PR Add documentation for secret masking by jborean93 · Pull Request #3934 · ansible/ansible-documentation · GitHub that is not yet live.
Known Problems
This is a list of known problems in the current implementation that we are aiming to either fix or explicitly document as a known limitation:
- Module journalctl/syslog of invocation args do not redact module options with a value less than 4 characters in suboptions
- This will be fixed to keep compatibility with existing Ansible versions
- We will not be registering these short values as secrets for the display output, only the syslog/journalctl behaviour is being kept
- PR opened Redact syslog secrets regardless of length by jborean93 · Pull Request #87517 · ansible/ansible · GitHub
- A failure in
register_secretsfilter that contains the literal value leaks in the Ansible error origin statement -{{ 12345678 | register_secret }}(fails due to being non-str)- Still trying to find the best way forward for this
I’ll be editing this section so it should stay up to date.