Network Automation     Archive

Custom credentials in Ansible Tower to store Github private keys

This post shows you how to add a custom credential in Ansible Tower, that stores SSH private keys. I won’t dive into what custom credentials are and how to get started with them because it is brilliantly covered in this “Inside Playbook” by @bill_nottingham

My use case

One of the roles in my playbook involves cloning a private git repo. For this, I use the Ansible git module. Since this is a private repo, I cannot use the https URL, since doing so, my playbook will always require human input. Running the following playbook:

  hosts: localhost
  gather_facts: no

    - name: CLONE THE GIT REPO
        dest: /tmp/my_test_repo

…will result in the following output:

[ec2-user@ip-10-0-0-194 tmp]$ ansible-playbook demo_interactive_private_key.yml 
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not
match 'all'

PLAY [DEMO AN INTERACTIVE PRIVATE KEY] *****************************************************************************

TASK [CLONE THE GIT REPO] ******************************************************************************************
Username for '': 

…where the playbook is waiting on my interactive response

Solving using a SSH key pair

This situation is easily remedied by switching from the https to a ssh url and then using the SSH key pair registered with github.

  hosts: localhost
  gather_facts: no

    - name: CLONE THE GIT REPO
        dest: /tmp/my_test_repo
        key_file: ./my_private_key

Moving this solution to Ansible Tower

To move this playbook to my central Tower server, I need to take a different approach because I don’t want my private key to be on the Tower file system. One option is to define a custom credential and supply my private key through this credential. Custom credentials come in handy when you want to inject sensitive data as extra vars or environment variables into your playbook (exhaustive details here)

Let’s dive right into it. The work flow I use is as follows:

1. First we create a new credential type

2. Then, add new credential(s) using this custom credential 

3. Invoke the credential from the job template

1. Create a new credential type

As a Tower admin, you can create a custom credential by clicking on ADMINISTRATION> Credential Types

Here, I’ve created a new credential type and I’ve named it PRIVATE KEY CRED. Custom credentials are defined using 2 components :

1. Input Configuration

2. Injector Configuration

Simply put, the input configuration is where you will define the characteristics/attributes of the custom credential you are defining. In my case:

  - id: my_private_key
    type: string
    label: private_key
    secret: true
    multiline: true

The secret: true will make sure that the data stored in this credential will be encrypted at rest and multiline: true will accommodate for private keys, which typically contain multiple lines.

The Injector Configuration section, is how the data contained in the custom credential will be injected into our playbook. Let’s break this down for my example:

  template.my_key: "{{ my_private_key  }}"
  secret_key: "{{ tower.filename.my_key  }}"

The file parameter tells us that the string data of the my_private_key custom credential id will be stored as a temporary file. The template.my_key is how Tower will reference this file, using the identifier my_key. So now, at this point the data collected from the input field is going to be stored as a temporary file that Tower can reference using my_key.

The next part is extra_vars. Here, I am configuring the injector to pass the data into the playbook as an extra var. The extra var name is secret_key and the value of this variable will be the temporary file referenced by Tower as my_key. This will be clear further down when we debug this variable within our playbook.

In other words tower.filename is not user defined, whereas my_key is.

Ok, now onto using this brand new custom credential that we just created!

2. Add new credentials

This step should look familiar to Tower/AWX users. Create a new credential by choosing the RESOURCES > Credentials option from the sidebar:

After saving the private key is encrypted and stored securely on Tower.

Repeat this step for as many users as needed that will be using the github repo.

3. Invoke the custom credential from the job template

This step is where it all comes together. So first, let’s update our playbook so that we can take advantage of the custom credential that’s being injected as an extra var:

  hosts: localhost
  gather_facts: no

        var: secret_key

    - name: CLONE THE GIT REPO
        dest: /tmp/my_test_repo
        key_file: "{{ secret_key  }}"
      delegate_to: localhost

Recall that in step 1, we are injecting the private key as a variable named secret_key

Great, now let’s add a job template that ties this playbook and our custom credential together:

Note how I have the credential to be PROMPT ON LAUNCH? For my use case, I’d like others in my org to be able to interact with this private github repo. Selecting the PROMPT ON LAUNCH enables each individual to use their own private ssh key through the custom credentials we discussed above.

When I launch the template, I am prompted to choose a credential type. I choose the custom credential we created PRIVATE KEY CRED. Which then lists the available private keys of this type.

Use the powerful Tower RBAC to restrict who can use which credentials.

Finally, launching the template results in the repo being cloned as expected:

Note the contents of the secret_key variable in the play output. It references the temporary file that the custom credential creates (and deletes after the play execution), containing my private key details.


Hopefully this post helps folks who are trying to figure out how to deal with SSH keys and Github connectivity. Please let me know if you have questions through the comments/social media.

If you have solved this in Tower using a different method, I’d love to hear how you did it as well.