Using docker behind an http proxy with authentication

3 minute read

no-alignment

Introduction

Web proxies are mostly used in corporate environments but can be useful on small offices / home offices as well. Squid is an example of a easy to install and configure proxy, that can help us achieve centralized control of the web traffic in our home. Using an http_proxy from the client perspective is pretty simple, and comes down to specifying the proxy address in the browser settings, but from an engineer perspective things are more interesting!

I have been playing a lot with docker lately and I had a really hard time in configuring it to use an authenticated http(s) proxy, so I thought I ‘d share my experience here.

Environment variables

On Unix environments most applications respect the http_proxy, https_proxy environment variables. If we want to tell wget to use a proxy for instance, we would do something like that:

$ export https_proxy=http://<my_usename>:<my_password>@my_proxy.com
$ wget someurl.com

Docker, however, is a daemon and is managed by systemd on the most popular Linux distributions, so we need to configure these environment variables in a different way.

Systemd directives

According to the official documentation, we should create a file /etc/systemd/system/docker.service.d/http-proxy.conf with the following contents:

[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80/"

Note: HTTP_PROXY doesn't have to be in uppercase. As per golang's documentation the lowercase variants work as well.

This works fine, if the web proxy does not use authentication or, if it does use it, our credentials do not contain special characters. If my password looks like this MyAw3s0m3p^$$.!, and I create the systemd configuration file as instructed:

[Service]
Environment="http_proxy=http://myusername:MyAw3s0m3p^$$.!@proxy.example.com:80/"

docker won’t be able to use the http_proxy. docker pull will fail miserably with an error like this:

$ docker pull hello-world
Error response from daemon: Get https://registry-1.docker.io/v2/: proxyconnect tcp: dial tcp: lookup http: no such host

There are two issues here. First of all, golang (which powers docker) doesn’t like these special characters in the url. We need to urlencode this password. http://myusername:MyAw3s0m3p^$$.!@proxy.example.com:80/ becomes http%3A%2F%2Fmyusername%3AMyAw3s0m3p%5E%24%24.%21%40proxy.example.com%3A80%2F. But even the urlencoded url won’t work, cause systemd doesn’t like the % and some other special characters in there. There is systemd-escape which escapes strings for usage in systemd unit names, but this didn’t work for me. My solution to that was to use the EnironmentFile directive instead of the Environment. Instead of specifying the environment variables in the systemd configuration file, we will create another file that systemd is going to read in order to load the environment variables.

The /etc/systemd/system/docker.service.d/http-proxy.conf file becomes:

[Service]
EnvironmentFile=/etc/system/default/docker

And the /etc/system/default/docker file looks like this:

http_proxy='http%3A%2F%2Fmyusername%3AMyAw3s0m3p%5E%24%24.%21%40proxy.example.com%3A80%2F'

This is a good practice btw, as we can restrict the permissions to that file so that it’s not readable by the world. If we want to include passwords in our environment variables we wouldn’t want some unauthorized user to be able to view our passwords by looking at the systemd configuration file or by using systemctld show docker for instance. Let’s do that and restrict access to this file:

$ chgrp docker /etc/default/docker   # change group owner to docker
$ chmod o-r /etc/default/docker      # remove read access from ppl not in the docker group

Having made the changes above, we reload - restart the docker service and everything works as expected.

$ systemctl daemon-reload
$ systemctl restart docker

Conclusion

We saw that golang needs the urls in an encoded form when special characters are included. We also noticed that systemd cannot read these encoded urls when specified in its configuration files using the Environment directive, and we used the EnvironmentFile instead with a new file that contains our environment variables under /etc/default/docker. Finally, we restricted access to that file so that only members of the docker group can read it.

Comments