Skip to content

Commit 5335ea2

Browse files
h4x3rotabclaude
andcommitted
Harden build reproducibility and input sanitization
- Pin haproxy base image by digest for reproducible builds - Add USER root comment explaining why (haproxy defaults to uid 99) - Add sanitize_alpn() validator; ALPN is now validated at startup - Require time suffix on haproxy timeouts (bare numbers are milliseconds in HAProxy which is almost never what users intend) - Remove `local` usage outside function scope in renewal-daemon.sh - Document ALPN env var in README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a44963b commit 5335ea2

File tree

5 files changed

+36
-8
lines changed

5 files changed

+36
-8
lines changed

custom-domain/dstack-ingress/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
FROM haproxy:3.1-bookworm
1+
FROM haproxy@sha256:49a0a0d6f0b8b7e59c233b06eefab1564f2c8d64f673554d368fd7d2ab4b2c2d
22

3+
# haproxy image runs as non-root (uid 99) by default; we need root for
4+
# certbot, DNS management, and writing to /etc/haproxy/certs.
35
USER root
46

57
RUN --mount=type=bind,source=pinned-packages.txt,target=/tmp/pinned-packages.txt,ro \

custom-domain/dstack-ingress/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ environment:
178178
| `TIMEOUT_SERVER` | `86400s` | Server-side timeout |
179179
| `EVIDENCE_SERVER` | `true` | Serve evidence files at `/evidences/` on the TLS port |
180180
| `EVIDENCE_PORT` | `80` | Internal port for evidence HTTP server |
181+
| `ALPN` | | TLS ALPN protocols (e.g. `h2,http/1.1`). Only set if backends support h2c |
181182

182183
For DNS provider credentials, see [DNS_PROVIDERS.md](DNS_PROVIDERS.md).
183184

custom-domain/dstack-ingress/scripts/functions.sh

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,28 @@ sanitize_positive_integer() {
8585
sanitize_haproxy_timeout() {
8686
local candidate="$1"
8787
local name="${2:-timeout}"
88-
if [[ "$candidate" =~ ^[0-9]+(us|ms|s|m|h|d)?$ ]]; then
88+
# Require a time suffix — bare numbers are milliseconds in HAProxy,
89+
# which is almost never what users intend.
90+
if [[ "$candidate" =~ ^[0-9]+(us|ms|s|m|h|d)$ ]]; then
8991
echo "$candidate"
9092
else
91-
echo "Error: Invalid ${name}: $candidate (e.g. 10s, 5m, 86400s)" >&2
93+
echo "Error: Invalid ${name}: $candidate (must include suffix, e.g. 10s, 5m, 86400s)" >&2
94+
return 1
95+
fi
96+
}
97+
98+
sanitize_alpn() {
99+
local candidate="$1"
100+
if [ -z "$candidate" ]; then
101+
echo ""
102+
return 0
103+
fi
104+
# ALPN value is comma-separated protocol names (e.g. "h2,http/1.1")
105+
# Only allow alphanumeric, dots, slashes, hyphens, and commas.
106+
if [[ "$candidate" =~ ^[A-Za-z0-9./-]+(,[A-Za-z0-9./-]+)*$ ]]; then
107+
echo "$candidate"
108+
else
109+
echo "Error: Invalid ALPN value: $candidate (e.g. h2,http/1.1)" >&2
92110
return 1
93111
fi
94112
}

custom-domain/dstack-ingress/scripts/renewal-daemon.sh

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ while true; do
2525
build-combined-pems.sh || echo "Combined PEM build failed"
2626

2727
# Graceful reload: send SIGUSR2 to haproxy master process
28-
local pidfile="/var/run/haproxy/haproxy.pid"
29-
if [ ! -f "$pidfile" ]; then
30-
echo "HAProxy reload failed: PID file $pidfile not found" >&2
31-
elif ! kill -USR2 "$(cat "$pidfile")"; then
32-
echo "HAProxy reload failed: SIGUSR2 to PID $(cat "$pidfile") failed" >&2
28+
if [ ! -f /var/run/haproxy/haproxy.pid ]; then
29+
echo "HAProxy reload failed: PID file /var/run/haproxy/haproxy.pid not found" >&2
30+
elif ! kill -USR2 "$(cat /var/run/haproxy/haproxy.pid)"; then
31+
echo "HAProxy reload failed: SIGUSR2 to PID $(cat /var/run/haproxy/haproxy.pid) failed" >&2
3332
else
3433
echo "Certificate renewed and HAProxy reloaded successfully"
3534
fi

custom-domain/dstack-ingress/scripts/tests/test_sanitizers.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ assert_equal "$(sanitize_haproxy_timeout 5m TIMEOUT)" "5m" "sanitize_haproxy_tim
6363
assert_equal "$(sanitize_haproxy_timeout 500ms TIMEOUT)" "500ms" "sanitize_haproxy_timeout accepts 500ms"
6464
assert_equal "$(sanitize_haproxy_timeout 100us TIMEOUT)" "100us" "sanitize_haproxy_timeout accepts 100us"
6565
assert_equal "$(sanitize_haproxy_timeout 1d TIMEOUT)" "1d" "sanitize_haproxy_timeout accepts 1d"
66+
assert_equal "$(sanitize_alpn 'h2,http/1.1')" "h2,http/1.1" "sanitize_alpn accepts h2,http/1.1"
67+
assert_equal "$(sanitize_alpn 'h2')" "h2" "sanitize_alpn accepts h2"
68+
assert_equal "$(sanitize_alpn 'http/1.1')" "http/1.1" "sanitize_alpn accepts http/1.1"
69+
assert_equal "$(sanitize_alpn '')" "" "sanitize_alpn accepts empty"
6670

6771
# Failing cases
6872
assert_fails "sanitize_port rejects non-numeric" sanitize_port abc
@@ -100,6 +104,10 @@ assert_fails "sanitize_dns_label rejects invalid characters" sanitize_dns_label
100104
assert_fails "sanitize_positive_integer rejects zero" sanitize_positive_integer 0 MAXCONN
101105
assert_fails "sanitize_positive_integer rejects non-numeric" sanitize_positive_integer abc MAXCONN
102106
assert_fails "sanitize_haproxy_timeout rejects bare text" sanitize_haproxy_timeout abc TIMEOUT
107+
assert_fails "sanitize_haproxy_timeout rejects bare number" sanitize_haproxy_timeout 10 TIMEOUT
108+
assert_fails "sanitize_alpn rejects semicolons" sanitize_alpn "h2;drop"
109+
assert_fails "sanitize_alpn rejects newlines" sanitize_alpn $'h2\nhttp/1.1'
110+
assert_fails "sanitize_alpn rejects spaces" sanitize_alpn "h2, http/1.1"
103111

104112
if [[ $failures -eq 0 ]]; then
105113
echo "All sanitizer tests passed"

0 commit comments

Comments
 (0)