Securing Tower Installer Passwords

Securing Tower Installer Passwords

One of the crucial pieces of the Red Hat Ansible Automation Platform is Ansible Tower. Ansible Tower helps scaling IT automation, managing complex deployments and speeding up productivity. A strength of Ansible Tower is its simplicity that also extends to the installation routine: when installed as a non-container version, a simple script is used to read in variables from an initial configuration to deploy Ansible Tower. The same script and initial configuration can even be re-used to extend the setup and add, for example, more cluster nodes.

However, part of this initial configuration are passwords for the database, Ansible Tower itself and so on. In many online examples, these passwords are often stored in plain text. One question I frequently get as a Red Hat Consultant is how to protect this information. A common solution is to simply remove the file after you complete the installation of Ansible Tower. But, there are reasons you may want to keep the file around. In this article, I will present another way to protect the passwords in your installation files.

Ansible Tower's setup.sh

For some quick background, setup.sh is the script used to install Ansible Tower and is provided in both the regular and bundled installer. The setup.sh script only performs a couple of tasks, such as validating that Ansible is installed on the local system and setting up the installer logs; but most importantly, it launches Ansible to handle the installation of Ansible Tower. An inventory file can be specified to the installer using the -i parameter or, if unspecified, the default provided inventory file (which sits alongside setup.sh) is used. In the first section of the inventory file, we have groups to specify the servers that Ansible Tower and the database will be installed on:

[tower]
localhost ansible_connection=local

[database]

And, after those group specifications, there are variables that can be used to set the connections and passwords, and is where you would normally enter your plain text passwords, such as:

[all:vars]
admin_password='T0w3r123!'

pg_host=''
pg_port=''

pg_database='awx'
pg_username='awx'
pg_password='DB_Pa55w0rd!'

In the example above, these passwords are displayed as plain text. Many clients I have worked with are not comfortable with leaving their passwords in plain text within the inventory file for security reasons. Once Ansible Tower is installed, this file can be safely removed, but if you ever need to modify your installation to add a node to a cluster or add/remove inventory groups, this file will need to be regenerated. Likewise, if you want to use the backup and restore functions of setup.sh, you will also need the inventory file with all of the passwords as it was originally installed.

Vault to the Rescue

Since the installer is using Ansible to install Ansible Tower, we can leverage some Ansible concepts to secure our passwords. Specifically, we will use Ansible vault to have an encrypted password instead of a plain text password. If you are not familiar with Ansible vault, it is a program shipped with Red Hat Ansible Automation Platform itself and is a mechanism to encrypt and decrypt data. It can be used against individual strings or it can encrypt an entire file. In our example, we will encrypt individual strings as passwords. This will be beneficial if you end up committing your inventory file into a source control management tool. The SCM will be able to show you individual passwords that were changed in a commit versus just being able to say an encrypted file changed (but not being able to show which password within the encrypted file changed).

To start, we are going to encrypt our admin password with the following command (fields in <> indicate input to ansible-vault):

$ ansible-vault encrypt_string --stdin-name admin_password
New Vault password:
Confirm New Vault password:
Reading plaintext input from stdin. (ctrl-d to end input)
<t0w3r123!>admin_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66663534356430343166356461373464336332343439393731363365303063353032373564623537
          3466663861633936366463346135656130306538376637320a303738653264333737343463613366
          31396336633730323639303436653330386536363838646161653562373631323766346431663461
          6536646264633563660a343163303334336164376339363161373662613137633436393263376631
          3539
Encryption successful
</t0w3r123!>

In this example, we are running ansible-vault and asking it to encrypt a string. We've told ansible-vault that this variable will be called admin_password and it will have a value of T0w3r123! (what we would have entered into our inventory file). In the example, we used a password of 'password' to encrypt these values. In a production environment, a much stronger password should be used to perform your vault encryption. In the output of the command, after the two ctrl-d inputs, our encrypted variable is displayed on the screen. We will take this output and put it into a file called passwords.yml next to our inventory file. After encrypting the second pg_password our password.yml file looks like this:

