Skip to content

Commit 4900882

Browse files
feat: add Docker API TLS client-certificate support (#1265)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6fc80eb commit 4900882

7 files changed

Lines changed: 82 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ jobs:
121121
- test-name: standalone_ipv6
122122
setup: 2containers
123123
pebble-config: pebble-config.json
124+
- test-name: docker_api_tls
125+
setup: 2containers
126+
pebble-config: pebble-config.json
124127
runs-on: ubuntu-latest
125128

126129
steps:

app/entrypoint.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ function check_docker_socket {
2323
echo "Typically you should run your container with: '-v /var/run/docker.sock:$socket_file:ro'" >&2
2424
exit 1
2525
fi
26+
elif parse_true "${DOCKER_TLS_VERIFY:-}"; then
27+
if [[ -z "${DOCKER_CERT_PATH:-}" ]]; then
28+
echo "Error: DOCKER_TLS_VERIFY is enabled but DOCKER_CERT_PATH is not set." >&2
29+
echo "Set DOCKER_CERT_PATH to the in-container directory holding ca.pem, cert.pem and key.pem." >&2
30+
exit 1
31+
fi
32+
local cert_file
33+
for cert_file in ca.pem cert.pem key.pem; do
34+
if [[ ! -r "${DOCKER_CERT_PATH}/${cert_file}" ]]; then
35+
echo "Error: TLS certificate file ${DOCKER_CERT_PATH}/${cert_file} is missing or not readable." >&2
36+
echo "DOCKER_CERT_PATH must point to an in-container directory containing readable ca.pem, cert.pem and key.pem files." >&2
37+
echo "Typically you should mount your Docker client certificate directory, e.g.: '-v /path/to/docker/certs:${DOCKER_CERT_PATH}:ro'" >&2
38+
exit 1
39+
fi
40+
done
2641
fi
2742
}
2843

app/functions.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ function docker_api {
232232
if [[ $DOCKER_HOST == unix://* ]]; then
233233
curl_opts+=(--unix-socket "${DOCKER_HOST#unix://}")
234234
scheme='http://localhost'
235+
elif parse_true "${DOCKER_TLS_VERIFY:-}"; then
236+
curl_opts+=(--cert "${DOCKER_CERT_PATH}/cert.pem")
237+
curl_opts+=(--key "${DOCKER_CERT_PATH}/key.pem")
238+
curl_opts+=(--cacert "${DOCKER_CERT_PATH}/ca.pem")
239+
scheme="https://${DOCKER_HOST#*://}"
235240
else
236241
scheme="http://${DOCKER_HOST#*://}"
237242
fi

docs/Container-configuration.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,24 @@ You can also create test certificates per container (see [Test certificates](./L
4545
* `RELOAD_NGINX_ONLY_ONCE` - The companion reload nginx configuration after every new or renewed certificate. Previously this was done only once per service loop, at the end of the loop (this was causing delayed availability of HTTPS enabled application when multiple new certificates where requested at once, see [issue #1147](https://github.com/nginx-proxy/acme-companion/issues/1147)). You can restore the previous behaviour if needed by setting the environment variable `RELOAD_NGINX_ONLY_ONCE` to `true`.
4646

4747
* `DOCKER_CONTAINER_FILTERS` - You can filter which containers are considered by acme-companion by using the `DOCKER_CONTAINER_FILTERS` environment variable (by default, acme-companion will consider all running containers). It takes a comma separated list of `key=value` pairs. For example, setting `DOCKER_CONTAINER_FILTERS` environment variable to `network=mynetwork` will cause acme-companion to consider only containers connected to the `mynetwork` network. See the [Docker CLI documentation](https://docs.docker.com/reference/cli/docker/container/ls/#filter) for details on available filters.
48+
49+
* `DOCKER_HOST` - The Docker API endpoint acme-companion talks to. Defaults to `unix:///var/run/docker.sock` (the mounted Docker socket). To connect to a remote or TLS-protected Docker daemon over TCP, set it to `tcp://<host>:<port>` (the conventional Docker TLS port is `2376`).
50+
51+
* `DOCKER_TLS_VERIFY` and `DOCKER_CERT_PATH` - Enable TLS client-certificate authentication when connecting to the Docker daemon over `tcp://`. Set `DOCKER_TLS_VERIFY` to `true` and `DOCKER_CERT_PATH` to the **in-container** path of a directory containing `ca.pem`, `cert.pem` and `key.pem`. These variable names and file names match the [Docker CLI convention](https://docs.docker.com/engine/security/protect-access/) and the ones used by **docker-gen**, so the same certificate directory can be shared across containers.
52+
53+
> **Important:** `DOCKER_CERT_PATH` is a path **inside the container**. You must mount your certificate directory into the container with a volume, otherwise the files will not be found and the container will exit on startup with an error.
54+
55+
For example, connecting to a TLS-protected Docker daemon over TCP (note that the Docker socket is **not** mounted in this case):
56+
57+
```bash
58+
$ docker run --detach \
59+
--name nginx-proxy-acme \
60+
--volumes-from nginx-proxy \
61+
--volume certs:/etc/nginx/certs:rw \
62+
--volume acme:/etc/acme.sh \
63+
--volume /path/to/docker/certs:/docker-certs:ro \
64+
--env "DOCKER_HOST=tcp://docker-host.example.com:2376" \
65+
--env "DOCKER_TLS_VERIFY=true" \
66+
--env "DOCKER_CERT_PATH=/docker-certs" \
67+
nginxproxy/acme-companion
68+
```

test/config.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set -e
44
globalTests+=(
55
docker_api
66
docker_api_legacy
7+
docker_api_tls
78
location_config
89
debug_acmesh_log
910
certs_single
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## tls-verify-true-get
2+
-s --cert /docker-certs/cert.pem --key /docker-certs/key.pem --cacert /docker-certs/ca.pem -X GET https://docker:2376/version
3+
## tls-verify-true-post
4+
-s --cert /docker-certs/cert.pem --key /docker-certs/key.pem --cacert /docker-certs/ca.pem -H Content-Type: application/json -X POST https://docker:2376/containers/test/restart
5+
## tls-verify-false-get
6+
-s -X GET http://docker:2376/version
7+
## unix-socket-get
8+
-s --unix-socket /var/run/docker.sock -X GET http://localhost/version

test/tests/docker_api_tls/run.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
## Test for Docker API TLS client-certificate support (DOCKER_TLS_VERIFY / DOCKER_CERT_PATH).
4+
##
5+
## Deterministic verification that docker_api builds the expected curl invocation for
6+
## each transport, by stubbing curl. This needs no external TLS daemon and is therefore
7+
## stable on CI runners.
8+
##
9+
## This test is transport-focused and produces identical output regardless of the
10+
## SETUP (2containers / 3containers); it is registered to run once via the CI matrix.
11+
12+
# Replace curl with a stub that prints the arguments it would have received, then
13+
# exercise docker_api over each transport in an isolated subshell (to avoid the
14+
# bash quirk where `VAR=x function_call` leaks VAR into the calling shell).
15+
commands="$(cat <<'EOF'
16+
curl() { printf '%s\n' "$*"; }
17+
source /app/functions.sh
18+
echo '## tls-verify-true-get'
19+
( export DOCKER_HOST='tcp://docker:2376' DOCKER_TLS_VERIFY='true' DOCKER_CERT_PATH='/docker-certs'; docker_api '/version' )
20+
echo '## tls-verify-true-post'
21+
( export DOCKER_HOST='tcp://docker:2376' DOCKER_TLS_VERIFY='true' DOCKER_CERT_PATH='/docker-certs'; docker_api '/containers/test/restart' 'POST' )
22+
echo '## tls-verify-false-get'
23+
( export DOCKER_HOST='tcp://docker:2376' DOCKER_TLS_VERIFY='false' DOCKER_CERT_PATH='/docker-certs'; docker_api '/version' )
24+
echo '## unix-socket-get'
25+
( export DOCKER_HOST='unix:///var/run/docker.sock'; docker_api '/version' )
26+
EOF
27+
)"
28+
29+
docker run --rm "$1" bash -c "$commands" 2>&1

0 commit comments

Comments
 (0)