fix(deploy): provision the certs dir traversable by the nonroot gateway
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m54s

The Ansible base-directory loop created /opt/scrabble/certs at mode 0750
(deploy:deploy), like config/dumps/images. The gateway (and backend) run as
the distroless nonroot UID 65532 — not the deploy user — so the container
cannot traverse a 0750 certs dir and fails at startup with
"mtls: load server keypair: ... permission denied", crash-looping.

This is latent: a long-running container holds the keypair in memory and
never re-reads the file, so the misconfig only bites when a container
restarts (a host reboot / redeploy). A hoster maintenance reboot exposed it
on prod — the gateway came back crash-looping while the deploy could not SSH
in mid-reboot.

Split certs out of the 0750 loop and create it 0755 (traversable). The keys
stay 0644 by design (the gateway compose relies on it); the host is
single-tenant + SSH-access-controlled, so a traversable certs dir adds no
meaningful exposure. The live prod host was already chmod-fixed by hand; this
keeps the next provisioning run from re-tightening it.
This commit is contained in:
Ilia Denisov
2026-07-10 12:34:21 +02:00
parent df9eace09f
commit 2d2dd2bc47
+15 -1
View File
@@ -213,6 +213,20 @@
loop:
- ""
- config
- certs
- dumps
- images
# The certs dir holds the reverse-mTLS bot-link keypair, bind-mounted into the gateway (and
# backend) which run as the distroless nonroot UID 65532 — not the deploy user. The dir must be
# traversable by "other" (0755) or the nonroot process cannot open the 0644 keypair and crash-loops
# at startup ("mtls: load server keypair: ... permission denied") — a latent failure that only bites
# on a container restart (e.g. a host reboot), not while a long-running container holds the keypair
# in memory. The keys themselves are 0644 by design (see the gateway compose); the host is
# single-tenant and SSH-access-controlled, so a traversable certs dir adds no meaningful exposure.
- name: Create the scrabble certs directory (traversable by the nonroot gateway UID)
ansible.builtin.file:
path: "{{ scrabble_base_dir }}/certs"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: "0755"