Repository navigation
Add portable static build with HTTP/3 and daily releases (binary + Docker image) - #216
Open
serhii-nakon wants to merge 6 commits into
Open
serhii-nakon wants to merge 6 commits into
serhii-nakon wants to merge 6 commits into
Conversation
Add portable/Dockerfile that builds a fully statically linked https_dns_proxy with HTTP/3 (QUIC) support on Alpine (musl). OpenSSL, nghttp3, ngtcp2, nghttp2 and curl are built from source, because Alpine's libcurl has no HTTP/3 and its static library pulls in dependencies a DoH proxy does not use. libev and c-ares come from Alpine packages. The Dockerfile has two targets: - binary: exports only the executable - image: the executable and a CA bundle in a single layer on an empty base image, running as an unprivileged user Add a daily "Portable release" workflow. It runs only on the default branch and only when src, CMakeLists.txt or portable/ changed since the last release. It creates a release tagged with the build date (YYYY.MM.DD) with the binary, a systemd unit and SHA256SUMS, and pushes the image to ghcr.io/<owner>/https_dns_proxy. Add a hardened systemd unit for the portable binary and document the portable build and Docker image in the README, including why the static binary is about 8 MB instead of <30 kiB.
Git tags, Docker tags and GitHub Action tags are mutable: anyone who controls an upstream repository can move a tag to different content, and the next build would use it without notice. Commit hashes and image digests are computed from the content itself, so they cannot be redirected. - Pin the Alpine base image by digest. - Add a pinned commit for OpenSSL, nghttp3, ngtcp2, nghttp2 and curl. Each one is still cloned by its tag, but the build fails if the tag no longer points to the pinned commit. Submodules are pinned by the parent commit. - Pin all GitHub Actions in the release workflow by commit hash, with the version in a comment. The workflow can write releases and packages, so it must not run code that changed after review. Comments in both files explain the reason and how to update the pins.
The smoke test wrote versions.txt into the workspace between the two builds. That changed the build context, so the image build compiled the binary again with a different build time, and the binary in the image did not match the release asset or SHA256SUMS. Write all temporary files to the runner temp directory, and add a step that fails the release if the binary in the pushed image differs from the release binary.
- Build and run unit tests inside the Docker build, linked against the same static libraries as the release binary. - Run the TCP lifecycle integration test and the Robot Framework functional tests on the runner against the built static binary, from a copy outside the workspace so logs do not change the build context. - Test the pushed image with a resolver given by hostname, over UDP and TCP, and move the "latest" tag only after it passes.
Run the pushed image with -q against a resolver given by hostname, query it over UDP and TCP, and require curl to log "using HTTP/3", so a silent fallback or a broken QUIC stack blocks the release.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds an optional, self-contained way to ship https_dns_proxy as a single
portable executable and as a minimal Docker image, built and published
automatically by GitHub Actions. Nothing in
src/or in the existing CMakebuild changes.
What is added
portable/Dockerfile: builds a fully statichttps_dns_proxywithHTTP/3 (QUIC) support on Alpine (musl). It follows
development_build_with_http3.sh. OpenSSL, nghttp3, ngtcp2, nghttp2 and curlare built from source. libev and c-ares come from Alpine packages, which
already include static libraries. The unit tests (
ctest -L unit) are builtwith the same static libraries and must pass, or the build fails. The release
binary is byte-identical with and without
BUILD_TESTING. It has two targets:binary: exports only the executable.image: the executable, a CA bundle and/etc/servicesin one layer onan empty (
scratch) image. It runs as UID 65534 and listens on0.0.0.0:5053..github/workflows/portable-release.yml: runs daily and on manualdispatch, on the default branch only. If
src/,CMakeLists.txtorportable/changed since the last release, it:(it must be static and report HTTP3),
tests against the built static binary,
ghcr.io/<owner>/https_dns_proxy:<YYYY.MM.DD>,binary, and tests the image over UDP and TCP with a resolver given by
hostname, once with default settings and once with
-q(HTTP/3 only,checking that curl reports
using HTTP/3),latesttag to this image,YYYY.MM.DDwithhttps_dns_proxy-linux-amd64,https_dns_proxy.serviceandSHA256SUMS.portable/https_dns_proxy.service: a systemd unit for the portablebinary, with
DynamicUserand sandboxing options.Why
or installed packages. No build step and no
libcurlwith HTTP/3 needed onthe target.
Trade-offs to be aware of
nghttp2, c-ares, libev and musl are all linked in. The README explains this.
through a new release, not through distribution updates.
Type=simple.development_build_with_http3.shdescribes this kind ofbuild as for development and testing. The portable build pins exact,
current releases (OpenSSL 4.1.0, curl 8.23.0, ngtcp2 1.25.0, nghttp3 1.18.0,
nghttp2 1.70.0), but you may want to decide whether you are comfortable
publishing it as an official artifact.
servers. If one of them is down, that day's release fails, and the next
daily run retries it.
cannot replace
mallocin a static binary, so leak detection is weakerthan in the regular dynamic build that
cmake.ymlalready checks.Supply-chain hardening
point to the pinned commit hash.
releases and packages.
and LDAP, which a DoH proxy does not need.
untagged
sha256entries next to each tag.Setup needed after merge
contents: writeandpackages: write.release and image.
The image is linked to this repository through the
org.opencontainers.image.sourcelabel and gets the repository's visibility.In my fork it was public right after the first push, with no extra settings.
GitHub disables scheduled workflows after 60 days without repository
activity. A commit or a re-enable restarts them.
Testing
I have been running this build on my home router (behind dnsmasq, with
-q)for about a month: first with OpenSSL 3.6.5 and curl 8.22.0, and for the last
3 days with OpenSSL 4.1.0 and curl 8.23.0. I have seen no issues so far.
The workflow is also running in my fork (serhii-nakon/https_dns_proxy) and has
already published releases and images. Checks done on the static build:
similar to the GitHub runner).
-qand must reportusing HTTP/3.static-pie linked,-VreportsHTTP2 HTTP3, andSHA256SUMSverifies.-qand a hostname resolver (cloudflare-dns.com):bootstrap works,
using HTTP/3, queries resolve.scratchimage with-q -4: the certificate is verified from thebundled CA file,
using HTTP/3, UDP and TCP queries resolve.