Skip to content

Commit c858558

Browse files
authored
feat: support custom CA certificates for all containers (#4216)
1 parent e04fa72 commit c858558

10 files changed

+398
-8
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
certificates/.gitignore

.env

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ HEALTHCHECK_FILE_START_PERIOD=600s
2929
# Set SETUP_JS_SDK_ASSETS to 1 to enable the setup of JS SDK assets
3030
# SETUP_JS_SDK_ASSETS=1
3131
#
32+
# Set SETUP_CUSTOM_CA_CERTIFICATE to 1 to generate trust stores that inject
33+
# your custom CA certificates (from ./certificates/*.crt) into non-Sentry
34+
# services (relay, symbolicator, vroom, snuba, taskbroker, uptime-checker).
35+
# Re-run ./install.sh after adding or changing certificates — no Docker image
36+
# rebuilds are required. Follow the printed instructions to update your
37+
# docker-compose.override.yml on the first run.
38+
# SETUP_CUSTOM_CA_CERTIFICATE=1
39+
#
3240
# Sentry (and its' surrounding services) uses Statsd for metrics collection.
3341
# It's also the proper way to monitor self-hosted Sentry systems.
3442
# Set STATSD_ADDR to a valid IP:PORT combination to enable Statsd metrics collection.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ target/
7171

7272
# https://docs.docker.com/compose/extends/
7373
docker-compose.override.yml
74+
# Generated by install/setup-custom-ca-certificate.sh and rebuilt on each run.
75+
# Do not version these ephemeral trust-store artifacts.
76+
certificates/.generated/
7477

7578
# https://docs.docker.com/compose/environment-variables/#using-the---env-file--option
7679
.env.custom

_unit-test/bootstrap-s3-profiles-test.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ source install/dc-detect-version.sh
55
source install/create-docker-volumes.sh
66
source install/ensure-files-from-examples.sh
77
export COMPOSE_PROFILES="feature-complete"
8-
$dc pull vroom
8+
# `docker compose pull vroom` can be skipped when pull_policy is set to `never`.
9+
$CONTAINER_ENGINE pull "${VROOM_IMAGE}"
910
source install/ensure-correct-permissions-profiles-dir.sh
1011

1112
# Generate some random files on `sentry-vroom` volume for testing
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env bash
2+
3+
source _unit-test/_test_setup.sh
4+
source install/dc-detect-version.sh
5+
6+
CERT_DIR="./certificates"
7+
GENERATED_DIR="${CERT_DIR}/.generated"
8+
9+
# -----------------------------------------------------------------------
10+
# Test 1: Feature flag is NOT set → script is a no-op, no generated dir.
11+
# -----------------------------------------------------------------------
12+
unset SETUP_CUSTOM_CA_CERTIFICATE
13+
source install/setup-custom-ca-certificate.sh
14+
test ! -d "$GENERATED_DIR"
15+
echo "Pass: no-op when SETUP_CUSTOM_CA_CERTIFICATE is unset"
16+
17+
# -----------------------------------------------------------------------
18+
# Test 2: Feature flag set but no .crt files → prints a message, no-op.
19+
# -----------------------------------------------------------------------
20+
export SETUP_CUSTOM_CA_CERTIFICATE=1
21+
source install/setup-custom-ca-certificate.sh
22+
test ! -d "$GENERATED_DIR"
23+
echo "Pass: no-op when certificates/ has no .crt files"
24+
25+
# -----------------------------------------------------------------------
26+
# Shared setup: generate a self-signed test CA certificate.
27+
# -----------------------------------------------------------------------
28+
openssl req -x509 -newkey rsa:2048 \
29+
-keyout /tmp/self-hosted-test-ca.key \
30+
-out "${CERT_DIR}/self-hosted-test-ca.crt" \
31+
-days 1 -nodes -subj "/CN=Self-Hosted Test CA DO NOT TRUST" 2>/dev/null
32+
echo "Test certificate generated."
33+
34+
# -----------------------------------------------------------------------
35+
# Test 3: Invalid .crt file → script exits non-zero (subshell to isolate).
36+
# -----------------------------------------------------------------------
37+
echo "not a certificate" >"${CERT_DIR}/bad.crt"
38+
if (
39+
export SETUP_CUSTOM_CA_CERTIFICATE=1
40+
source install/setup-custom-ca-certificate.sh
41+
) 2>/dev/null; then
42+
echo "Expected setup-custom-ca-certificate.sh to fail for invalid certificate input"
43+
exit 1
44+
fi
45+
rm "${CERT_DIR}/bad.crt"
46+
echo "Pass: invalid certificate causes a non-zero exit"
47+
48+
# -----------------------------------------------------------------------
49+
# Test 4: Happy path — valid cert, flag set.
50+
#
51+
# We override all image vars to sentry-self-hosted-jq-local (guaranteed
52+
# present after _test_setup.sh) so the test runs without pulling large
53+
# upstream images. The jq image has no /etc/ssl/certs, so the script uses
54+
# an empty baseline and overlays our custom cert on top.
55+
# -----------------------------------------------------------------------
56+
export RELAY_IMAGE=sentry-self-hosted-jq-local
57+
export SYMBOLICATOR_IMAGE=sentry-self-hosted-jq-local
58+
export SNUBA_IMAGE=sentry-self-hosted-jq-local
59+
export VROOM_IMAGE=sentry-self-hosted-jq-local
60+
export TASKBROKER_IMAGE=sentry-self-hosted-jq-local
61+
export UPTIME_CHECKER_IMAGE=sentry-self-hosted-jq-local
62+
63+
export SETUP_CUSTOM_CA_CERTIFICATE=1
64+
source install/setup-custom-ca-certificate.sh
65+
66+
# The generated directory must exist.
67+
test -d "$GENERATED_DIR"
68+
echo "Pass: generated directory was created"
69+
70+
# Each service's trust store directory must exist.
71+
for nickname in relay symbolicator snuba vroom taskbroker uptime-checker; do
72+
test -d "${GENERATED_DIR}/${nickname}/etc/ssl/certs"
73+
echo " Pass: ${nickname} trust store directory exists"
74+
done
75+
76+
# Each service's bundle must contain the custom cert's PEM block.
77+
for nickname in relay symbolicator snuba vroom taskbroker uptime-checker; do
78+
bundle="${GENERATED_DIR}/${nickname}/etc/ssl/certs/ca-certificates.crt"
79+
test -f "$bundle"
80+
grep -q "BEGIN CERTIFICATE" "$bundle"
81+
echo " Pass: ${nickname} ca-certificates.crt contains at least one certificate"
82+
done
83+
84+
# The individual .crt file must be present in each service's cert dir.
85+
for nickname in relay symbolicator snuba vroom taskbroker uptime-checker; do
86+
test -f "${GENERATED_DIR}/${nickname}/etc/ssl/certs/self-hosted-test-ca.crt"
87+
echo " Pass: ${nickname} has the individual self-hosted-test-ca.crt file"
88+
done
89+
90+
# openssl rehash must have created at least one hash symlink per service dir.
91+
for nickname in relay symbolicator snuba vroom taskbroker uptime-checker; do
92+
cert_dir="${GENERATED_DIR}/${nickname}/etc/ssl/certs"
93+
hash_links=$(find "$cert_dir" -maxdepth 1 -type l | wc -l)
94+
test "$hash_links" -gt 0
95+
echo " Pass: ${nickname} has ${hash_links} OpenSSL hash symlink(s)"
96+
done
97+
98+
# -----------------------------------------------------------------------
99+
# Test 5: Idempotency — running again produces the same result.
100+
# -----------------------------------------------------------------------
101+
source install/setup-custom-ca-certificate.sh
102+
103+
for nickname in relay symbolicator snuba vroom taskbroker uptime-checker; do
104+
bundle="${GENERATED_DIR}/${nickname}/etc/ssl/certs/ca-certificates.crt"
105+
count=$(grep -c "BEGIN CERTIFICATE" "$bundle")
106+
# Each run wipes and rebuilds .generated/, so exactly one copy of our cert.
107+
test "$count" -eq 1
108+
echo " Pass: ${nickname} bundle has exactly 1 certificate after second run (no duplication)"
109+
done
110+
echo "Pass: script is idempotent"
111+
112+
report_success

docker-compose.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ x-sentry-defaults: &sentry_defaults
7777
- "./geoip:/geoip:ro"
7878
- "./certificates:/usr/local/share/ca-certificates:ro"
7979
x-snuba-defaults: &snuba_defaults
80-
<<: *restart_policy
80+
<<: [*restart_policy, *pull_policy]
8181
depends_on:
8282
clickhouse:
8383
<<: *depends_on-healthy
@@ -470,7 +470,7 @@ services:
470470
healthcheck:
471471
<<: *file_healthcheck_defaults
472472
symbolicator:
473-
<<: *restart_policy
473+
<<: [*restart_policy, *pull_policy]
474474
image: "$SYMBOLICATOR_IMAGE"
475475
environment:
476476
SYMBOLICATOR_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125}
@@ -708,7 +708,7 @@ services:
708708
<<: *depends_on-healthy
709709
restart: true
710710
relay:
711-
<<: *restart_policy
711+
<<: [*restart_policy, *pull_policy]
712712
image: "$RELAY_IMAGE"
713713
environment:
714714
RELAY_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125}
@@ -732,7 +732,7 @@ services:
732732
<<: *healthcheck_defaults
733733
test: ["CMD", "/bin/relay", "healthcheck"]
734734
taskbroker:
735-
<<: *restart_policy
735+
<<: [*restart_policy, *pull_policy]
736736
image: "$TASKBROKER_IMAGE"
737737
environment:
738738
TASKBROKER_KAFKA_CLUSTER: "kafka:9092"
@@ -753,7 +753,7 @@ services:
753753
healthcheck:
754754
<<: *file_healthcheck_defaults
755755
vroom:
756-
<<: *restart_policy
756+
<<: [*restart_policy, *pull_policy]
757757
image: "$VROOM_IMAGE"
758758
environment:
759759
SENTRY_KAFKA_BROKERS_PROFILING: "kafka:9092"
@@ -778,7 +778,7 @@ services:
778778
profiles:
779779
- feature-complete
780780
uptime-checker:
781-
<<: *restart_policy
781+
<<: [*restart_policy, *pull_policy]
782782
image: "$UPTIME_CHECKER_IMAGE"
783783
command: run
784784
environment:

install.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ source install/set-up-and-migrate-database.sh
4545
source install/migrate-pgbouncer.sh
4646
source install/geoip.sh
4747
source install/setup-js-sdk-assets.sh
48+
source install/setup-custom-ca-certificate.sh
4849
source install/wrap-up.sh

install/ensure-relay-credentials.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ else
2424
# 1.x and 2.2.3+ (funny story about that ... ;). Note that the long opt
2525
# --no-tty doesn't exist in Docker Compose 1.
2626

27-
$dc pull relay
27+
# `docker compose pull relay` can be skipped when service-level pull_policy is
28+
# set to `never`, so pull the configured relay image directly.
29+
$CONTAINER_ENGINE pull "${RELAY_IMAGE}" || true
2830
creds="$dcr --no-deps -T relay credentials"
2931
$creds generate --stdout >"$RELAY_CREDENTIALS_JSON".tmp
3032
mv "$RELAY_CREDENTIALS_JSON".tmp "$RELAY_CREDENTIALS_JSON"

0 commit comments

Comments
 (0)