diff --git a/.github/workflows/build_binary.yml b/.github/workflows/build_binary.yml index e7ee0c398a2..db4c1393923 100644 --- a/.github/workflows/build_binary.yml +++ b/.github/workflows/build_binary.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | apt-get update - apt-get install -y --no-install-recommends git ca-certificates gcc libc6-dev curl make zip + apt-get install -y --no-install-recommends git ca-certificates gcc g++ libc6-dev curl make zip - uses: actions/checkout@v7.0.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b307e45f7..ea074a038d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Upgrade release image to Debian 13. ([#6110](https://github.com/getsentry/relay/pull/6110)) - Prefix upload location query params for forward compatibility. ([#6076](https://github.com/getsentry/relay/pull/6076)) - Use upstream descriptor in upload requests. ([#6128](https://github.com/getsentry/relay/pull/6128)) +- Use a hardened static base image for the release container and statically link `libstdc++`. ([#6131](https://github.com/getsentry/relay/pull/6131)) ## 26.6.0 diff --git a/Dockerfile.release b/Dockerfile.release index ebd5dc3adf2..97406654e8d 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,16 +1,16 @@ -FROM gcr.io/distroless/cc-debian13:debug AS builder +FROM us-docker.pkg.dev/sentryio/dhi-mirror/static:20250419-glibc-debian13 AS base -RUN ["/busybox/busybox", "mkdir", "/work", "/etc/relay"] - - -FROM gcr.io/distroless/cc-debian13:nonroot +FROM us-docker.pkg.dev/sentryio/dhi-mirror/static:20250419-glibc-debian13 ARG TARGETPLATFORM EXPOSE 3000 -COPY --from=builder --chown=nonroot:nonroot /etc/relay /etc/relay -COPY --from=builder --chown=nonroot:nonroot /work /work +# The static base image has no shell, so we can't `mkdir` the volume dirs. +# Instead seed them from the image's own pre-existing, empty `/home/nonroot` +# directory, which is already owned by the nonroot user (uid/gid 65532). +COPY --from=base --chown=nonroot:nonroot /home/nonroot /etc/relay +COPY --from=base --chown=nonroot:nonroot /home/nonroot /work VOLUME ["/etc/relay", "/work"] WORKDIR /work diff --git a/relay-crash/build.rs b/relay-crash/build.rs index f5f14eb90dd..867e909e3c3 100644 --- a/relay-crash/build.rs +++ b/relay-crash/build.rs @@ -2,11 +2,10 @@ use std::path::{Path, PathBuf}; use std::process::Command; fn main() { - // sentry-native dependencies - match std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { - "macos" => println!("cargo:rustc-link-lib=dylib=c++"), - "linux" => println!("cargo:rustc-link-lib=dylib=stdc++"), - _ => return, // allow building with --all-features, fail during runtime + // sentry-native is only built on macOS and Linux. + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + if !matches!(target_os.as_str(), "macos" | "linux") { + return; // allow building with --all-features, fail during runtime } if !Path::new("sentry-native/.git").exists() { @@ -39,6 +38,36 @@ fn main() { println!("cargo:rustc-link-lib=static=breakpad_client"); println!("cargo:rustc-link-lib=static=sentry"); + // Link the C++ standard library that breakpad/sentry-native depend on. This must come *after* + // the static libs above so the linker can resolve their symbols (static archive linking is + // order-sensitive). On Linux we link it statically so the binary runs on minimal base images + // that don't ship `libstdc++.so.6` (e.g. the distroless `static` image). + match target_os.as_str() { + "macos" => println!("cargo:rustc-link-lib=dylib=c++"), + "linux" => { + // `static=stdc++` makes rustc resolve `libstdc++.a` itself, but it lives in a + // gcc-internal, arch/version-specific directory that is not on rustc's default link + // search path (only the `cc` driver knows it). Ask the compiler where it is. + let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string()); + let path = Command::new(&cc) + .arg("-print-file-name=libstdc++.a") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()); + // `-print-file-name` echoes the bare filename back when it can't resolve it. + if let Some(dir) = path + .as_deref() + .map(str::trim) + .filter(|p| *p != "libstdc++.a") + .and_then(|p| Path::new(p).parent()) + { + println!("cargo:rustc-link-search=native={}", dir.display()); + } + println!("cargo:rustc-link-lib=static=stdc++"); + } + _ => unreachable!(), + } + let bindings = bindgen::Builder::default() .header("sentry-native/include/sentry.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))