diff --git a/util/RustSdk/RustSdk.csproj b/util/RustSdk/RustSdk.csproj index e9b22781606e..ea943f000ff7 100644 --- a/util/RustSdk/RustSdk.csproj +++ b/util/RustSdk/RustSdk.csproj @@ -27,6 +27,11 @@ true runtimes/linux-x64/native/libsdk.so + + Always + true + runtimes/linux-arm64/native/libsdk.so + Always true @@ -50,6 +55,11 @@ true runtimes/linux-x64/native/libsdk.so + + Always + true + runtimes/linux-arm64/native/libsdk.so + Always true diff --git a/util/SeederUtility/Configuration/ServiceCollectionExtension.cs b/util/SeederUtility/Configuration/ServiceCollectionExtension.cs index 57adda2b2e4e..059739856881 100644 --- a/util/SeederUtility/Configuration/ServiceCollectionExtension.cs +++ b/util/SeederUtility/Configuration/ServiceCollectionExtension.cs @@ -33,7 +33,15 @@ public static void ConfigureServices(ServiceCollection services, bool enableMang services.AddSingleton, PasswordHasher>(); services.TryAddSingleton(); - services.AddDataProtection().SetApplicationName("Bitwarden"); + var dpBuilder = services.AddDataProtection().SetApplicationName("Bitwarden"); + // Persist DataProtection keys to a shared directory when configured, so + // records this tool encrypts are decryptable by the running app. Needed + // when seeding an instance whose components share a DataProtection key ring + // (e.g. self-host). No-op when the directory isn't set. + if (!string.IsNullOrWhiteSpace(globalSettings.DataProtection.Directory)) + { + dpBuilder.PersistKeysToFileSystem(new DirectoryInfo(globalSettings.DataProtection.Directory)); + } services.AddDatabaseRepositories(globalSettings); diff --git a/util/SeederUtility/Dockerfile b/util/SeederUtility/Dockerfile new file mode 100644 index 000000000000..f77cd9e3ad97 --- /dev/null +++ b/util/SeederUtility/Dockerfile @@ -0,0 +1,115 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build + +# Docker buildx supplies these values +ARG TARGETPLATFORM +ARG BUILDPLATFORM + +# Install base build dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Rust toolchain: RustSdk builds a native library via cargo during publish. +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + --default-toolchain stable \ + --profile minimal \ + --no-modify-path + +ENV PATH="/root/.cargo/bin:${PATH}" + +# Determine target architecture and install cross-compilation tools +RUN case "$TARGETPLATFORM" in \ + "linux/amd64") \ + RUST_TARGET=x86_64-unknown-linux-gnu && \ + RID=linux-x64 && \ + ARCH_PACKAGES="" \ + ;; \ + "linux/arm64") \ + RUST_TARGET=aarch64-unknown-linux-gnu && \ + RID=linux-arm64 && \ + ARCH_PACKAGES="gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross" \ + ;; \ + *) \ + echo "Unsupported platform: $TARGETPLATFORM" && exit 1 \ + ;; \ + esac \ + && if [ -n "$ARCH_PACKAGES" ]; then \ + apt-get update && apt-get install -y $ARCH_PACKAGES && rm -rf /var/lib/apt/lists/* ; \ + fi \ + && echo "RUST_TARGET=${RUST_TARGET}" >> /etc/build-env \ + && echo "RID=${RID}" >> /etc/build-env \ + && . /etc/build-env \ + && rustup target add ${RUST_TARGET} \ + && echo "Rust target: ${RUST_TARGET}, .NET RID: ${RID}" + +# Configure Rust for cross-compilation with proper linkers +RUN . /etc/build-env \ + && mkdir -p /root/.cargo \ + && case "$TARGETPLATFORM" in \ + "linux/amd64") \ + echo "[target.x86_64-unknown-linux-gnu]" >> /root/.cargo/config.toml \ + && echo "linker = \"gcc\"" >> /root/.cargo/config.toml \ + ;; \ + "linux/arm64") \ + echo "[target.aarch64-unknown-linux-gnu]" >> /root/.cargo/config.toml \ + && echo "linker = \"aarch64-linux-gnu-gcc\"" >> /root/.cargo/config.toml \ + ;; \ + esac + +# Copy project files +WORKDIR /source +COPY . ./ + +# Restore .NET dependencies +WORKDIR /source/util/SeederUtility +RUN . /etc/build-env && dotnet restore -r ${RID} + +# Publish a self-contained, single-file binary +WORKDIR /source/util/SeederUtility +RUN . /etc/build-env \ + && export NoWarn="CA1305;CS1591" \ + && rustc --version \ + && cargo --version \ + && echo "Building for Rust target: ${RUST_TARGET}, .NET RID: ${RID}" \ + && dotnet publish SeederUtility.csproj \ + -c Release \ + --no-restore \ + --self-contained \ + /p:PublishSingleFile=true \ + -r ${RID} \ + -o /app/out + +############################################### +# App stage # +############################################### +FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble AS app + +ARG TARGETPLATFORM +LABEL com.bitwarden.product="bitwarden" + +ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_ENVIRONMENT=Production +# The single-file binary extracts native libs at startup; it runs non-root, so +# point extraction at a writable dir. +ENV DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp/.net + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gosu \ + && rm -rf /var/lib/apt/lists/* + +# Copy app from the build stage +WORKDIR /app +COPY --from=build /app/out /app +COPY ./util/SeederUtility/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# CLI entrypoint; args pass through, e.g. `docker run preset --name `. +# DB connection comes from globalSettings__* env vars. +ENTRYPOINT ["/entrypoint.sh"] diff --git a/util/SeederUtility/entrypoint.sh b/util/SeederUtility/entrypoint.sh new file mode 100755 index 000000000000..d97b27220ea9 --- /dev/null +++ b/util/SeederUtility/entrypoint.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +GROUPNAME="bitwarden" +USERNAME="bitwarden" + +LUID=${LOCAL_UID:-0} +LGID=${LOCAL_GID:-0} + +# Step down from host root to well-known nobody/nogroup user +if [ $LUID -eq 0 ] +then + LUID=65534 +fi +if [ $LGID -eq 0 ] +then + LGID=65534 +fi + +if [ "$(id -u)" = "0" ] +then + groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || + groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 + useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || + usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 + + chown -R $USERNAME:$GROUPNAME /app + mkdir -p /etc/bitwarden/core + mkdir -p /etc/bitwarden/logs + mkdir -p /etc/bitwarden/ca-certificates + chown -R $USERNAME:$GROUPNAME /etc/bitwarden + + gosu_cmd="gosu $USERNAME:$GROUPNAME" +else + gosu_cmd="" +fi + +# Pass through any CLI subcommand and arguments (e.g. `preset --name ...`). +exec $gosu_cmd /app/SeederUtility "$@"