---
admin_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66663534356430343166356461373464336332343439393731363365303063353032373564623537
          3466663861633936366463346135656130306538376637320a303738653264333737343463613366
          31396336633730323639303436653330386536363838646161653562373631323766346431663461
          6536646264633563660a343163303334336164376339363161373662613137633436393263376631
          3539
pg_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          65633239383761336539313437643733323235366337653164383934303563643464626562633865
          3130313231666531613131633736386134343664373039620a336237393631333532373066343135
          65316431626630633965623134623133353635376236306538653230363038333661623236376330
          3664346237396139610a376536373132313237653239353832623433663230393464343331356561
          3435

Now that we have our completed passwords.yml file, we have to tell the installer to load the passwords from this file and also to prompt us for the vault password to decrypt the value. To do this we will add three parameters to our setup.sh command. The first option is -e@passwords.yml, which is a standard syntax to tell Ansible to load variables from a specified file name (in this case passwords.yml). The second option will be --, which will tell the setup.sh script that any following options should be passed on to Ansible instead of being processed by setup.sh. The final option will be --ask-vault-pass, which tells Ansible to prompt us for the password to be able to decrypt the vault secrets. All together our setup command will become:

$ ./setup.sh -e@passwords.yml -- --ask-vault-pass

If you normally add arguments to setup.sh, they will need to be merged into this command structure. Arguments to setup.sh will need to go before the -- and any arguments you passed to Ansible would go after the --

When running setup.sh with these options you will now be prompted to enter the vault password before the Ansible installer begins:

$ ./setup.sh -e@passwords.yml -- --ask-vault-pass
Using /etc/ansible/ansible.cfg as config file
Vault <password>:

PLAY [tower:database:instance_group_*:isolated_group_*] ******************************************************************************************

Here I have to enter my weak vault password of 'password' for the decryption process to work. 

This technique will work even if you leave the blank password variables in the inventory file because of the variable precedence from Ansible. The highest precedence any variable can take comes from extra_vars (which is the -e option we added to the installer), so values in our vault file will override any values specified in the inventory file.

Using this method allows you to keep the inventory file and password files on disk or in an SCM and not have plain text passwords contained within them.

Another Solution

Another option you could take if you only wanted a single inventory file would be to convert the existing ini inventory file into a yaml based inventory. This would allow you to embed the variables as vault encrypted values directly. While the scope of doing that is beyond this article, an example inventory.yml file might look similar to this:

all:
  children:
    database: {}
    tower:
      hosts:
        localhost:
  vars:
    admin_password: !vault |
        $ANSIBLE_VAULT;1.1;AES256
        66663534356430343166356461373464336332343439393731363365303063353032373564623537
        3466663861633936366463346135656130306538376637320a303738653264333737343463613366
        31396336633730323639303436653330386536363838646161653562373631323766346431663461
        6536646264633563660a343163303334336164376339363161373662613137633436393263376631
        3539
    ansible_connection: local
    pg_database: awx
    pg_host: ''
    pg_password: !vault |
        $ANSIBLE_VAULT;1.1;AES256
        65633239383761336539313437643733323235366337653164383934303563643464626562633865
        3130313231666531613131633736386134343664373039620a336237393631333532373066343135
        65316431626630633965623134623133353635376236306538653230363038333661623236376330
        3664346237396139610a376536373132313237653239353832623433663230393464343331356561
        3435
    pg_port: ''
    pg_sslmode: prefer
    pg_username: awx
    rabbitmq_cookie: cookiemonster
    rabbitmq_password: ''
    rabbitmq_username: tower
    tower_package_name: ansible-tower
    tower_package_release: '1'
    tower_package_version: 3.6.3

Using a file like this, setup.sh could then be called as:

$ ./setup.sh -i inventory.yml -- --ask-vault-pass

Using this method will require more work when upgrading Ansible Tower, as any field changes in the provided inventory file will need to be reflected in your yaml inventory, whereas the previous method only requires new password fields added to the inventory file to be added into the password.yml file.