Increasingly, large companies are implementing transparent HTTPS proxy servers in order to enforce company policy on appropriate Internet use or detect unauthorized exfiltration of corporate IP. While I understand the motivation behind this trend, the implementation is, in my considered opinion, a bloody dumpster fire.
That’s because the proxies do it by executing a literal man-in-the middle attack. The proxy strips off the TLS encryption from the connection and re-encrypts traffic in both directions with its own certificate. This works for the typical office worker because there’s generally a locally installed bit of client software that overrides network routing to send all HTTP/S traffic to the proxy and injects the proxy’s root certificate into the operating system’s secure certificate store.
Developers, on the other hand, tend to use things like Docker and Podman and WSL2 and k3s and the AWS CLI and a host of other dev tools that 1) have to make HTTPS connections to work correctly and b) have their own dedicated certificate stores. Generally the first indication you have when something goes wrong is ERR_SSL_PROTOCOL_ERROR error messages or the equivalent.
In my experience when this happens during a docker build
the first thing developers will do is assume it’s the Docker
daemon that needs fixing, when in reality it’s the Linux distro inside the container that’s missing the proxy’s root cert.
I’ve collected here a set of snippets for injecting the proxy root cert into containers during build. Note that in some
cases you might have to do this multiple times, for instance if you manually installed the AWS CLI in a container: the AWS
CLI uses its own cert store from python’s certifi
library, while other tools will use the system cert store of the Linux
distro in the container
All of these snippets assume the root certificate is named tls-proxy-root-cert.crt
, is stored in the root of the
Docker build context, and is a Base64-encoded x.509 certificate (note: filename extensions are not an accurate indicator
of the actual format of a certificate file. Open it up and verify it’s a Base64-encoded file.)
There’s a chicken-and-egg problem if your security admins haven’t whitelisted the package servers for your favourite base
image’s distro: you need to install the ca-certificates
package to get the update-ca-certificates
command, but you
can’t install ca-certificates
because you can’t get to the package servers. The fix is to copy the TLS proxy root cert
into /etc/ssl/certs/ca-certificates.crt
, which is enough to get access to the package servers working. Once ca-certificates
is installed, copying the TLS proxy root cert to the correct staging location and running update-ca-certificates
regenerates the CA cert bundle with all the necessary certs for general use.
Debian/Ubuntu Link to heading
This should also work for Linux distributions based on Debian, like Linux Mint, but I haven’t tested any of those.
Debian-based distros require the certificate filename to have the .crt
extension when copied to /usr/local/share/ca-certificates
for some reason.
COPY ./tls-proxy-root-cert.crt /etc/ssl/certs/ca-certificates.crt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY ./tls-proxy-root-cert.crt /usr/local/share/ca-certificates/tls-proxy-root-cert.crt
RUN update-ca-certificates
Fedora/RHEL Link to heading
This should also work for all current Red Hat derivatives - Rocky Linux, AlmaLinux, Oracle Linux, etc.
Fedora’s dnf
uses curl under the hood, so you need to inject the cert first. Fortunately, Fedora doesn’t need the
ca-certificates
bundle installed to update the CA cert bundle.
COPY ./tls-proxy-root-cert.crt /etc/pki/ca-trust/source/anchors/tls-proxy-root-cert.crt
RUN update-ca-trust
RUN dnf makecache --refresh \
&& dnf -y install \
ca-certificates \
&& dnf clean all
Alpine Linux Link to heading
Alpine has the same chicken-and-egg problem Debian does.
COPY ./tls-proxy-root-cert.crt /etc/ssl/certs/ca-certificates.crt
RUN apk update \
&& apk add ca-certificates \
&& rm -rf /var/cache/apk/*
COPY ./tls-proxy-root-cert.crt /usr/local/share/ca-certificates/tls-proxy-root-cert.crt
RUN update-ca-certificates
AWS and Azure CLIs Link to heading
Both the Azure and AWS CLIs are written in python and bundle their own python and dependent libraries. This includes a dedicated certificate store which they use by default. You can override the location of the certificate store with an environment variable:
Tool | Environment Variable | Debian | Fedora | Alpine |
---|---|---|---|---|
AWS CLI | AWS_CA_BUNDLE | /etc/ssl/certs/ca-certificates.crt |
/etc/pki/tls/certs/ca-bundle.crt |
/etc/ssl/certs/ca-certificates.crt |
Azure CLI | REQUESTS_CA_BUNDLE | /etc/ssl/certs/ca-certificates.crt |
/etc/pki/tls/certs/ca-bundle.crt |
/etc/ssl/certs/ca-certificates.crt |
To use these tools inside a Docker container, first install the proxy’s root certificate as described above, and then set an environment variable as listed in the table before running the command.
Note that these instructions are tested and correct as of the date of writing, but life comes at you fast. If you’re coming across this two years from now there’s a good chance all this will have changed.