From 6b0fcb11806dd584617944b3fed89f7748d0d7ec Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Sun, 5 Jul 2026 10:31:28 +0800 Subject: [PATCH] fix(docker): require explicit root credentials (#4278) --- .config/make/tests.mak | 1 + Dockerfile.glibc | 2 - entrypoint.sh | 150 ++++++++++++++++++++++++- scripts/test_entrypoint_credentials.sh | 77 +++++++++++++ 4 files changed, 223 insertions(+), 7 deletions(-) create mode 100755 scripts/test_entrypoint_credentials.sh diff --git a/.config/make/tests.mak b/.config/make/tests.mak index a2ed84dd2..13bfbe5f4 100644 --- a/.config/make/tests.mak +++ b/.config/make/tests.mak @@ -6,6 +6,7 @@ TEST_THREADS ?= 1 script-tests: ## Run shell script tests @echo "Running script tests..." ./scripts/test_build_rustfs_options.sh + ./scripts/test_entrypoint_credentials.sh .PHONY: test test: core-deps test-deps script-tests ## Run all tests diff --git a/Dockerfile.glibc b/Dockerfile.glibc index 4ca9e40e7..20005d983 100644 --- a/Dockerfile.glibc +++ b/Dockerfile.glibc @@ -105,8 +105,6 @@ RUN groupadd -g 10001 rustfs && \ ENV RUSTFS_ADDRESS=":9000" \ RUSTFS_CONSOLE_ADDRESS=":9001" \ - RUSTFS_ACCESS_KEY="rustfsadmin" \ - RUSTFS_SECRET_KEY="rustfsadmin" \ RUSTFS_CONSOLE_ENABLE="true" \ RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ RUSTFS_VOLUMES="/data" \ diff --git a/entrypoint.sh b/entrypoint.sh index 49364facb..3ce6c74e6 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -21,6 +21,151 @@ else set -- /usr/bin/rustfs "$@" fi +DEFAULT_ROOT_CREDENTIAL="rustfsadmin" + +resolve_credential_source() { + CREDENTIAL_ENV_NAME="$1" + CREDENTIAL_FILE_ENV_NAME="$2" + CREDENTIAL_VALUE_OPTION="$3" + CREDENTIAL_FILE_OPTION="$4" + shift 4 + + CREDENTIAL_DIRECT_SET=false + CREDENTIAL_DIRECT_VALUE="" + CREDENTIAL_FILE_SET=false + CREDENTIAL_FILE_VALUE="" + + eval "CREDENTIAL_ENV_VALUE=\${$CREDENTIAL_ENV_NAME:-}" + eval "CREDENTIAL_ENV_FILE_VALUE=\${$CREDENTIAL_FILE_ENV_NAME:-}" + + if [ -n "$CREDENTIAL_ENV_VALUE" ]; then + CREDENTIAL_DIRECT_SET=true + CREDENTIAL_DIRECT_VALUE="$CREDENTIAL_ENV_VALUE" + fi + + if [ -n "$CREDENTIAL_ENV_FILE_VALUE" ]; then + CREDENTIAL_FILE_SET=true + CREDENTIAL_FILE_VALUE="$CREDENTIAL_ENV_FILE_VALUE" + fi + + while [ "$#" -gt 0 ]; do + arg="$1" + case "$arg" in + --) + break + ;; + --"$CREDENTIAL_VALUE_OPTION"=*) + CREDENTIAL_DIRECT_SET=true + CREDENTIAL_DIRECT_VALUE="${arg#*=}" + ;; + --"$CREDENTIAL_VALUE_OPTION") + shift + if [ "$#" -eq 0 ]; then + echo "error:--$CREDENTIAL_VALUE_OPTION requires a value." + return + fi + CREDENTIAL_DIRECT_SET=true + CREDENTIAL_DIRECT_VALUE="$1" + ;; + --"$CREDENTIAL_FILE_OPTION"=*) + CREDENTIAL_FILE_SET=true + CREDENTIAL_FILE_VALUE="${arg#*=}" + ;; + --"$CREDENTIAL_FILE_OPTION") + shift + if [ "$#" -eq 0 ]; then + echo "error:--$CREDENTIAL_FILE_OPTION requires a value." + return + fi + CREDENTIAL_FILE_SET=true + CREDENTIAL_FILE_VALUE="$1" + ;; + esac + + shift + done + + if [ "$CREDENTIAL_DIRECT_SET" = "true" ] && [ "$CREDENTIAL_FILE_SET" = "true" ]; then + echo "conflict" + return + fi + + if [ "$CREDENTIAL_DIRECT_SET" = "true" ]; then + echo "value:$CREDENTIAL_DIRECT_VALUE" + return + fi + + if [ "$CREDENTIAL_FILE_SET" = "true" ]; then + echo "file:$CREDENTIAL_FILE_VALUE" + return + fi + + echo "missing" +} + +validate_credential_source() { + CREDENTIAL_NAME="$1" + FILE_NAME="$2" + CREDENTIAL_SOURCE="$3" + + case "$CREDENTIAL_SOURCE" in + error:*) + echo "ERROR: ${CREDENTIAL_SOURCE#error:}" >&2 + exit 1 + ;; + conflict) + echo "ERROR: Set either $CREDENTIAL_NAME or $FILE_NAME, not both." >&2 + exit 1 + ;; + missing) + echo "ERROR: $CREDENTIAL_NAME or $FILE_NAME must be set explicitly for container startup." >&2 + exit 1 + ;; + value:*) + CREDENTIAL_VALUE="${CREDENTIAL_SOURCE#value:}" + if [ -z "$CREDENTIAL_VALUE" ]; then + echo "ERROR: $CREDENTIAL_NAME must not be empty." >&2 + exit 1 + fi + if [ "$CREDENTIAL_VALUE" = "$DEFAULT_ROOT_CREDENTIAL" ]; then + echo "ERROR: $CREDENTIAL_NAME must not use the default $DEFAULT_ROOT_CREDENTIAL credential." >&2 + exit 1 + fi + ;; + file:*) + CREDENTIAL_FILE="${CREDENTIAL_SOURCE#file:}" + if [ -z "$CREDENTIAL_FILE" ]; then + echo "ERROR: $FILE_NAME must not be empty." >&2 + exit 1 + fi + if [ ! -r "$CREDENTIAL_FILE" ]; then + echo "ERROR: $FILE_NAME points to an unreadable file." >&2 + exit 1 + fi + if IFS= read -r CREDENTIAL_FILE_CONTENT < "$CREDENTIAL_FILE"; then + : + else + CREDENTIAL_FILE_CONTENT="" + fi + if [ -z "$CREDENTIAL_FILE_CONTENT" ]; then + echo "ERROR: $FILE_NAME must not be empty." >&2 + exit 1 + fi + if [ "$CREDENTIAL_FILE_CONTENT" = "$DEFAULT_ROOT_CREDENTIAL" ]; then + echo "ERROR: $FILE_NAME must not contain the default $DEFAULT_ROOT_CREDENTIAL credential." >&2 + exit 1 + fi + ;; + esac +} + +if [ "$1" = "/usr/bin/rustfs" ]; then + ACCESS_SOURCE=$(resolve_credential_source "RUSTFS_ACCESS_KEY" "RUSTFS_ACCESS_KEY_FILE" "access-key" "access-key-file" "$@") + SECRET_SOURCE=$(resolve_credential_source "RUSTFS_SECRET_KEY" "RUSTFS_SECRET_KEY_FILE" "secret-key" "secret-key-file" "$@") + validate_credential_source "RUSTFS_ACCESS_KEY" "RUSTFS_ACCESS_KEY_FILE" "$ACCESS_SOURCE" + validate_credential_source "RUSTFS_SECRET_KEY" "RUSTFS_SECRET_KEY_FILE" "$SECRET_SOURCE" +fi + # 2) Process data volumes (separate from log directory) DATA_VOLUMES="" process_data_volumes() { @@ -112,11 +257,6 @@ process_log_directory() { process_data_volumes process_log_directory -# 4) Default credentials warning -if [ "${RUSTFS_ACCESS_KEY:-}" = "rustfsadmin" ] || [ "${RUSTFS_SECRET_KEY:-}" = "rustfsadmin" ]; then - echo "!!!WARNING: Using default RUSTFS_ACCESS_KEY or RUSTFS_SECRET_KEY. Override them in production!" -fi - # 5) Append DATA_VOLUMES only if no data paths in arguments # Check if any argument looks like a data path (starts with / and not an option) HAS_DATA_PATH=false diff --git a/scripts/test_entrypoint_credentials.sh b/scripts/test_entrypoint_credentials.sh new file mode 100755 index 000000000..b33399de5 --- /dev/null +++ b/scripts/test_entrypoint_credentials.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +ENTRYPOINT="$ROOT_DIR/entrypoint.sh" +DOCKERFILE_GLIBC="$ROOT_DIR/Dockerfile.glibc" + +if grep -Eq 'RUSTFS_(ACCESS|SECRET)_KEY=' "$DOCKERFILE_GLIBC"; then + echo "Dockerfile.glibc must not bake root credentials into image ENV" >&2 + exit 1 +fi + +run_expect_failure() { + label="$1" + expected="$2" + shift 2 + + log_file="$TMP_DIR/$label.log" + if env -i PATH="$PATH" RUSTFS_VOLUMES="$TMP_DIR/data" RUSTFS_OBS_LOG_DIRECTORY= "$@" sh "$ENTRYPOINT" rustfs >"$log_file" 2>&1; then + echo "Expected $label to fail" >&2 + exit 1 + fi + + if ! grep -q "$expected" "$log_file"; then + echo "Expected $label output to contain: $expected" >&2 + cat "$log_file" >&2 + exit 1 + fi +} + +missing_log="$TMP_DIR/missing.log" +if env -i PATH="$PATH" RUSTFS_VOLUMES="$TMP_DIR/data" RUSTFS_OBS_LOG_DIRECTORY= sh "$ENTRYPOINT" >"$missing_log" 2>&1; then + echo "Expected missing credentials to fail" >&2 + exit 1 +fi +grep -q "RUSTFS_ACCESS_KEY or RUSTFS_ACCESS_KEY_FILE must be set explicitly" "$missing_log" + +run_expect_failure \ + "missing-secret" \ + "RUSTFS_SECRET_KEY or RUSTFS_SECRET_KEY_FILE must be set explicitly" \ + RUSTFS_ACCESS_KEY=custom-access + +run_expect_failure \ + "default-access-env" \ + "RUSTFS_ACCESS_KEY must not use the default rustfsadmin credential" \ + RUSTFS_ACCESS_KEY=rustfsadmin \ + RUSTFS_SECRET_KEY=custom-secret + +default_access_cli_log="$TMP_DIR/default-access-cli.log" +if env -i PATH="$PATH" RUSTFS_VOLUMES="$TMP_DIR/data" RUSTFS_OBS_LOG_DIRECTORY= RUSTFS_SECRET_KEY=custom-secret sh "$ENTRYPOINT" rustfs --access-key rustfsadmin >"$default_access_cli_log" 2>&1; then + echo "Expected default-access-cli to fail" >&2 + exit 1 +fi +grep -q "RUSTFS_ACCESS_KEY must not use the default rustfsadmin credential" "$default_access_cli_log" + +default_access_file="$TMP_DIR/default-access-key" +printf 'rustfsadmin\n' >"$default_access_file" +run_expect_failure \ + "default-access-file" \ + "RUSTFS_ACCESS_KEY_FILE must not contain the default rustfsadmin credential" \ + RUSTFS_ACCESS_KEY_FILE="$default_access_file" \ + RUSTFS_SECRET_KEY=custom-secret + +cargo_log="$TMP_DIR/cargo.log" +if ! env -i PATH="$PATH" RUSTFS_VOLUMES="$TMP_DIR/data" RUSTFS_OBS_LOG_DIRECTORY= sh "$ENTRYPOINT" cargo --version >"$cargo_log" 2>&1; then + echo "Expected cargo passthrough to skip server credential checks" >&2 + cat "$cargo_log" >&2 + exit 1 +fi +if grep -q "must be set explicitly" "$cargo_log"; then + echo "Cargo passthrough must not require server credentials" >&2 + cat "$cargo_log" >&2 + exit 1 +fi