Ansible, APT & Proxies

I’ve been working on a new Ansible setup which is behind a HTTP proxy and I’ve run into what seems to be a common problem: The Ansible modules apt_key and apt_repository don’t work behind proxies. There appear to be multiple bug reports in Ansible about these problems, but no resolution yet despite the bugs being several years old.

Adding a new repository to APT involves two steps: Adding the repository’s key to APT’s GPG keyring, then actually adding the repository.

Step 1 – Adding the key (Method 1)

The apt_key module in Ansible has the nice bug that it doesn’t honor any standard unix method for specifying use of a HTTP proxy.

Putting “Acquire::http::Proxy” in /etc/apt/apt.conf doesn’t work. Nor does setting the http_proxy environment variable.

Instead, you have to execute the apt-key command (not Ansible module) directly and supply a http-proxy parameter to it. This is from my playbook for installing Ansible:

- name: Add Ansible Repo Key
    - /usr/bin/apt-key
    - adv
    - --keyserver-options
    - http-proxy=
    - --keyserver
    - hkp://
    - --recv-keys
    - 6125E2A8C77F2818FB7BD15B93C4A3FD7BB9C367

When adding other repositories, you may have to do some digging to get the ID of the key you wish to receive.

Step 1 – Adding the key (Method 2)

Method 1 only works if the repository key is in Ubuntu’s key server. If the key is in a third party site (such as what Gitlab do), things can get even more complicated. This is the best I’ve come up with.

The way I do it, is to create a temporary file, download the key into this temporary file and then pass the key to the apt_key module.

First off, create the temporary file:

- name: Create temp file
   state: file
  register: tempfile_1

Now we download the key from the remote server and save it into our temporary file:

- name: Get Gitlab Key from remote server
   dest: "{{ tempfile_1.path }}"
   http_proxy: ""
   https_proxy: ""

Now we need to pass the file’s contents to apt_key. You may (as I was!) be tempted to use the lookup(‘file’, ‘/somewhere/filename’) helper function. This won’t work ☹

The lookup helper function runs on the local Ansible machine and not the remote machine we’re configuring. So lookup() fails to find the file because it doesn’t exist “here”, only “over there”.

Instead, we have to use the slurp module in Ansible to read the file contents into a variable.

- name: Read Gitlab Key into memory
   src: "{{ tempfile_1.path }}"
  register: key_data

And then we just pass this to apt_key:

- name: Add Gitlab Key
   data: "{{ key_data['content'] }}"

Except this doesn’t work. This is because when the slurp module reads the file into memory, it Base64 encodes it. So when we read the data back out again, we need to Base64 decode it.

- name: Add Gitlab Key
   data: "{{ key_data['content'] | b64decode }}"

Step 2 – Adding the repository

The apt_repository is slightly more helpful: It does honor the http_proxy variables. But you also need another variable: APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE This is needed as the apt_repository module still kicks off apt-key somewhere and apt-key really doesn’t want to be run from a script. My play for this step looks like:

- name: Add Ansible repo
  apt_repository: repo="ppa:ansible/ansible" state=present update_cache=yes

Note that you have to set both the http_proxy AND https_proxy variables.

Step 3 – Install Ansible

Now we can fall back to standard APT configurations! Create the file /etc/apt/apt.conf.d/10proxy.conf  and set its contents to be:

Acquire {
 HTTP::proxy "";
 HTTPS::proxy "";

Then in your Ansible play you can just say:

- name: Add Ansible
   name: ansible
   state: present

Or, if you’re installing Gitlab’s Runner package:

- name: Install gitlab runner
   name: gitlab-runner
   state: present

That was way more convoluted that it needed to be.

Appendix 1 – Proxy Variables

You will have noticed we replicated the proxy settings in a couple of places. Being the good programmers that we are, we see that this is a bad thing and we want to place these in a variable.

There are several places we can define the a variable. In my simple case, I want it accessible to all hosts, so I put it in the group_var/all.yaml file:

  http_proxy: ""
  https_proxy: ""

Then in our roles, we can just put:

  - "{{ proxy_env }}"

And if we need to add any extra variables, we just add them to the bottom of the list:

 - "{{ proxy_env }}"


In my playing with these issues, I’ve discovered something I don’t understand. For some APT sources, I have to supply the APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE environment variable, yet for others, I don’t.

e.g. To install Ansible, I have to add the variable, but for Gitlab’s runner, I don’t.

I’ll leave it as an exercise for the reader to work out what’s going on.