Docker behind SSL intercepting proxy

Situation

  • You need to build a Docker image from a repository in the Internet.
  • Your application running in the Docker container accesses an HTTPS server in the Internet.
  • You are behind intercepting SSL proxy.

The examples below are for Debian-based systems unless noted otherwise.

Background

SSL intercepting proxy requires that the application:

  1. Sends HTTPS requests to the proxy (as opposed to the target host directly)
  2. Trusts the intercepting certificate.

These two actions are configured separately:

  • For the former, one usually sets the environment variable https_proxy. HTTPS clients which read the environment are supposed to use it; others require own specific configuration.
  • For the latter, the trust can be configured either "system-wide", or on the application basis. Being behind the intercepting proxy you might want the system-wide configuration.

Pulling images

Images are pulled by the Docker daemon. The daemon is (normally) not aware of the environment of the shell you are using, so the $http_proxy in your environment, if set, will not have any effect.

$ docker pull hello-world
Using default tag: latest
Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io on 127.0.1.1:53: no such host

We need to configure the daemon to use the proxy according to the manual in /etc/systemd/system/docker.service.d/http-proxy.conf:

[Service]
Environment="HTTPS_PROXY=https://proxy.example.com:443/" "NO_PROXY=localhost,127.0.0.1,docker-registry.somecorporation.com"

And restart the daemon:

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
$ systemctl show --property=Environment docker

Docker daemon uses the proxy, but does not yet trust the intercepting certificate:

$ docker pull hello-world
[...]
docker: error pulling image configuration: Get https://[...]: x509: certificate signed by unknown authority.

You need to obtain your intercepting proxy certificate and add it to the system storage. (Q: How to add it to docker only, without root access?):

$ sudo cp my-intercepting-cert.crt /usr/local/share/ca-certificates/
$ sudo update-ca-certificates 
Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...

done.
done.

Or on RedHat-based systems:

sudo cp my-intercepting-cert.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

Now do sudo systemctl restart docker and you should be able to pull images.

Running containers

The steps above affected the daemon only. If your container needs to access the Internet behind the proxy, you want your dockerized application to be aware of it. While the build is run by the docker daemon, the daemon is not instructed to make "proxy-aware" images; in the end, you can build non-proxified images if you need to.

Docker documentation describes three ways to pass environment variables to the image.

  • In Dockerfile:
ENV http_proxy proxy.example.com:80
ENV https_proxy proxy.example.com:443
  • In the build command line:
docker build -t my-proxified-image \
            --build-arg http_proxy="http://proxy.example.com:80" \
            --build-arg https_proxy="https://proxy.example.com:443" \
            .
  • Configuring the Docker client (since Docker 17.07):

Add the snip to ~/.docker/config.json in the user's home directory:

{
 "proxies":
 {
   "default":
   {
     "httpsProxy": "http://127.0.0.1:80",
     "noProxy": "*.test.example.com,.example2.com"
   }
 }
}

Your image can contain programs which require proxy to operate, but obtain it from somewhere else than the environment. Configuration for such programs must be provided on the individual basis.

  • Example: if you want to be able to install software with "apt" in your Debian-based image:
RUN echo \
  'Acquire::http::proxy "http://proxy.example.com:80/";\n\
   Acquire::https::proxy "https://proxy.example.com:443/";'\
   > /etc/apt/apt.conf.d/00proxy.conf

Note: the application in the container needs to be able to resolve the given proxy domain name. A restrictive environment behind SSL proxy may not provide you with a general DNS access, in which case you'd have to use the IP address of the proxy.

Again, for SSL interception, in addition to the proxy itself the intercepting certificate must be configured as trusted. A snippet from Dockerfile for a "system-wide" configuration in a Debian-based system:

ADD dir-containing-intercepting-cert /usr/local/share/ca-certificates
RUN update-ca-certificates

At the runtime

The environment variable can be passed individually per container:

$ docker run -e "https_proxy=http://proxy.example.com:80" my-image

Afterword

SSL intercepting proxies reduce the connection security in many cases. Additionally to that, an application which performs certificate check but is not configured to trust the intercepting certificate will show a security warning (or just silently fail); as not every user is capable of properly configuring the trust in every case - in the end, users just need the application to work - many will ignore the warning and/or "set the insecure mode" if possible. In this way, the SSL intercepting proxy teaches to ignore security warnings, exactly rendering users vulnerable to attacks.

Acknowledgements


Contents © 2021 Konstantin Shemyak - Powered by Nikola