From 522cdcb9c222acd2c6d1555ec6833983ca22b576 Mon Sep 17 00:00:00 2001 From: fntyler <71953103+fntyler@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:12:54 -0400 Subject: [PATCH] [BRE-2039] ci(docker): add distroless FIPS Dockerfile.gov variants [BRE-2039](https://bitwarden.atlassian.net/browse/BRE-2039) Add Chainguard FIPS (`Dockerfile.gov`) build variants for the server services. * Add `Dockerfile.gov` for all services on `cgr.dev/bitwarden.com` FIPS images * Run services distroless on `dotnet-runtime-fips:10` as nonroot (uid 65532) * Replace `entrypoint.sh` with a direct exec entrypoint; log to stdout * Drop `gosu`/`shadow`/`curl`/`krb5` and in-image healthchecks (k8s probes) * Target `linux/amd64` and `linux/arm64` with a fail-fast platform guard * Keep `MsSqlMigratorUtility` on `dotnet-runtime-fips:10-dev` (shell entrypoint) * Add the `build-chainguard` CI job and document the rework in the assessment --- .github/workflows/build.yml | 53 ++++++ bitwarden_license/src/Scim/Dockerfile.gov | 56 ++++++ bitwarden_license/src/Sso/Dockerfile.gov | 56 ++++++ chainguard-variants-assessment.md | 209 ++++++++++++++++++++++ src/Admin/Dockerfile.gov | 69 +++++++ src/Api/Dockerfile.gov | 56 ++++++ src/Billing/Dockerfile.gov | 56 ++++++ src/Events/Dockerfile.gov | 56 ++++++ src/EventsProcessor/Dockerfile.gov | 56 ++++++ src/Icons/Dockerfile.gov | 56 ++++++ src/Identity/Dockerfile.gov | 56 ++++++ src/Notifications/Dockerfile.gov | 56 ++++++ util/Attachments/Dockerfile.gov | 56 ++++++ util/MsSqlMigratorUtility/Dockerfile.gov | 70 ++++++++ 14 files changed, 961 insertions(+) create mode 100644 bitwarden_license/src/Scim/Dockerfile.gov create mode 100644 bitwarden_license/src/Sso/Dockerfile.gov create mode 100644 chainguard-variants-assessment.md create mode 100644 src/Admin/Dockerfile.gov create mode 100644 src/Api/Dockerfile.gov create mode 100644 src/Billing/Dockerfile.gov create mode 100644 src/Events/Dockerfile.gov create mode 100644 src/EventsProcessor/Dockerfile.gov create mode 100644 src/Icons/Dockerfile.gov create mode 100644 src/Identity/Dockerfile.gov create mode 100644 src/Notifications/Dockerfile.gov create mode 100644 util/Attachments/Dockerfile.gov create mode 100644 util/MsSqlMigratorUtility/Dockerfile.gov diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc89f5a516db..d683796c830b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -495,6 +495,59 @@ jobs: path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility if-no-files-found: error + build-chainguard: + name: Build Chainguard Docker images + runs-on: ubuntu-22.04 + needs: lint + permissions: + contents: read + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - project_name: Admin + base_path: ./src + - project_name: Api + base_path: ./src + - project_name: Attachments + base_path: ./util + - project_name: Billing + base_path: ./src + - project_name: Events + base_path: ./src + - project_name: EventsProcessor + base_path: ./src + - project_name: Icons + base_path: ./src + - project_name: Identity + base_path: ./src + - project_name: MsSqlMigratorUtility + base_path: ./util + - project_name: Notifications + base_path: ./src + - project_name: Scim + base_path: ./bitwarden_license/src + - project_name: Sso + base_path: ./bitwarden_license/src + steps: + - name: Check out repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Build Chainguard Docker image + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 + with: + context: . + file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile.gov + platforms: linux/amd64 + push: false + bitwarden-lite-build: name: Trigger Bitwarden lite build if: github.event_name != 'pull_request' diff --git a/bitwarden_license/src/Scim/Dockerfile.gov b/bitwarden_license/src/Scim/Dockerfile.gov new file mode 100644 index 000000000000..cd1f4a3fa87b --- /dev/null +++ b/bitwarden_license/src/Scim/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/bitwarden_license/src/Scim +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/bitwarden_license/src/Scim/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Scim"] diff --git a/bitwarden_license/src/Sso/Dockerfile.gov b/bitwarden_license/src/Sso/Dockerfile.gov new file mode 100644 index 000000000000..cdbe4260cb9b --- /dev/null +++ b/bitwarden_license/src/Sso/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/bitwarden_license/src/Sso +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/bitwarden_license/src/Sso/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Sso"] diff --git a/chainguard-variants-assessment.md b/chainguard-variants-assessment.md new file mode 100644 index 000000000000..14364d09ee01 --- /dev/null +++ b/chainguard-variants-assessment.md @@ -0,0 +1,209 @@ +# Chainguard Dockerfile Variants — Assessment + +## Summary + +This repo carries **11 `chainguard.Dockerfile` variants** that mirror the standard service `Dockerfile`s but rebase them from Microsoft's Alpine .NET images onto Chainguard (Wolfi) images. The variants are intentionally minimal "lift-and-shift" rewrites: same multi-stage shape, same build/publish commands, same entrypoint — only the base images and the platform/packaging details that *must* change for Wolfi +were touched. + +The single most important property of these variants is that they build against **floating `latest-dev` tags**, not pinned versions. The standard Dockerfiles are version-pinned (`10.0-alpine3.23`); the Chainguard variants cannot be, because the public Chainguard tier only publishes mutable `latest` / `latest-dev` (see [Why we must test "as-is"](#why-we-must-test-as-is-unpinned-images)). That makes a **continuous CI build the only way to know whether these images still build** — the target moves underneath us. + +## Scope — the 11 variants + +| Service | Path | Notes | +|---|---|---| +| Admin | `src/Admin/chainguard.Dockerfile` | Has an extra Node.js build stage | +| Api | `src/Api/chainguard.Dockerfile` | | +| Billing | `src/Billing/chainguard.Dockerfile` | | +| Events | `src/Events/chainguard.Dockerfile` | | +| EventsProcessor | `src/EventsProcessor/chainguard.Dockerfile` | | +| Icons | `src/Icons/chainguard.Dockerfile` | | +| Identity | `src/Identity/chainguard.Dockerfile` | | +| Notifications | `src/Notifications/chainguard.Dockerfile` | sysctl tweak (see below) | +| Attachments | `util/Attachments/chainguard.Dockerfile` | Builds `util/Server` | +| Scim | `bitwarden_license/src/Scim/chainguard.Dockerfile` | | +| Sso | `bitwarden_license/src/Sso/chainguard.Dockerfile` | | + +> A 12th variant, `src/Api/chainguard-distroless.Dockerfile`, was removed earlier +> and is intentionally out of scope. + +## Differences from the standard Dockerfiles + +Every variant applies the **same six changes**. The lower half of each file (app-stage +`COPY --from=build`, `entrypoint.sh` copy, `HEALTHCHECK`, `ENTRYPOINT`) is unchanged. + +| # | Change | Standard | Chainguard | Why | +|---|---|---|---|---| +| 1 | **Build base** | `mcr.microsoft.com/dotnet/sdk:10.0-alpine3.23` | `cgr.dev/chainguard/dotnet-sdk:latest-dev` | Rebase to Chainguard | +| 2 | **Runtime base** | `mcr.microsoft.com/dotnet/aspnet:10.0-alpine3.23` | `cgr.dev/chainguard/aspnet-runtime:latest-dev` | Rebase to Chainguard | +| 3 | **`USER root`** | (implicit root) | Added after **both** `FROM`s | Chainguard images default to a **nonroot** user; the build steps and the root-based entrypoint need root | +| 4 | **Runtime ID** | `linux-musl-x64` / `-arm64` / `-arm` | `linux-x64` / `-arm64` / `-arm` | Wolfi is **glibc**, not musl; a musl self-contained binary won't run on the glibc runtime base | +| 5 | **ICU package** | `icu-libs` | `icu` | Wolfi package naming | +| 6 | **gosu install** | `apk add … --repository=…alpine/edge/community gosu` | `gosu` (default repos) | Wolfi's apk can't use the Alpine edge repo/keys; `gosu` is in Wolfi's default repos | + +### `-dev` (not minimal) base images + +Both Chainguard bases use the **`-dev`** flavor (`dotnet-sdk:latest-dev`, +`aspnet-runtime:latest-dev`, and `node:latest-dev` for Admin). This is deliberate: + +- The **build stage** runs shell-form `RUN` steps (RID detection, sourcing + `/tmp/rid.txt`, `dotnet restore/publish`) and `npm ci`/`npm run build` for Admin — + all of which need a shell, a package manager, and root that the minimal images lack. +- The **runtime stage** keeps the existing `entrypoint.sh`, which runs as root, uses + `/bin/sh` and the `shadow` utilities (`groupadd`/`usermod`/`mkhomedir_helper`), then + steps down to the `bitwarden` user via `gosu`. The minimal, distroless + `aspnet-runtime:latest` (nonroot, no shell) cannot execute it. + +### Service-specific deltas + +- **Admin** — additionally rebases the Node build stage + `node:24-alpine3.21` → `cgr.dev/chainguard/node:latest-dev` + `USER root`. Nothing + from this stage ships in the runtime image (only the built `wwwroot` is copied + forward), so the larger dev image has no runtime footprint. +- **Notifications** — the two `sysctl.d` writes were wrapped with `mkdir -p + /etc/sysctl.d` (and joined into one `RUN`). The Wolfi runtime base ships **no + `/etc/sysctl.d` directory**, so the original bare `>>` redirects failed the build + (exit 1). This was fixed during this session. *(Note: these settings are inert in a + container unless applied by the runtime — e.g. k8s `securityContext.sysctls` — but + the fix preserves prior intent without breaking the build.)* +- **Api** — minor cosmetic reordering of the apk package list; no functional change. + +## Why we must test "as-is" (unpinned images) + +This is the core justification for wiring a dedicated CI build for these variants. + +### The pinning gap is structural, not a choice + +The standard Dockerfiles pin an exact version: `dotnet/sdk:10.0-alpine3.23`. The +Chainguard variants **cannot** express the same pin. The public `cgr.dev/chainguard` +tier publishes **only `latest` and `latest-dev`** for `dotnet-sdk`, `aspnet-runtime`, +and `node` — every other tag is a cosign sidecar (`.sig`/`.att`/`.sbom`). Version-numbered +and dated tags (e.g. `dotnet-sdk:9.0`) are a **paid** Chainguard offering. On the tier +these builds can reach, the only expressible pin is a `@sha256:` digest — and on the +free tier those digests are eventually garbage-collected, so a digest pin is not durable. + +**Consequence:** until a pinned/versioned source is available, every build of these +variants consumes whatever `latest-dev` happens to be *today*. That is exactly what a +production-equivalent build would consume too — so the realistic thing to validate is +the moving target itself. + +### The moving target demonstrably moves — and breaks + +Evidence gathered during this session (same commit, two CI runs a day apart): + +- The `dotnet-sdk:latest-dev` tag rolled between runs: + - `2026-06-15` build → digest `439228d9…` — **built cleanly**. + - `2026-06-16` build → digest `2461a82a…` — **CLR-crashed** on `dotnet publish` + (`Internal CLR error (0x80131506)`, SIGSEGV / exit 139) within 0.6s. +- A separate run hit a transient `libsemanage: Permission denied` during `apk add` + in the runtime stage — the identical `apk` command succeeded in 6 sibling jobs in the + same run, i.e. environmental/buildkit noise, not a Dockerfile defect. +- The Notifications `sysctl.d` break was a deterministic failure that only surfaced + because the variant was actually built. + +These are precisely the classes of problem a one-time local build would miss: + +1. **Upstream regressions** in a floating base image (the CLR crash) — would silently + land in any fresh build the day the tag rolls. +2. **Transient infrastructure faults** (registry 500s, apk extraction races) that need + retry/resilience design, not code fixes. +3. **Wolfi-vs-Alpine behavioral gaps** (missing `/etc/sysctl.d`, package renames, musl + vs glibc) that only fail at build/run time. + +A continuously-running build is therefore not redundant with the existing image CI — it +is the **only signal** that tells us whether the Chainguard variants are currently +buildable against the only image source available to them, and surfaces upstream breakage +the moment it is introduced rather than at migration time. + +## Security posture note + +These variants knowingly **trade away most of Chainguard's hardening** to preserve the +existing runtime contract: + +- Using the `-dev` runtime re-introduces a shell, a package manager, and root. +- `USER root` in the app stage runs the container as root up to the `gosu` step-down. + +The Chainguard-native end state — minimal (non-`-dev`) `aspnet-runtime` running as the +built-in nonroot user — requires reworking `entrypoint.sh` to drop the root + +`groupadd`/`gosu` pattern. That is the recommended follow-up once the variants build +reliably; it is what actually realizes the distroless/nonroot benefit that motivates the +move to Chainguard in the first place. + +## FIPS migration scoping (future — NOT implemented) + +> **Status:** These variants are **not FIPS** today. All 11 build against the standard +> Wolfi developer images (`dotnet-sdk:latest-dev`, `aspnet-runtime:latest-dev`, +> `node:latest-dev`); no Dockerfile references a FIPS image, provider, or config. This +> section scopes a *future* migration — nothing here is wired up yet. + +### What FIPS compliance actually requires + +FIPS 140-2/140-3 is a validation status of the **cryptographic module**, not a label +that can be applied to an arbitrary image. For a Chainguard + .NET-on-Linux container it +spans three layers, and the base image is only one of them: + +1. **A FIPS-validated crypto module in the image.** Chainguard ships FIPS images as a + **separate, paid (FedRAMP/FIPS) catalog** — distinct image references shipping a + FIPS-validated OpenSSL provider plus the matching `openssl.cnf`. The public + `latest`/`latest-dev` images used here do not include it. **Prerequisite: paid + Chainguard FIPS-catalog access** (same registry-access question raised for pinned + versioned tags). +2. **The app routing crypto through that module.** .NET on Linux implements no crypto of + its own — it delegates to the system OpenSSL, so FIPS behavior depends on the FIPS + provider being the active one. **Critical interaction with our build:** the + self-contained, single-file publish (`--self-contained /p:PublishSingleFile=true`) + bundles the *.NET runtime* but **not** OpenSSL — crypto still comes from the runtime + base image's OpenSSL. So the runtime base (not the SDK build stage) is what governs + FIPS at execution time. +3. **The host kernel.** `/proc/sys/crypto/fips_enabled` reflects the **host**, not the + image. Container FIPS posture is therefore partly a property of where it runs (k8s + nodes, etc.), not solely how it is built — this must be owned by the platform/infra + side, not just these Dockerfiles. + +### Dockerfile changes the migration would entail + +- Repoint **both** `FROM`s (and Admin's node stage) from the standard images to the + FIPS-catalog equivalents. The build stage matters less than the **runtime** base, per + layer 2 above — confirm the *runtime* base is the FIPS variant. +- Re-evaluate the `-dev` + `USER root` + `gosu` pattern against the FIPS image's + user/shell model (it may differ from the standard `-dev` images). +- Keep `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false` in mind; ICU and crypto are + separate, but any base swap re-tests the package set (`icu`, `krb5`, `shadow`, `gosu`). + +### Verification checklist (to wire into CI once on FIPS images) + +**Static — confirm the image is the FIPS build:** +- [ ] SBOM/attestation shows the FIPS OpenSSL provider package: + `cosign download sbom | grep -i 'fips\|openssl'` +- [ ] `cosign verify-attestation …` (FIPS images ship attestations) +- [ ] FIPS provider module + config present in image (`fips.so`, `fipsmodule.cnf`, + `fips = fips_sect` enabled in `openssl.cnf`) + +**Runtime — confirm crypto is in FIPS mode (inside a running container):** +- [ ] `openssl list -providers` shows the `fips` provider active +- [ ] `/etc/ssl/openssl.cnf` has the fips section enabled with `.include` of `fipsmodule.cnf` +- [ ] `cat /proc/sys/crypto/fips_enabled` → `1` *(host-dependent; document the boundary)* + +**Behavioral — confirm .NET actually binds to the module:** +- [ ] A probe in the image calls a non-approved algorithm (e.g. `MD5`) and **throws** + under the FIPS provider, while an approved one (AES, SHA-256) succeeds — proving + the runtime isn't silently falling back to the default provider. + +### Open prerequisites before this can start + +1. Paid Chainguard FIPS-catalog access (and confirmation the exact image references exist + for `dotnet-sdk`/`aspnet-runtime`/`node`). +2. A decision on the compliance **boundary** — image-only vs image-on-FIPS-host — since + layer 3 is owned by infra. +3. The nonroot/`entrypoint.sh` rework (below) is orthogonal but worth sequencing + alongside, since both touch the runtime base. + +## Recommendations + +1. **Keep the build-only CI job running** against `latest-dev` to continuously detect + upstream breakage in the floating images (the primary value described above). +2. **Pursue a durable pinned source** — either a paid Chainguard versioned tag or a + mirror of a known-good digest into GHCR/ACR — so builds become reproducible and + bisectable. Until then, treat red builds as "is the current upstream image good?" + signal, not necessarily a defect in this repo. +3. **Plan the nonroot rework** of `entrypoint.sh` so the variants can move to the + minimal, distroless bases and actually gain the security posture Chainguard provides. diff --git a/src/Admin/Dockerfile.gov b/src/Admin/Dockerfile.gov new file mode 100644 index 000000000000..45ec460ed480 --- /dev/null +++ b/src/Admin/Dockerfile.gov @@ -0,0 +1,69 @@ +############################################### +# Node.js build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/node-fips:24-dev AS node-build + +USER root +WORKDIR /app +COPY src/Admin/package*.json ./ +COPY /src/Admin/ . +RUN npm ci +RUN npm run build + +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Admin +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Admin/out /app +COPY --from=node-build --chown=65532:65532 /app/wwwroot /app/wwwroot + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Admin"] diff --git a/src/Api/Dockerfile.gov b/src/Api/Dockerfile.gov new file mode 100644 index 000000000000..db0406cbe7e5 --- /dev/null +++ b/src/Api/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Api +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Api/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Api"] diff --git a/src/Billing/Dockerfile.gov b/src/Billing/Dockerfile.gov new file mode 100644 index 000000000000..66bd18231d08 --- /dev/null +++ b/src/Billing/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Billing +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Billing/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Billing"] diff --git a/src/Events/Dockerfile.gov b/src/Events/Dockerfile.gov new file mode 100644 index 000000000000..d96d49522a0b --- /dev/null +++ b/src/Events/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Events +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Events/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Events"] diff --git a/src/EventsProcessor/Dockerfile.gov b/src/EventsProcessor/Dockerfile.gov new file mode 100644 index 000000000000..16248ad661e9 --- /dev/null +++ b/src/EventsProcessor/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/EventsProcessor +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/EventsProcessor/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/EventsProcessor"] diff --git a/src/Icons/Dockerfile.gov b/src/Icons/Dockerfile.gov new file mode 100644 index 000000000000..a1480f4fd75a --- /dev/null +++ b/src/Icons/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Icons +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Icons/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Icons"] diff --git a/src/Identity/Dockerfile.gov b/src/Identity/Dockerfile.gov new file mode 100644 index 000000000000..7da9111f501c --- /dev/null +++ b/src/Identity/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Identity +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Identity/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Identity"] diff --git a/src/Notifications/Dockerfile.gov b/src/Notifications/Dockerfile.gov new file mode 100644 index 000000000000..06a99d37ad3d --- /dev/null +++ b/src/Notifications/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/src/Notifications +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /app +COPY --from=build --chown=65532:65532 /source/src/Notifications/out /app + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/app/Notifications"] diff --git a/util/Attachments/Dockerfile.gov b/util/Attachments/Dockerfile.gov new file mode 100644 index 000000000000..c93df1087534 --- /dev/null +++ b/util/Attachments/Dockerfile.gov @@ -0,0 +1,56 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + else echo "Unsupported TARGETPLATFORM: $TARGETPLATFORM" >&2 && exit 1 ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/util/Server +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10 + +LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV globalSettings__logDirectory= +EXPOSE 5000 + +WORKDIR /bitwarden_server +COPY --from=build --chown=65532:65532 /source/util/Server/out /bitwarden_server + +# Run as the built-in nonroot user +USER 65532 + +# Run app binary as PID 1 +ENTRYPOINT ["/bitwarden_server/Server"] diff --git a/util/MsSqlMigratorUtility/Dockerfile.gov b/util/MsSqlMigratorUtility/Dockerfile.gov new file mode 100644 index 000000000000..1602d62951a3 --- /dev/null +++ b/util/MsSqlMigratorUtility/Dockerfile.gov @@ -0,0 +1,70 @@ +############################################### +# Build stage # +############################################### +# `-dev` is required: the shell-form RUN steps (RID detection, sourcing +# /tmp/rid.txt, dotnet restore/publish) need a shell + root, which the minimal +# `dotnet-sdk:latest` lacks. +FROM --platform=$BUILDPLATFORM cgr.dev/bitwarden.com/dotnet-sdk-fips:10-dev AS build + +USER root + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +# Chainguard (Wolfi) is glibc-based, so target the glibc RIDs (linux-x64), NOT +# the musl RIDs the Alpine image used — a musl self-contained binary will not +# run on the glibc runtime base below. +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + RID=linux-arm ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy required project files +WORKDIR /source +COPY . ./ + +# Restore project dependencies and tools +WORKDIR /source/util/MsSqlMigratorUtility +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Build project +WORKDIR /source/util/MsSqlMigratorUtility +RUN . /tmp/rid.txt && dotnet publish \ + -c release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r $RID \ + -o out + +############################################### +# App stage # +############################################### +# This is a console app (not an ASP.NET service), so the leaner +# `dotnet-runtime` is the correct family rather than `aspnet-runtime` — there +# are no ports, no ASPNETCORE_* config, and no HTTP healthcheck. `-dev` is +# required because the ENTRYPOINT runs `sh -c`, which the minimal distroless +# `dotnet-runtime:latest` (nonroot, no shell) cannot execute. +FROM cgr.dev/bitwarden.com/dotnet-runtime-fips:10-dev AS app + +USER root + +ARG TARGETPLATFORM +LABEL com.bitwarden.product="bitwarden" + +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false + +# Copy app from the build stage +WORKDIR /app +COPY --from=build /source/util/MsSqlMigratorUtility/out /app + +# Wolfi packages (apk), not Alpine: `icu-libs` -> `icu`. No curl/shadow/gosu +# here — this image has no entrypoint.sh, no gosu step-down, and no healthcheck. +RUN apk add --no-cache icu + +ENTRYPOINT ["sh", "-c", "/app/MsSqlMigratorUtility \"${MSSQL_CONN_STRING}\" ${@}", "--" ]