fix: pass vector rotation operator env

This commit is contained in:
2026-08-05 08:12:38 +02:00
parent 09834d5cd4
commit c01202f06c
4 changed files with 139 additions and 10 deletions
+3
View File
@@ -241,6 +241,7 @@ chmod 0600 "$secret_dir/bootstrap-wrong" "$secret_dir/bootstrap-next" \
"$secret_dir/bootstrap-before-negative" "$secret_dir/bootstrap-whitespace"
if COMPOSE_PROJECT_NAME="$smoke_project" \
./scripts/vector-rotate-bootstrap-password.sh \
--env-file "$operator_env" \
"$secret_dir/bootstrap" "$secret_dir/bootstrap-whitespace" \
>/dev/null 2>&1; then
echo "bootstrap rotation accepted whitespace in a secret" >&2
@@ -254,6 +255,7 @@ compose run --rm --no-deps --entrypoint psql \
if COMPOSE_PROJECT_NAME="$smoke_project" \
./scripts/vector-rotate-bootstrap-password.sh \
--env-file "$operator_env" \
"$secret_dir/bootstrap-wrong" "$secret_dir/bootstrap-next" \
>/dev/null 2>&1; then
echo "bootstrap rotation accepted the wrong old secret" >&2
@@ -263,6 +265,7 @@ cmp "$secret_dir/bootstrap" "$secret_dir/bootstrap-before-negative"
COMPOSE_PROJECT_NAME="$smoke_project" \
./scripts/vector-rotate-bootstrap-password.sh \
--env-file "$operator_env" \
"$secret_dir/bootstrap" "$secret_dir/bootstrap-next"
new_bootstrap_password=$(cat "$secret_dir/bootstrap")
bootstrap_password="$new_bootstrap_password"
+75 -6
View File
@@ -10,7 +10,29 @@ log="$tmp/docker.log"
cat >"$fake" <<'SH'
#!/bin/sh
set -eu
printf '%s:%s\n' "${THT_VECTOR_BOOTSTRAP_USER:-unset}" "$*" >>"$FAKE_DOCKER_LOG"
invocation=$*
test "${1:-}" = compose
shift
env_file=
while [ "$#" -gt 0 ]; do
case "$1" in
--env-file)
[ "$#" -ge 2 ] || exit 64
env_file=$2
shift 2
;;
--env-file=*)
env_file=${1#--env-file=}
shift
;;
*) shift ;;
esac
done
[ -n "$env_file" ] && [ -f "$env_file" ] || {
echo "fake docker rejected missing env file: $env_file" >&2
exit 64
}
printf '%s:%s\n' "${THT_VECTOR_BOOTSTRAP_USER:-unset}" "$invocation" >>"$FAKE_DOCKER_LOG"
exit "${FAKE_DOCKER_EXIT:-0}"
SH
chmod 0755 "$fake"
@@ -20,10 +42,46 @@ printf '%s' "new-'quoted-\$-password" >"$tmp/new"
cp "$tmp/old" "$tmp/original"
printf 'invalid password\n' >"$tmp/whitespace"
chmod 0600 "$tmp/old" "$tmp/new" "$tmp/original" "$tmp/whitespace"
printf '%s\n' 'THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/thoth-workspaces.git' \
>"$tmp/operator.env"
printf '%s\n' 'THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/unsafe.git' \
>"$tmp/unsafe.env"
ln -s "$tmp/operator.env" "$tmp/operator-link.env"
chmod 0600 "$tmp/old" "$tmp/new" "$tmp/original" "$tmp/whitespace" "$tmp/operator.env"
chmod 0660 "$tmp/unsafe.env"
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" \
./scripts/vector-rotate-bootstrap-password.sh "$tmp/old" "$tmp/new" \
>"$tmp/out" 2>"$tmp/err"; then
echo "rotation silently assumed an operator env file" >&2
exit 1
fi
grep -q 'requires --env-file or THT_VECTOR_OPERATOR_ENV_FILE' "$tmp/err"
test ! -s "$log"
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" \
./scripts/vector-rotate-bootstrap-password.sh --env-file "$tmp/missing.env" \
"$tmp/old" "$tmp/new" >"$tmp/out" 2>"$tmp/err"; then
echo "rotation accepted a nonexistent operator env file" >&2
exit 1
fi
grep -q 'operator env must be a readable regular file' "$tmp/err"
test ! -s "$log"
for unsafe_env in "$tmp/unsafe.env" "$tmp/operator-link.env"; do
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" \
./scripts/vector-rotate-bootstrap-password.sh --env-file "$unsafe_env" \
"$tmp/old" "$tmp/new" >"$tmp/out" 2>"$tmp/err"; then
echo "rotation accepted unsafe operator env file $unsafe_env" >&2
exit 1
fi
test ! -s "$log"
done
: >"$log"
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" THT_VECTOR_BOOTSTRAP_USER=custom_admin \
./scripts/vector-rotate-bootstrap-password.sh "$tmp/old" "$tmp/whitespace" \
./scripts/vector-rotate-bootstrap-password.sh --env-file "$tmp/operator.env" \
"$tmp/old" "$tmp/whitespace" \
>"$tmp/out" 2>"$tmp/err"; then
echo "rotation accepted a whitespace-containing secret" >&2
exit 1
@@ -36,7 +94,8 @@ if find "$tmp" -name 'old.rotate.*' -print | grep -q .; then
fi
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" FAKE_DOCKER_EXIT=1 \
./scripts/vector-rotate-bootstrap-password.sh "$tmp/old" "$tmp/new" \
./scripts/vector-rotate-bootstrap-password.sh --env-file "$tmp/operator.env" \
"$tmp/old" "$tmp/new" \
>"$tmp/out" 2>"$tmp/err"; then
echo "rotation unexpectedly succeeded when database verification failed" >&2
exit 1
@@ -45,12 +104,22 @@ cmp "$tmp/old" "$tmp/original"
: >"$log"
PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" THT_VECTOR_BOOTSTRAP_USER=custom_admin \
./scripts/vector-rotate-bootstrap-password.sh "$tmp/old" "$tmp/new" \
./scripts/vector-rotate-bootstrap-password.sh --env-file "$tmp/operator.env" \
"$tmp/old" "$tmp/new" \
>"$tmp/out" 2>"$tmp/err"
cmp "$tmp/old" "$tmp/new"
grep -q -- "--env-file $tmp/operator.env" "$log"
grep -q '/run/secrets/bootstrap-old:ro' "$log"
grep -q '/run/secrets/bootstrap-new:ro' "$log"
grep -q '^custom_admin:' "$log"
grep -q 'atomically replaced only after verified database login' "$tmp/out"
echo "bootstrap rotation ordering and no-config-change failure contracts passed."
printf '%s' old-password >"$tmp/old"
: >"$log"
PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" \
THT_VECTOR_OPERATOR_ENV_FILE="$tmp/operator.env" \
./scripts/vector-rotate-bootstrap-password.sh "$tmp/old" "$tmp/new" \
>"$tmp/out" 2>"$tmp/err"
grep -q -- "--env-file $tmp/operator.env" "$log"
echo "bootstrap rotation env propagation, validation, ordering, and failure contracts passed."
+47 -4
View File
@@ -4,9 +4,49 @@ set -eu
cd "$(dirname "$0")/.."
. ./deploy/vector/secret-policy.sh
if [ "$#" -ne 2 ]; then
echo "usage: $0 OLD_SECRET_FILE NEW_SECRET_FILE" >&2
usage() {
echo "usage: $0 [--env-file OPERATOR_ENV] OLD_SECRET_FILE NEW_SECRET_FILE" >&2
echo "set THT_VECTOR_OPERATOR_ENV_FILE instead of --env-file when required by automation" >&2
exit 2
}
operator_env=${THT_VECTOR_OPERATOR_ENV_FILE:-}
while [ "$#" -gt 0 ]; do
case "$1" in
--env-file)
[ "$#" -ge 2 ] || usage
operator_env=$2
shift 2
;;
--env-file=*)
operator_env=${1#--env-file=}
shift
;;
--) shift; break ;;
-*) usage ;;
*) break ;;
esac
done
if [ -z "$operator_env" ]; then
echo "rotation requires --env-file or THT_VECTOR_OPERATOR_ENV_FILE; there is no implicit default" >&2
exit 2
fi
if [ -L "$operator_env" ] || [ ! -f "$operator_env" ] || [ ! -r "$operator_env" ]; then
echo "operator env must be a readable regular file, not a symlink: $operator_env" >&2
exit 2
fi
operator_env_mode=$(stat -c '%a' "$operator_env" 2>/dev/null || stat -f '%Lp' "$operator_env" 2>/dev/null) || {
echo "cannot inspect operator env permissions: $operator_env" >&2
exit 2
}
if [ $((0$operator_env_mode & 022)) -ne 0 ]; then
echo "operator env must not be writable by group or other users: $operator_env" >&2
exit 2
fi
if [ "$#" -ne 2 ]; then
usage
fi
absolute_file() {
@@ -14,6 +54,8 @@ absolute_file() {
printf '%s/%s\n' "$directory" "$(basename -- "$1")"
}
operator_env=$(absolute_file "$operator_env")
old_secret=$(absolute_file "$1")
new_secret=$(absolute_file "$2")
validate_secret_file "$old_secret" old_bootstrap_secret
@@ -29,7 +71,7 @@ trap 'rm -f "$replacement"' EXIT HUP INT TERM
cp "$new_secret" "$replacement"
chmod 0600 "$replacement"
docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local-vector.yaml \
docker compose --env-file "$operator_env" -f compose.yaml -f deploy/compose.local-vector.yaml \
--project-name "$project" --profile local-vector run --rm --no-deps \
--user 0:0 \
--entrypoint /opt/venv/bin/python \
@@ -43,4 +85,5 @@ mv -f "$replacement" "$old_secret"
trap - EXIT HUP INT TERM
echo "Deployment bootstrap secret atomically replaced only after verified database login."
echo "Re-run: docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local-vector.yaml --project-name $project --profile local-vector up --wait vector-reconcile vector-migrate core"
echo "Re-run with the same operator env file: $operator_env"
echo "docker compose --env-file OPERATOR_ENV -f compose.yaml -f deploy/compose.local-vector.yaml --project-name $project --profile local-vector up --wait vector-reconcile vector-migrate core"