Ansible Vault Tutorial

In previous tutorials we discussed Ansible, a great tool we can use for automation and provisioning. We talked about basic Ansible concepts, we saw some of the most used Ansible modules, how to manage variables and how to perform basic loops in playbooks; now it’s time to see how to protect sensitive information which sometimes may be needed to accomplish some tasks. In order to protect sensitive information when using Ansible, we encrypt them with Ansible Vault.

In this article we discuss the basics of Ansible Vault, and we see how we can use it to encrypt variables or entire playbooks.

In this tutorial you will learn:

  • What is Ansible Vault
  • How to encrypt and decrypt single variables
  • How to encrypt and decrypt whole playbooks
Ansible Vault Tutorial
Introduction to Ansible Vault
Software requirements and conventions used
Category Requirements, Conventions or Software Version Used
System Distribution-independent
Software Ansible-vault
Other None
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command $ – requires given linux-commands to be executed as a regular non-privileged user

Introduction

Ansible Vault is a tool we can use to encrypt and decrypt single variable values or entire playbooks in order to protect sensitive information, which otherwise would be visible as plain text. The userspace application we work with to perform such operations is called ansible-vault, and is included in every standard Ansible installation.

Encrypting variables

Why we may want to encrypt the value of single variables when we can encrypt whole playbooks? Encrypting variables has one big advantage: since the file in which they are used remains in plaintext format, is still easy to read it and modify it. To encrypt the value of a variable, we invoke the ansible-vault utility and use the “encrypt_string” command. Here is an example: suppose in a playbook we want to use a variable called “password”, but we want, for obvious reasons, to avoid storing its value in plain text. To encrypt it using Ansible Vault, we could run the following command:

$ ansible-vault encrypt_string "secret_value_to_encrypt" --name password



We used the “encrypt_string” command of ansible-vault : this command encrypts the provided string (the sensitive content we want to protect – “secret_value_to_encrypt”, in this example), and protects it using a password. The name of the variable is specified via the --name option. As soon as we launch the command, we are prompted to provide and confirm the encryption password. The same password will be requested to decrypt the content:

New Vault password: 
Confirm New Vault password:

As soon as we provide the password, the content will be encrypted. The result of the command will be printed on screen:

password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          64313761333061643861656539643036396538363063343736316362333736313035386636396536
          6564356435343135643161363033626535393832343733320a323633393637643462643866393536
          38363435336539353532356530373033663862363234343035643632306665343165656634303563
          6636613562396531360a333861356337346536323063653964373833633739346664363664396537
          35346537353930316535613638626233343433323039386262363232376230353131

If we don’t want to provide the encryption password interactively, we can write it in a file, and reference the file, instead. Supposing we wrote our password in a file called “password.txt”, for example, we would run:

$ ansible-vault encrypt_string --vault-password-file password.txt "secret_value_to_encrypt" --name password

The password must be written in a single line. As you can see in the example above, the file containing the password is referenced via the --vault-password-file option. The rest of the command remains the same.

Another, potentially more secure option, is to build a script which does interact with our favorite password manager application. The id to be retrieved from the password manager can be specified via the --vault-id option, so the script must accept such option. If a password is required to access the password manager, the script should prompt for it in the terminal, and once the password needed to encrypt/decrypt the variable or file with ansible-vault is retrieved, it must print it to standard output. The script name must end in “-client” or “-client.extension” (e.g “-client.py”). Let’s see an example.

In a previous article we saw how can we organize our password using the “pass” password manager. Such manager is based on the use of GPG keys, and can be used straight from the command line, via the pass utility. Here is an example of a Python script which interacts with this password manager, and can be used with ansible-vault:

#!/usr/bin/env python3

import argparse
import sys
from subprocess import run, CalledProcessError


def parse_args():
    parser = argparse.ArgumentParser(description="Get password from pass password manager")
    parser.add_argument('--vault-id', help='The password to retrieve', default='vault')

    return parser.parse_args()


def main(args):

    try:
        completed_process = run(['pass', args.vault_id], capture_output=True, check=True)
    except CalledProcessError:
        sys.stderr.write('There was an error retrieving the password')
        return 1

    sys.stdout.write(completed_process.stdout.decode('utf8'))
    return 0


if __name__ == '__main__':
    sys.exit(main(parse_args()))

