From 74d907290db288dd8ff65034b5987a1f4d9222ac Mon Sep 17 00:00:00 2001 From: jnfrati Date: Wed, 9 Sep 2026 12:15:53 +0200 Subject: [PATCH] [client] Separate image changes from invoking user fix Keep this PR limited to resolving unmapped non-root invoking users. Move container permissions and their smoke test to a dependent image branch so they can be reviewed separately. --- client/Dockerfile-rootless | 20 +-- client/test/rootless-arbitrary-uid-docker.sh | 172 ------------------- 2 files changed, 6 insertions(+), 186 deletions(-) delete mode 100755 client/test/rootless-arbitrary-uid-docker.sh diff --git a/client/Dockerfile-rootless b/client/Dockerfile-rootless index 36ae24009..8141af6ed 100644 --- a/client/Dockerfile-rootless +++ b/client/Dockerfile-rootless @@ -1,28 +1,20 @@ # build & run locally with: # cd "$(git rev-parse --show-toplevel)" # CGO_ENABLED=0 go build -o netbird ./client -# podman build -t localhost/netbird:latest -f client/Dockerfile-rootless --ignorefile .dockerignore-client . -# podman run --rm -it --user 1001230000:0 --cap-drop=ALL --security-opt=no-new-privileges localhost/netbird:latest -# -# Reuse /var/lib/netbird volumes with the same runtime UID. -# Config/profile files start at 0600; Kubernetes fsGroup may widen their modes. -# Where supported, pod securityContext.fsGroupChangePolicy: OnRootMismatch -# preserves modes when the volume root's ownership and permissions match. +# podman build -t localhost/netbird:latest -f client/Dockerfile --ignorefile .dockerignore-client . +# podman run --rm -it --cap-add={BPF,NET_ADMIN,NET_RAW} localhost/netbird:latest FROM alpine:3.24 RUN apk add --no-cache \ bash \ ca-certificates \ - && adduser -D -u 1000 -h /var/lib/netbird netbird \ - && chgrp -R 0 /var/lib/netbird \ - && chmod -R g=u /var/lib/netbird + && adduser -D -h /var/lib/netbird netbird WORKDIR /var/lib/netbird -USER 1000:0 +USER netbird:netbird ENV \ - HOME="/var/lib/netbird" \ NETBIRD_BIN="/usr/local/bin/netbird" \ NB_USE_NETSTACK_MODE="true" \ NB_ENABLE_NETSTACK_LOCAL_FORWARDING="true" \ @@ -37,5 +29,5 @@ ENV \ ENTRYPOINT [ "/usr/local/bin/netbird-entrypoint.sh" ] ARG TARGETPLATFORM ARG NETBIRD_BINARY=$TARGETPLATFORM/netbird -COPY --chown=0:0 --chmod=0755 client/netbird-entrypoint.sh /usr/local/bin/netbird-entrypoint.sh -COPY --chown=0:0 --chmod=0755 "${NETBIRD_BINARY}" /usr/local/bin/netbird +COPY client/netbird-entrypoint.sh /usr/local/bin/netbird-entrypoint.sh +COPY "${NETBIRD_BINARY}" /usr/local/bin/netbird diff --git a/client/test/rootless-arbitrary-uid-docker.sh b/client/test/rootless-arbitrary-uid-docker.sh deleted file mode 100755 index 97555bc05..000000000 --- a/client/test/rootless-arbitrary-uid-docker.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bash -set -eEuo pipefail - -RUNTIME="${CONTAINER_RUNTIME:-}" -if [[ -z "${RUNTIME}" ]]; then - if command -v docker >/dev/null 2>&1; then - RUNTIME=docker - elif command -v podman >/dev/null 2>&1; then - RUNTIME=podman - else - echo "docker or podman is required" >&2 - exit 127 - fi -fi -if ! command -v "${RUNTIME}" >/dev/null 2>&1; then - echo "container runtime not found: ${RUNTIME}" >&2 - exit 127 -fi -if ! command -v go >/dev/null 2>&1; then - echo "go is required" >&2 - exit 127 -fi - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -IMAGE="${IMAGE:-netbird-rootless-arbitrary-uid-test:local}" -TARGETARCH="${TARGETARCH:-$(go env GOARCH)}" -PLATFORM="${PLATFORM:-linux/${TARGETARCH}}" -WAIT_TIMEOUT="${WAIT_TIMEOUT:-30}" -TMP_DIR="$(mktemp -d)" -CONTAINER="netbird-rootless-uid-${RANDOM}-$$" -VOLUME="" - -cleanup() { - local status=$? - "${RUNTIME}" rm -f "${CONTAINER}" >/dev/null 2>&1 || true - if [[ -n "${VOLUME}" ]] && ! "${RUNTIME}" volume rm "${VOLUME}" >/dev/null; then - echo "failed to remove test volume ${VOLUME}" >&2 - status=1 - fi - rm -rf "${TMP_DIR}" - exit "${status}" -} -trap cleanup EXIT - -container_logs() { - echo "---- ${CONTAINER} logs ----" >&2 - "${RUNTIME}" logs "${CONTAINER}" >&2 || true - echo "----------------------------" >&2 -} - -build_image() { - echo "==> Building Linux ${TARGETARCH} netbird binary" - mkdir -p "${TMP_DIR}/context/client" - cp "${ROOT_DIR}/client/Dockerfile-rootless" "${TMP_DIR}/context/Dockerfile" - cp "${ROOT_DIR}/client/netbird-entrypoint.sh" "${TMP_DIR}/context/client/netbird-entrypoint.sh" - - ( - cd "${ROOT_DIR}" - CGO_ENABLED=0 GOOS=linux GOARCH="${TARGETARCH}" \ - go build -o "${TMP_DIR}/context/netbird" ./client - ) - - echo "==> Building ${IMAGE} for ${PLATFORM}" - "${RUNTIME}" build \ - --platform "${PLATFORM}" \ - --build-arg NETBIRD_BINARY=netbird \ - -t "${IMAGE}" \ - -f "${TMP_DIR}/context/Dockerfile" \ - "${TMP_DIR}/context" -} - -start_container() { - echo "==> Starting ${CONTAINER} as unmapped UID 1001230000" - "${RUNTIME}" run -d \ - --name "${CONTAINER}" \ - --user 1001230000:0 \ - --volume "${VOLUME}:/var/lib/netbird" \ - --cap-drop=ALL \ - --security-opt=no-new-privileges \ - --entrypoint /usr/local/bin/netbird \ - "${IMAGE}" \ - --log-file console \ - service run >/dev/null -} - -wait_until_live() { - local deadline=$((SECONDS + WAIT_TIMEOUT)) - - while (( SECONDS < deadline )); do - if "${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird status --check live >/dev/null 2>&1; then - return 0 - fi - - if [[ "$("${RUNTIME}" inspect -f '{{.State.Running}}' "${CONTAINER}" 2>/dev/null || true)" != "true" ]]; then - echo "container exited before the daemon became live" >&2 - container_logs - return 1 - fi - sleep 1 - done - - echo "timed out waiting for the daemon after ${WAIT_TIMEOUT}s" >&2 - container_logs - return 1 -} - -assert_arbitrary_uid_contract() { - echo "==> Verifying arbitrary UID image contract" - "${RUNTIME}" exec "${CONTAINER}" sh -ec ' - test "$(id -u)" = 1001230000 - test "$(id -g)" = 0 - test "${HOME}" = /var/lib/netbird - test -r /usr/local/bin/netbird-entrypoint.sh - test -x /usr/local/bin/netbird-entrypoint.sh - test "$(head -n 1 /usr/local/bin/netbird-entrypoint.sh)" = "#!/usr/bin/env bash" - bash -n /usr/local/bin/netbird-entrypoint.sh - touch /var/lib/netbird/.uid-smoke - rm /var/lib/netbird/.uid-smoke - test -S /var/lib/netbird/netbird.sock - test "$(stat -c %a /var/lib/netbird/config.json)" = 600 - test "$(stat -c %a /var/lib/netbird/active_profile.json)" = 600 - ' - "${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile list >/dev/null -} - -assert_same_uid_restart() { - echo "==> Verifying persistent profiles with the same runtime UID" - local profile_name="rootless-restart" profiles_before profiles_after - - "${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile add "${profile_name}" >/dev/null - profiles_before="$("${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile list --show-id)" - if [[ "${profiles_before}" != *"${profile_name}"* ]]; then - echo "created profile is missing before restart" >&2 - return 1 - fi - - "${RUNTIME}" stop "${CONTAINER}" >/dev/null - "${RUNTIME}" rm "${CONTAINER}" >/dev/null - start_container - wait_until_live - assert_arbitrary_uid_contract - - profiles_after="$("${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile list --show-id)" - if [[ "${profiles_after}" != "${profiles_before}" ]]; then - echo "profiles changed after recreating the container with the same volume and UID" >&2 - container_logs - return 1 - fi - - "${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile rename "${profile_name}" "${profile_name}-renamed" >/dev/null - profiles_after="$("${RUNTIME}" exec "${CONTAINER}" \ - /usr/local/bin/netbird profile list --show-id)" - if [[ "${profiles_after}" != *"${profile_name}-renamed"* ]]; then - echo "persisted profile could not be updated after restart" >&2 - return 1 - fi -} - -build_image -VOLUME="$("${RUNTIME}" volume create "${CONTAINER}-state")" -start_container -wait_until_live -assert_arbitrary_uid_contract -assert_same_uid_restart - -echo "==> Rootless arbitrary UID and same-UID persistence validation passed"