Variables
Where to put variables
I always store all my variables at the following three locations:
- group_vars folder
- host_vars folder
- defaults folder in roles
The defaults-folder contains only default values for all variables used by the role.
Naming Variables
The variable name should be self-explanatory (as brief as possible, as detailed as necessary), use multiple words and don't shorten things.
- Multiple words are separated with underscores (
_
) - List-Variables are suffixed with
_list
- Dictionary-Variables are suffixed with
_dict
- Boolean values are provided with lowercase
true
orfalse
Referencing variables
After a variable is defined, use Jinja2 syntax to reference it. Jinja2 variables use double curly braces ({{
and }}
).
Use spaces after and before the double curly braces and the variable name.
When referencing list or dictionary variables, try to use the bracket notation instead of the dot notation.
Bracket notation always works and you can use variables inside the brackets. Dot notation can cause problems because some keys collide with attributes and methods of python dictionaries.
Encrypted variables
Tip
All variables with sensitive content should be vault-encrypted.
Although encrypting just the value of a single variable is possible (with ansible-vault encrypt_string
), you should avoid this. Store all sensitive variables in a single file and encrypt the whole file.
For example, to store sensitive variables in group_vars
, create the subdirectory for the group and within create two files named vars.yml
and vault.yml
.
Inside of the vars.yml
file, define all of the variables needed, including any sensitive ones. Next, copy all of the sensitive variables over to the vault.yml
file and prefix these variables with vault_
. Adjust the variables in the vars file to point to the matching vault_ variables using Jinja2 syntax, and ensure that the vault file is vault encrypted.
---
# file: group_vars/database_servers/vars.yml
username: "{{ vault_username }}"
password: "{{ vault_password }}"
---
# file: group_vars/database_servers/vault.yml
# NOTE: THIS FILE MUST ALWAYS BE VAULT-ENCRYPTED
vault_username: admin
vault_password: ex4mple
I can still read the credentials...?
Obviously, you wouldn't be able to read the content of the file group_vars/database_servers/vault.yml
, as the file would be encrypted.
This only demonstrates how the variables are referencing each other.
The encrypted vault.yml
file looks something like this:
$ANSIBLE_VAULT;1.1;AES256
30653164396132376333316665656131666165613863343330616666376264353830323234623631
6361303062336532303665643765336464656164363662370a663834313837303437323332336631
65656335643031393065333366366639653330353634303664653135653230656461666266356530
3935346533343834650a323934346666383032636562613966633136663631636435333834393261
36363833373439333735653262306331333062383630623432633134386138656636343137333439
61633965323066633433373137383330366466366332626334633234376231393330363335353436
62383866616232323132376366326161386561666238623731323835633237373036636561666165
36363838313737656232376365346136633934373861326130636531616438643036656137373762
39616234353135613063393536306536303065653231306166306432623232356465613063336439
34636232346334386464313935356537323832666436393336366536626463326631653137313639
36353532623161653266666436646135396632656133623762643131323439613534643430636333
31386635613238613233
Defining variables this way makes sure that you can still find them with grep.
Encrypting files can be done with this command:
Once a variable file is encrypted, it should not be decrypted again (because it may get committed unencrypted). View or edit the file like this:
Warning
There are modules which will print the values of encrypted variables into STDOUT while using them or with higher verbosity. Be sure to check the parameters and return values of all modules which use encrypted variables!
A good example is the ansible.builtin.user
module, it automatically obfuscates the value for the password parameter, replacing it with the string NOT_LOGGING_PASSWORD
.
The ansible.builtin.debug
module on the other hand is a bad example, it will output the password in clear-text (well, by design, but this is not what you would expect)!
Success
Always add the no_log: true
key-value-pair for tasks that run the risk of leaking vault-encrypted content!
---
- name: Using no_log parameter
hosts: database_servers
tasks:
- name: Add user
ansible.builtin.user:
name: "{{ username }}"
password: "{{ password }}"
- name: Debugging a vaulted variable with no_log
ansible.builtin.debug:
msg: "{{ password }}"
no_log: true
Output of playbook run
Using the stdout_callback: community.general.yaml for better readability, see Ansible configuration for more info.
$ ansible-playbook nolog.yml -v
[...]
TASK [Add user] *********************************************
[WARNING]: The input password appears not to have been hashed. The 'password'
argument must be encrypted for this module to work properly.
ok: [db_server1] => changed=false
append: false
comment: ''
group: 1002
home: /home/admin
move_home: false
name: admin
password: NOT_LOGGING_PASSWORD
shell: /bin/bash
state: present
uid: 1002
ASK [Debugging a vaulted Variable with no_log] *************
ok: [db_server1] =>
censored: 'the output has been hidden due to the fact that ''no_log: true'' was specified for this result'
[...]
Hint
Observing the output from the "Add user" task, you can see that the value of the password parameter is not shown. The warning from the "Add user" task stating an unencrypted password is related to not having hashed the password. You can achieve this by using the password_hash filter:
This example uses the stringmysecretsalt
for salting, in cryptography, a salt is random data that is used as an additional input to a one-way function. Consider using a variable for the salt and treat it the same as the password itself!
In this example, the salt is stored in a variable, the same way as the password itself. If you hashed the password, the warning will disappear.
- name: Not using no_log parameter
hosts: database_servers
become: true
tasks:
- name: Add user
ansible.builtin.user:
name: "{{ username }}"
password: "{{ password }}"
- name: Debugging a vaulted Variable
ansible.builtin.debug:
msg: "{{ password }}"
Output of playbook run
$ ansible-playbook nolog.yml -v
[...]
TASK [Add user] *********************************************
[WARNING]: The input password appears not to have been hashed. The 'password'
argument must be encrypted for this module to work properly.
ok: [db_server1] => changed=false
append: false
comment: ''
group: 1002
home: /home/admin
move_home: false
name: admin
password: NOT_LOGGING_PASSWORD
shell: /bin/bash
state: present
uid: 1002
ASK [Debugging a vaulted Variable with no_log] *************
ok: [db_server1] =>
msg: ex4mple
[...]
Prevent unintentional commits
Use a pre-commit hook to prevent accidentally committing unencrypted sensitive content. The easiest way would be to use the pre-commit framework/tool with the following configuration:
repos:
- repo: https://github.com/timgrt/pre-commit-hooks
rev: v0.2.1
hooks:
- id: check-vault-files
Take a look at the development section for additional information.
Disable variable templating
Sometimes, it is necessary to provide special characters like curly braces. The most common use cases include passwords that allow special characters like {
or %
, and JSON arguments that look like templates but should not be templated.
Abstract
When handling values returned by lookup plugins, Ansible uses a data type called unsafe
to block templating. Marking data as unsafe prevents malicious users from abusing Jinja2 templates to execute arbitrary code on target machines. The Ansible implementation !unsafe
ensures that these values are never templated. You can use the same unsafe data type in variables you define, to prevent templating errors and information disclosure.
For complex variables such as hashes or arrays, use !unsafe
on the individual elements, take a look at this example for AWX/AAP automation.
For Jinja2 templates this behavior can be achieved with the {% raw %}
and {% endraw %}
tags.
Consider the following template where name_of_receiver_group should be replaced with a variable you set elsewhere, but details contains stuff which should stay as it is: