feat: register mounted CA certificates in windmill_extra at startup (#10545)

* feat: register mounted CA certificates in windmill_extra at startup

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: only claim a CA update when update-ca-certificates can read the mount

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: detect mounted CA certificates the way update-ca-certificates finds them

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-08-05 19:13:29 +00:00
committed by GitHub
parent a5423a81ca
commit d9d6ec82ab
3 changed files with 54 additions and 3 deletions
+12 -3
View File
@@ -118,9 +118,18 @@ not enough on its own, since `requests` carries its own bundle and Node reads on
`NODE_EXTRA_CA_CERTS`. Registry settings are deliberately not forwarded: they carry credentials and
only the service needs them.
To install that CA into the container's system store in the first place, set `INIT_SCRIPT` on the
`windmill_extra` container (e.g. `INIT_SCRIPT=update-ca-certificates`). It runs before any service
starts and aborts startup if it fails, the same hook a worker offers.
Registering that CA in the container's system store happens on its own: mount it into
`/usr/local/share/ca-certificates/` **named `*.crt`**, the only extension `update-ca-certificates`
reads, and `windmill_extra` runs it before starting any service. `RUN_UPDATE_CA_CERTIFICATE_AT_START=true` forces the same thing whether or not
certificates are mounted there, and `RUN_UPDATE_CA_CERTIFICATE_PATH` overrides the tool, matching
the server and worker. Both are best-effort: a UID that cannot write `/etc/ssl/certs` logs a warning
and the container still boots. `INIT_SCRIPT` remains the hook for anything more involved, and unlike
the CA update it aborts startup when it fails.
Note what the system store does *not* cover, which is most of what a debug session installs with:
uv trusts its own bundled roots unless `PY_NATIVE_CERT`/`UV_NATIVE_TLS` is `true`, Bun and Node read
only `NODE_EXTRA_CA_CERTS`, and `requests` carries certifi. Registering the CA fixes Python's stdlib
`ssl`, `curl` and `git`; the rest still needs the variables above.
Keeping the settings out of the session's environment only bounds what the debugged script can read
from itself. An unsandboxed session runs under the same user as the service and can still read the
+4
View File
@@ -187,6 +187,10 @@ services:
# - DEBUG_ALLOWED_ORIGINS=https://your-windmill-host # Optional CSWSH hardening: comma-separated allowlist of browser Origins permitted to open debug WebSockets
volumes:
- lsp_cache:/pyls/.cache
# Behind a TLS-intercepting proxy, mount its CA here (as .crt) and it is registered in the
# system trust store before any service starts. That alone does not cover dependency
# installation — see debugger/README.md for the variables it also needs
# - ./corp-ca.crt:/usr/local/share/ca-certificates/corp-ca.crt:ro
logging: *default-logging
caddy:
+38
View File
@@ -33,6 +33,44 @@ if [ ! -w "$HOME" ]; then
fi
export HOME
# Register CA certificates mounted into the image before anything opens a TLS connection.
# Best-effort on purpose, unlike INIT_SCRIPT below: a non-root UID cannot write /etc/ssl/certs, and
# a deployment that never needed a custom CA must still boot. Env var names and the default-off
# behavior match the server/worker binary, so one setting covers every container. What the system
# trust store does and does not reach is documented in debugger/README.md.
CA_CERT_DIR=/usr/local/share/ca-certificates
update_ca_certificates() {
local reason="$1"
local tool="${RUN_UPDATE_CA_CERTIFICATE_PATH:-/usr/sbin/update-ca-certificates}"
local output
if [ ! -x "$tool" ]; then
echo "[entrypoint] $reason but $tool is not executable, skipping CA update"
return
fi
echo "[entrypoint] $reason, running $tool"
if output=$("$tool" 2>&1); then
echo "[entrypoint] CA certificates updated"
else
# Carry the tool's own message: the usual cause is an unwritable /etc/ssl/certs under a
# non-root UID, but guessing that in place of the real error hides everything else.
echo "[entrypoint] WARNING: $tool failed (UID $(id -u)): ${output:-no output}; continuing" >&2
fi
}
if [ "$(echo "${RUN_UPDATE_CA_CERTIFICATE_AT_START:-false}" | tr '[:upper:]' '[:lower:]')" = "true" ]; then
update_ca_certificates "RUN_UPDATE_CA_CERTIFICATE_AT_START=true"
elif [ -n "$(find -L "$CA_CERT_DIR" -type f -name '*.crt' -print -quit 2>/dev/null)" ]; then
# Certificates mounted there are unambiguous intent, and they do nothing until registered, so
# take the same action without making the operator also find the env var.
update_ca_certificates "Found certificates in $CA_CERT_DIR"
elif [ -n "$(ls -A "$CA_CERT_DIR" 2>/dev/null)" ]; then
# Reporting success over a mount update-ca-certificates ignores would be worse than saying
# nothing: .pem is the spelling people reach for, and only .crt is read.
echo "[entrypoint] WARNING: $CA_CERT_DIR has files but none named *.crt, the only extension" \
"update-ca-certificates reads; they will be ignored" >&2
fi
# INIT_SCRIPT is the documented hook for preparing the host before anything reaches the network
# (CA certificates, proxies, mounts), matching the worker's INIT_SCRIPT. It must therefore complete
# before any service starts, and a failure has to abort: services that come up with an unprepared