From 8e4e73147ff560ff90f566d430edc3979ee636e9 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Thu, 3 Sep 2026 19:08:30 +0000 Subject: [PATCH] feat(ingress): enable X25519MLKEM768 TLS --- custom-domain/dstack-ingress/Dockerfile | 10 +- .../dstack-ingress/pinned-packages.txt | 280 +++++++++--------- custom-domain/dstack-ingress/requirements.txt | 12 +- .../dstack-ingress/scripts/haproxy-lib.sh | 2 +- .../scripts/tests/test_x25519mlkem768.sh | 58 ++++ 5 files changed, 208 insertions(+), 154 deletions(-) create mode 100755 custom-domain/dstack-ingress/scripts/tests/test_x25519mlkem768.sh diff --git a/custom-domain/dstack-ingress/Dockerfile b/custom-domain/dstack-ingress/Dockerfile index 369c9a6a..c3165e2a 100644 --- a/custom-domain/dstack-ingress/Dockerfile +++ b/custom-domain/dstack-ingress/Dockerfile @@ -1,4 +1,4 @@ -FROM haproxy@sha256:49a0a0d6f0b8b7e59c233b06eefab1564f2c8d64f673554d368fd7d2ab4b2c2d +FROM haproxy@sha256:f8095d0b91414340dbcd53bbe89585e17f333ec1f2a0f60456cb53a90486ece2 # haproxy image runs as non-root (uid 99) by default; we need root for # certbot, DNS management, and writing to /etc/haproxy/certs. @@ -7,8 +7,8 @@ USER root RUN --mount=type=bind,source=pinned-packages.txt,target=/tmp/pinned-packages.txt,ro \ set -e; \ # Create a sources.list file pointing to a specific snapshot - echo 'deb [check-valid-until=no] https://snapshot.debian.org/archive/debian/20250411T024939Z bookworm main' > /etc/apt/sources.list && \ - echo 'deb [check-valid-until=no] https://snapshot.debian.org/archive/debian-security/20250411T024939Z bookworm-security main' >> /etc/apt/sources.list && \ + echo 'deb [check-valid-until=no] https://snapshot.debian.org/archive/debian/20260824T000000Z trixie main' > /etc/apt/sources.list && \ + echo 'deb [check-valid-until=no] https://snapshot.debian.org/archive/debian-security/20260824T000000Z trixie-security main' >> /etc/apt/sources.list && \ echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/10no-check-valid-until && \ # Create preferences file to pin all packages rm -rf /etc/apt/sources.list.d/debian.sources && \ @@ -26,8 +26,8 @@ RUN --mount=type=bind,source=pinned-packages.txt,target=/tmp/pinned-packages.txt bash \ python3-pip \ python3-requests \ - python3.11 \ - python3.11-venv \ + python3 \ + python3-venv \ curl \ jq \ coreutils \ diff --git a/custom-domain/dstack-ingress/pinned-packages.txt b/custom-domain/dstack-ingress/pinned-packages.txt index 3058c6e4..f1a3ee97 100644 --- a/custom-domain/dstack-ingress/pinned-packages.txt +++ b/custom-domain/dstack-ingress/pinned-packages.txt @@ -1,144 +1,136 @@ -adduser=3.134 -apt=2.6.1 -base-files=12.4+deb12u11 -base-passwd=3.6.1 -bash=5.2.15-2+b8 -bsdutils=1:2.38.1-5+deb12u3 -ca-certificates=20230311+deb12u1 -coreutils=9.1-1 -curl=7.88.1-10+deb12u12 -dash=0.5.12-2 -debconf=1.5.82 -debian-archive-keyring=2023.3+deb12u2 -debianutils=5.7-0.5~deb12u1 -diffutils=1:3.8-4 -dpkg=1.21.22 -e2fsprogs=1.47.0-2 -findutils=4.9.0-4 -gcc-12-base:amd64=12.2.0-14+deb12u1 -gpgv=2.2.40-1.1 -grep=3.8-5 -gzip=1.12-1 -hostname=3.23+nmu1 -init-system-helpers=1.65.2 -jq=1.6-2.1 -libacl1:amd64=2.3.1-3 -libapt-pkg6.0:amd64=2.6.1 -libattr1:amd64=1:2.5.1-4 -libaudit-common=1:3.0.9-1 -libaudit1:amd64=1:3.0.9-1 -libblkid1:amd64=2.38.1-5+deb12u3 -libbrotli1:amd64=1.0.9-2+b6 -libbz2-1.0:amd64=1.0.8-5+b1 -libc-bin=2.36-9+deb12u10 -libc6:amd64=2.36-9+deb12u10 -libcap-ng0:amd64=0.8.3-1+b3 -libcap2:amd64=1:2.66-4+deb12u1 -libcom-err2:amd64=1.47.0-2 -libcrypt1:amd64=1:4.4.33-2 -libcurl4:amd64=7.88.1-10+deb12u12 -libdb5.3:amd64=5.3.28+dfsg2-1 -libdebconfclient0:amd64=0.270 -libexpat1:amd64=2.5.0-1+deb12u1 -libext2fs2:amd64=1.47.0-2 -libffi8:amd64=3.4.4-1 -libgcc-s1:amd64=12.2.0-14+deb12u1 -libgcrypt20:amd64=1.10.1-3 -libgmp10:amd64=2:6.2.1+dfsg1-1.1 -libgnutls30:amd64=3.7.9-2+deb12u5 -libgpg-error0:amd64=1.46-1 -libgssapi-krb5-2:amd64=1.20.1-2+deb12u2 -libhogweed6:amd64=3.8.1-2 -libidn2-0:amd64=2.3.3-1+b1 -libjq1:amd64=1.6-2.1 -libk5crypto3:amd64=1.20.1-2+deb12u2 -libkeyutils1:amd64=1.6.3-2 -libkrb5-3:amd64=1.20.1-2+deb12u2 -libkrb5support0:amd64=1.20.1-2+deb12u2 -libldap-2.5-0:amd64=2.5.13+dfsg-5 -liblua5.4-0:amd64=5.4.4-3+deb12u1 -liblz4-1:amd64=1.9.4-1 -liblzma5:amd64=5.4.1-1 -libmd0:amd64=1.0.4-2 -libmount1:amd64=2.38.1-5+deb12u3 -libncursesw6:amd64=6.4-4 -libnettle8:amd64=3.8.1-2 -libnghttp2-14:amd64=1.52.0-1+deb12u2 -libnsl2:amd64=1.3.0-2 -libonig5:amd64=6.9.8-1 -libp11-kit0:amd64=0.24.1-2 -libpam-modules-bin=1.5.2-6+deb12u1 -libpam-modules:amd64=1.5.2-6+deb12u1 -libpam-runtime=1.5.2-6+deb12u1 -libpam0g:amd64=1.5.2-6+deb12u1 -libpcre2-8-0:amd64=10.42-1 -libpsl5:amd64=0.21.2-1 -libpython3-stdlib:amd64=3.11.2-1+b1 -libpython3.11-minimal:amd64=3.11.2-6+deb12u5 -libpython3.11-stdlib:amd64=3.11.2-6+deb12u5 -libreadline8:amd64=8.2-1.3 -librtmp1:amd64=2.4+20151223.gitfa8646d.1-2+b2 -libsasl2-2:amd64=2.1.28+dfsg-10 -libsasl2-modules-db:amd64=2.1.28+dfsg-10 -libseccomp2:amd64=2.5.4-1+deb12u1 -libselinux1:amd64=3.4-1+b6 -libsemanage-common=3.4-1 -libsemanage2:amd64=3.4-1+b5 -libsepol2:amd64=3.4-2.1 -libsmartcols1:amd64=2.38.1-5+deb12u3 -libsqlite3-0:amd64=3.40.1-2+deb12u1 -libss2:amd64=1.47.0-2 -libssh2-1:amd64=1.10.0-3+b1 -libssl3:amd64=3.0.17-1~deb12u2 -libstdc++6:amd64=12.2.0-14+deb12u1 -libsystemd0:amd64=252.38-1~deb12u1 -libtasn1-6:amd64=4.19.0-2+deb12u1 -libtinfo6:amd64=6.4-4 -libtirpc-common=1.3.3+ds-1 -libtirpc3:amd64=1.3.3+ds-1 -libudev1:amd64=252.38-1~deb12u1 -libunistring2:amd64=1.0-2 -libuuid1:amd64=2.38.1-5+deb12u3 -libxxhash0:amd64=0.8.1-1 -libzstd1:amd64=1.5.4+dfsg2-5 -login=1:4.13+dfsg1-1+deb12u1 -logsave=1.47.0-2 -mawk=1.3.4.20200120-3.1 -lsb-base=11.6 -media-types=10.0.0 -mini-httpd=1.30-3 -mount=2.38.1-5+deb12u3 -ncurses-base=6.4-4 -ncurses-bin=6.4-4 -openssl=3.0.17-1~deb12u2 -passwd=1:4.13+dfsg1-1+deb12u1 -perl-base=5.36.0-7+deb12u2 -python3-certifi=2022.9.24-1 -python3-chardet=5.1.0+dfsg-2 -python3-charset-normalizer=3.0.1-2 -python3-distutils=3.11.2-3 -python3-idna=3.3-1+deb12u1 -python3-lib2to3=3.11.2-3 -python3-minimal=3.11.2-1+b1 -python3-pip-whl=23.0.1+dfsg-1 -python3-pip=23.0.1+dfsg-1 -python3-pkg-resources=66.1.1-1+deb12u1 -python3-requests=2.28.1+dfsg-1 -python3-setuptools-whl=66.1.1-1+deb12u1 -python3-setuptools=66.1.1-1+deb12u1 -python3-six=1.16.0-4 -python3-urllib3=1.26.12-1+deb12u1 -python3-wheel=0.38.4-2 -python3.11-minimal=3.11.2-6+deb12u5 -python3.11-venv=3.11.2-6+deb12u5 -python3.11=3.11.2-6+deb12u5 -python3=3.11.2-1+b1 -readline-common=8.2-1.3 -sed=4.9-1 -sysvinit-utils=3.06-4 -tar=1.34+dfsg-1.2+deb12u1 -tzdata=2025b-0+deb12u1 -usr-is-merged=37~deb12u1 -util-linux-extra=2.38.1-5+deb12u3 -util-linux=2.38.1-5+deb12u3 -zlib1g:amd64=1:1.2.13.dfsg-1 +apt=3.0.3 +base-files=13.8+deb13u6 +base-passwd=3.6.7 +bash=5.2.37-2+b9 +bsdutils=1:2.41.5-0+deb13u1 +ca-certificates=20250419 +coreutils=9.7-3 +curl=8.14.1-2+deb13u4 +dash=0.5.12-12 +debconf=1.5.91 +debian-archive-keyring=2025.1 +debianutils=5.23.2 +diffutils=1:3.10-4 +dpkg=1.22.22 +findutils=4.10.0-3 +gcc-14-base:amd64=14.2.0-19 +grep=3.11-4 +gzip=1.13-1 +hostname=3.25 +init-system-helpers=1.69~deb13u1 +jq=1.7.1-6+deb13u3 +libacl1:amd64=2.3.2-2+b1 +libapt-pkg7.0:amd64=3.0.3 +libattr1:amd64=1:2.5.2-3 +libaudit-common=1:4.0.2-2 +libaudit1:amd64=1:4.0.2-2+b2 +libblkid1:amd64=2.41.5-0+deb13u1 +libbrotli1:amd64=1.1.0-2+b7 +libbsd0:amd64=0.12.2-2 +libbz2-1.0:amd64=1.0.8-6 +libc-bin=2.41-12+deb13u3 +libc6:amd64=2.41-12+deb13u3 +libcap-ng0:amd64=0.8.5-4+b1 +libcap2:amd64=1:2.75-10+deb13u1+b1 +libcom-err2:amd64=1.47.2-3+b11 +libcrypt1:amd64=1:4.4.38-1 +libcurl4t64:amd64=8.14.1-2+deb13u4 +libdb5.3t64:amd64=5.3.28+dfsg2-9 +libdebconfclient0:amd64=0.280 +libexpat1:amd64=2.8.3-1~deb13u1 +libffi8:amd64=3.4.8-2 +libgcc-s1:amd64=14.2.0-19 +libgmp10:amd64=2:6.3.0+dfsg-3 +libgnutls30t64:amd64=3.8.9-3+deb13u4 +libgssapi-krb5-2:amd64=1.21.3-5+deb13u1 +libhogweed6t64:amd64=3.10.1-1 +libidn2-0:amd64=2.3.8-2 +libjq1:amd64=1.7.1-6+deb13u3 +libk5crypto3:amd64=1.21.3-5+deb13u1 +libkeyutils1:amd64=1.6.3-6 +libkrb5-3:amd64=1.21.3-5+deb13u1 +libkrb5support0:amd64=1.21.3-5+deb13u1 +liblastlog2-2:amd64=2.41.5-0+deb13u1 +libldap2:amd64=2.6.10+dfsg-1 +liblua5.4-0:amd64=5.4.7-1+b2 +liblz4-1:amd64=1.10.0-4 +liblzma5:amd64=5.8.1-1+deb13u1 +libmd0:amd64=1.1.0-2+b1 +libmount1:amd64=2.41.5-0+deb13u1 +libncursesw6:amd64=6.5+20250216-2 +libnettle8t64:amd64=3.10.1-1 +libnghttp2-14:amd64=1.64.0-1.1+deb13u1 +libnghttp3-9:amd64=1.8.0-1 +libonig5:amd64=6.9.9-1+b1 +libp11-kit0:amd64=0.25.5-3 +libpam-modules-bin=1.7.0-5 +libpam-modules:amd64=1.7.0-5 +libpam-runtime=1.7.0-5 +libpam0g:amd64=1.7.0-5 +libpcre2-8-0:amd64=10.46-1~deb13u1 +libpsl5t64:amd64=0.21.2-1.1+b1 +libpython3-stdlib:amd64=3.13.5-1 +libpython3.13-minimal:amd64=3.13.5-2+deb13u3 +libpython3.13-stdlib:amd64=3.13.5-2+deb13u3 +libreadline8t64:amd64=8.2-6 +librtmp1:amd64=2.4+20151223.gitfa8646d.1-2+b5 +libsasl2-2:amd64=2.1.28+dfsg1-9 +libsasl2-modules-db:amd64=2.1.28+dfsg1-9 +libseccomp2:amd64=2.6.0-2 +libselinux1:amd64=3.8.1-1 +libsemanage-common=3.8.1-1 +libsemanage2:amd64=3.8.1-1 +libsepol2:amd64=3.8.1-1 +libsmartcols1:amd64=2.41.5-0+deb13u1 +libsqlite3-0:amd64=3.46.1-7+deb13u1 +libssh2-1t64:amd64=1.11.1-1+deb13u1 +libssl3t64:amd64=3.5.7-1~deb13u2 +libstdc++6:amd64=14.2.0-19 +libsystemd0:amd64=257.13-1~deb13u1 +libtasn1-6:amd64=4.20.0-2+deb13u1 +libtinfo6:amd64=6.5+20250216-2 +libudev1:amd64=257.13-1~deb13u1 +libunistring5:amd64=1.3-2 +libuuid1:amd64=2.41.5-0+deb13u1 +libwrap0:amd64=7.6.q-36 +libxxhash0:amd64=0.8.3-2 +libzstd1:amd64=1.5.7+dfsg-1 +login.defs=1:4.17.4-2 +login=1:4.16.0-2+really2.41.5-0+deb13u1 +mawk=1.3.4.20250131-1 +media-types=13.0.0 +mini-httpd=1.30-13 +mount=2.41.5-0+deb13u1 +ncurses-base=6.5+20250216-2 +ncurses-bin=6.5+20250216-2 +netbase=6.5 +openssl-provider-legacy=3.5.7-1~deb13u2 +openssl=3.5.7-1~deb13u2 +passwd=1:4.17.4-2 +perl-base=5.40.1-6 +python3-certifi=2025.1.31+ds-1 +python3-chardet=5.2.0+dfsg-2 +python3-charset-normalizer=3.4.2-1 +python3-idna=3.10-1+deb13u1 +python3-minimal=3.13.5-1 +python3-packaging=25.0-1 +python3-pip-whl=25.1.1+dfsg-1 +python3-pip=25.1.1+dfsg-1 +python3-requests=2.32.3+dfsg-5+deb13u1 +python3-setuptools-whl=78.1.1-0.1 +python3-urllib3=2.3.0-3+deb13u2 +python3-venv=3.13.5-1 +python3-wheel=0.46.1-2 +python3.13-minimal=3.13.5-2+deb13u3 +python3.13-venv=3.13.5-2+deb13u3 +python3.13=3.13.5-2+deb13u3 +python3=3.13.5-1 +readline-common=8.2-6 +sed=4.9-2+deb13u1 +socat=1.8.0.3-1 +sqv=1.3.0-3+b2 +sysvinit-utils=3.14-4 +tar=1.35+dfsg-3.1 +tzdata=2026b-0+deb13u1 +util-linux=2.41.5-0+deb13u1 +zlib1g:amd64=1:1.3.dfsg+really1.3.1-1+b1 diff --git a/custom-domain/dstack-ingress/requirements.txt b/custom-domain/dstack-ingress/requirements.txt index d4f95736..90ae7ff6 100644 --- a/custom-domain/dstack-ingress/requirements.txt +++ b/custom-domain/dstack-ingress/requirements.txt @@ -25,10 +25,12 @@ certifi==2026.7.22 \ --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 cffi==2.1.1 \ --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ - --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 charset-normalizer==3.5.1 \ --hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \ - --hash=sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8 + --hash=sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8 \ + --hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 cloudflare==2.19.4 \ --hash=sha256:3b6000a01a237c23bccfdf6d20256ea5111ec74a826ae9e74f9f0e5bb5b2383f ConfigArgParse==1.7.5 \ @@ -69,7 +71,8 @@ python-dateutil==2.9.0.post0 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 PyYAML==6.0.3 \ --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ - --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 requests==2.32.3 \ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 requests-file==3.0.1 \ @@ -92,4 +95,5 @@ urllib3==2.7.0 \ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 zope.interface==8.6 \ --hash=sha256:9217b1123f6aeec9ddf1789bffd83da3123546d551c164a99f862a5d1f5ac0f8 \ - --hash=sha256:a43e669d68fd8c10fe315812f7e1d262c6c00e9667f29f799a3771f9a3b5b41d + --hash=sha256:a43e669d68fd8c10fe315812f7e1d262c6c00e9667f29f799a3771f9a3b5b41d \ + --hash=sha256:09522cdc6a77376bc36988b531db3b568c8cb0b6ca7286d8316aab283888770f diff --git a/custom-domain/dstack-ingress/scripts/haproxy-lib.sh b/custom-domain/dstack-ingress/scripts/haproxy-lib.sh index 8d506fce..94f3a52d 100644 --- a/custom-domain/dstack-ingress/scripts/haproxy-lib.sh +++ b/custom-domain/dstack-ingress/scripts/haproxy-lib.sh @@ -31,7 +31,7 @@ global ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets - ssl-default-bind-curves secp384r1 + ssl-default-bind-curves X25519MLKEM768:secp384r1 defaults log global diff --git a/custom-domain/dstack-ingress/scripts/tests/test_x25519mlkem768.sh b/custom-domain/dstack-ingress/scripts/tests/test_x25519mlkem768.sh new file mode 100755 index 00000000..2268881e --- /dev/null +++ b/custom-domain/dstack-ingress/scripts/tests/test_x25519mlkem768.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Verify that the ingress image can terminate TLS 1.3 with the +# X25519MLKEM768 hybrid key-exchange group. +# +# Usage: ./scripts/tests/test_x25519mlkem768.sh + +set -euo pipefail + +IMAGE="${1:?usage: $0 }" +CONTAINER="dstack-ingress-x25519mlkem768-$$" + +# shellcheck disable=SC2317 # Invoked by the EXIT trap below. +cleanup() { + docker rm -f "${CONTAINER}" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +docker run -d --rm --name "${CONTAINER}" --entrypoint bash "${IMAGE}" -c ' + set -euo pipefail + openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout /tmp/key.pem -out /tmp/cert.pem \ + -subj /CN=localhost -days 1 >/dev/null 2>&1 + cat /tmp/key.pem /tmp/cert.pem >/tmp/test.pem + + source /scripts/haproxy-lib.sh + MAXCONN=16 + TIMEOUT_CONNECT=5s + TIMEOUT_CLIENT=30s + TIMEOUT_SERVER=30s + haproxy_emit_global + + cat >>/etc/haproxy/haproxy.cfg </dev/null + +for _ in {1..20}; do + handshake="$(docker exec "${CONTAINER}" bash -c \ + 'openssl s_client -connect 127.0.0.1:24443 -servername localhost -tls1_3 -groups X25519MLKEM768 &1' \ + || true)" + if grep -Fq 'Negotiated TLS1.3 group: X25519MLKEM768' <<<"${handshake}"; then + grep -F 'Negotiated TLS1.3 group: X25519MLKEM768' <<<"${handshake}" + exit 0 + fi + sleep 0.1 +done + +printf '%s\n' "${handshake}" >&2 +echo 'X25519MLKEM768 TLS handshake did not complete' >&2 +exit 1