The script above, as requested, accepts the --vault-id option, which is used to communicate the “name” of the id to be retrieved using the password manager. In this case we set the default value of “vault” for the option: it will be used when the user doesn’t provide an identity explicitly. The call to “pass” is made by using the Python subprocess module (take a look at our tutorial on subprocess if you are not familiar with it). Here we used the run method, and set the capture_output and check parameters to True, so that the standard output of the command is captured (it can be later accessed in the “stdout” attribute of the CompletedProcess object returned by the method), and a CalledProcessError exception is raised if the exit status of the command is not 0.



In this case the script doesn’t need to prompt the user for the password by itself, because the pass utility will do it. By the way, to be sure the password is asked in the command line we must install the pinentry-tty package, and write the following line in the ~/.gnupg/gpg-agent.conf file:

pinentry-program /usr/bin/pinentry-tty

Here is how we can retrieve the encryption password using the script:

$ ansible-vault encrypt_string --vault-id vault@vault-client.py 'secret_value_to_encrypt' --name password

Notice that we use the --vault-id option to specify the identity to use, which in this case is the one at the left of the @; at the right of the symbol, instead, is the path of the script to be invoked. As said before, if we omit to provide an identity, the default one we used in the script will be used:

$ ansible-vault encrypt_string --vault-id vault-client.py 'secret_value_to_encrypt' --name password

If we explicitly provide the identity, instead, it will be reported in the generated output, for example:

password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;vault
          39393237653832393365366436353734343736366263366232656265313763623739346565653837
          3065656639616432646435393466633463336262353231330a633739633336633939333731643538
          63386462323561346334376633366134623862366462333066333563656464373633303533323439
          3038643639373966350a303065663830643635386630616263666636386537356233656330666239
          66393939633833643066643931326435633631626138373363303430626539356564

Decrypting a variable

To perform the reverse operation, so to check the original value of a variable, we can use the Ansible debug module. Imagine we stored the variable in the variable.yml file. To read its original content, retrieving the password using the script we used above, we would run:

$ ansible localhost -m debug -a var="password" -e @variable.yml --vault-id vault@vault-client.py



In the example above, we specified “localhost” as the machine where the task should be executed, and, as argument to the -m option, we passed the name of the module we want to use. The -a option, is used to pass arguments to the module, in the “key=value” pair format (var=”password”), and -e is used to pass extra variables (it is the short for -extra-vars) : additional variables must be also provided in key/value pairs; in this case, however, since we are passing the path of a file (the one in which we stored the variable), we just prepend it with an @ (take a look at the ansible manual for further instructions). Finally, as you can see, just like in the previous example, we used the vault-client.py script to retrieve the encryption/decryption password. The command returns the following result:

localhost | SUCCESS => {
"password": "secret_value_to_encrypt"
}

Encrypting files

Ansible-Vault can also be used to encrypt entire playbooks or other type of files. Suppose we want to encrypt a playbook called play.yml. To accomplish our mission, we would use the “encrypt” command:

$ ansible-vault encrypt play.yml

In this case the password would be asked interactively. Just like we did in the previous examples, however, we can store it in a file, or use a script to retrieve it:

$ ansible-vault encrypt --vault-password-file=password.txt play.yml

With the above command we encrypted an existing file, “play.yml”. If we want to create an encrypted file from scratch, instead, we can simply use the “create” command. When we do, the default text editor will be opened in order to let us write the content of the file. As soon as we will save it, the file will be encrypted with the provided password. Just as example, to create an encrypted file called playbook.yml,  we would run:

$ ansible-vault create playbook.yml --vault-password-file=password.txt



Notice that the file is encrypted in-place. If we want to edit it, we don’t need to decrypt it manually; we can conveniently use the “edit” command, which does the job for us, and opens it in our favorite text editor:

$ ansible-vault edit playbook.yml --vault-password-file=password.txt

To just decrypt the file, we use the “decrypt” command, instead:

$ ansible-vault decrypt playbook.yml --vault-password-file=password.txt

Providing the encryption password when executing a playbook

When we want to execute an encrypted playbook, or a playbook which contains one or more encrypted variables, we can use one of the methods we saw above, or even let Ansible ask for the password interactively using the --ask-vault-password option. Suppose we want to execute the tasks contained in the encrypted playbook.yml playbook. We could run:

$ ansible-playbook --ask-vault-password playbook.yml

To let the password be read from a file, instead:

$ ansible-playbook --vault-password-file=password.txt playbook.yml

To retrieve the password using a script, just like before:

$ ansible-playbook --vault-id vault@vault-client.py playbook.yml

Conclusions

In this article we learned how to use Ansible-Vault to protect sensitive content in playbooks. We saw how to encrypt and decrypt the value of single variables, and whole files. If you want to know more about Ansible-Vault, please take a look at the official documentation.



Comments and Discussions
Linux Forum