fix(deploy): align vector bootstrap identity policy
This commit is contained in:
@@ -116,3 +116,18 @@ Final tests:
|
|||||||
- `./scripts/test-vector-bootstrap-rotation.sh`: PASS
|
- `./scripts/test-vector-bootstrap-rotation.sh`: PASS
|
||||||
- `./scripts/local-vector-smoke.sh`: PASS with negative and positive live bootstrap rotation
|
- `./scripts/local-vector-smoke.sh`: PASS with negative and positive live bootstrap rotation
|
||||||
- existing local-vector collision/safety and Compose deployment contracts: PASS
|
- existing local-vector collision/safety and Compose deployment contracts: PASS
|
||||||
|
|
||||||
|
## Final identity and secret-policy alignment
|
||||||
|
|
||||||
|
- `THT_VECTOR_BOOTSTRAP_USER` is now passed through core as well as vector-db and reconciliation,
|
||||||
|
so the rotation helper uses the authoritative configured role instead of defaulting to `postgres`.
|
||||||
|
- Rotation and reconciliation source the same raw-file `secret-policy.sh`: non-empty and no
|
||||||
|
whitespace, including trailing newlines. Rotation validates both files before Docker,
|
||||||
|
PostgreSQL, or atomic replacement staging; `test-vector-secret-policy.sh` pins empty, newline,
|
||||||
|
internal-space, and valid metacharacter cases.
|
||||||
|
- Fake-Docker tests prove a non-default identity reaches the helper path and whitespace rejection
|
||||||
|
performs no Docker call and creates no staged replacement.
|
||||||
|
- The real smoke runs the entire stack as `thoth_bootstrap_smoke`. Its whitespace-negative case
|
||||||
|
leaves the deployment file unchanged and proves the existing database login still succeeds;
|
||||||
|
non-default-account bootstrap rotation, reconciliation, migration, core health, restart, and
|
||||||
|
persisted retrieval all pass.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ services:
|
|||||||
THT_DATA_ROOT: /data
|
THT_DATA_ROOT: /data
|
||||||
SETTINGS_FILE: /data/settings/settings.json
|
SETTINGS_FILE: /data/settings/settings.json
|
||||||
THT_VECTOR_DATABASE: "${THT_VECTOR_DATABASE:-thoth}"
|
THT_VECTOR_DATABASE: "${THT_VECTOR_DATABASE:-thoth}"
|
||||||
|
THT_VECTOR_BOOTSTRAP_USER: "${THT_VECTOR_BOOTSTRAP_USER:-postgres}"
|
||||||
THT_VECTOR_READER_USER: "${THT_VECTOR_READER_USER:-thoth_vector_reader}"
|
THT_VECTOR_READER_USER: "${THT_VECTOR_READER_USER:-thoth_vector_reader}"
|
||||||
THT_VECTOR_WRITER_USER: "${THT_VECTOR_WRITER_USER:-thoth_vector_writer}"
|
THT_VECTOR_WRITER_USER: "${THT_VECTOR_WRITER_USER:-thoth_vector_writer}"
|
||||||
THT_VECTOR_READER_PASSWORD: "${THT_VECTOR_READER_PASSWORD:-}"
|
THT_VECTOR_READER_PASSWORD: "${THT_VECTOR_READER_PASSWORD:-}"
|
||||||
@@ -92,6 +93,7 @@ services:
|
|||||||
PGPORT: 5432
|
PGPORT: 5432
|
||||||
PGDATABASE: "${THT_VECTOR_DATABASE:-thoth}"
|
PGDATABASE: "${THT_VECTOR_DATABASE:-thoth}"
|
||||||
PGUSER: "${THT_VECTOR_BOOTSTRAP_USER:-postgres}"
|
PGUSER: "${THT_VECTOR_BOOTSTRAP_USER:-postgres}"
|
||||||
|
THT_VECTOR_BOOTSTRAP_USER: "${THT_VECTOR_BOOTSTRAP_USER:-postgres}"
|
||||||
THT_VECTOR_MIGRATOR_USER: "${THT_VECTOR_MIGRATOR_USER:-thoth_vector_migrator}"
|
THT_VECTOR_MIGRATOR_USER: "${THT_VECTOR_MIGRATOR_USER:-thoth_vector_migrator}"
|
||||||
THT_VECTOR_READER_USER: "${THT_VECTOR_READER_USER:-thoth_vector_reader}"
|
THT_VECTOR_READER_USER: "${THT_VECTOR_READER_USER:-thoth_vector_reader}"
|
||||||
THT_VECTOR_WRITER_USER: "${THT_VECTOR_WRITER_USER:-thoth_vector_writer}"
|
THT_VECTOR_WRITER_USER: "${THT_VECTOR_WRITER_USER:-thoth_vector_writer}"
|
||||||
@@ -103,6 +105,7 @@ services:
|
|||||||
- vector_writer_password
|
- vector_writer_password
|
||||||
volumes:
|
volumes:
|
||||||
- ./deploy/vector/reconcile-roles.sh:/opt/thoth/reconcile-roles.sh:ro
|
- ./deploy/vector/reconcile-roles.sh:/opt/thoth/reconcile-roles.sh:ro
|
||||||
|
- ./deploy/vector/secret-policy.sh:/opt/thoth/secret-policy.sh:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
vector-db:
|
vector-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ THT_VEC_WRITE_API_KEY=
|
|||||||
|
|
||||||
# Optional local-vector profile. Keep these secret files outside Git and readable by Docker.
|
# Optional local-vector profile. Keep these secret files outside Git and readable by Docker.
|
||||||
THT_VECTOR_DATABASE=thoth
|
THT_VECTOR_DATABASE=thoth
|
||||||
|
THT_VECTOR_BOOTSTRAP_USER=postgres
|
||||||
THT_VECTOR_MIGRATOR_USER=thoth_vector_migrator
|
THT_VECTOR_MIGRATOR_USER=thoth_vector_migrator
|
||||||
THT_VECTOR_READER_USER=thoth_vector_reader
|
THT_VECTOR_READER_USER=thoth_vector_reader
|
||||||
THT_VECTOR_WRITER_USER=thoth_vector_writer
|
THT_VECTOR_WRITER_USER=thoth_vector_writer
|
||||||
|
|||||||
@@ -33,5 +33,10 @@ authentication or new-login verification fails, it exits without changing the de
|
|||||||
verification failure also attempts to restore the old database password over the still-open
|
verification failure also attempts to restore the old database password over the still-open
|
||||||
authenticated connection. After success, run the printed `vector-reconcile`/migration/core command.
|
authenticated connection. After success, run the printed `vector-reconcile`/migration/core command.
|
||||||
|
|
||||||
|
`THT_VECTOR_BOOTSTRAP_USER` is authoritative for database initialization, reconciliation, and
|
||||||
|
rotation; non-default bootstrap role names are supported. Bootstrap, migrator, reader, and writer
|
||||||
|
secret files must be non-empty and contain no whitespace (including trailing newlines). Rotation
|
||||||
|
rejects invalid files before contacting PostgreSQL or staging a deployment-file replacement.
|
||||||
|
|
||||||
Keep the staged new file on the same trusted host, mode `0600`, and retain a secure backup until the
|
Keep the staged new file on the same trusted host, mode `0600`, and retain a secure backup until the
|
||||||
post-rotation reconciliation and application health checks pass.
|
post-rotation reconciliation and application health checks pass.
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
read_secret() {
|
. /opt/thoth/secret-policy.sh
|
||||||
value=$(cat "/run/secrets/$1")
|
|
||||||
if [ -z "$value" ] || printf '%s' "$value" | grep -q '[[:space:]]'; then
|
|
||||||
echo "$1 must be non-empty and contain no whitespace" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
printf '%s' "$value"
|
|
||||||
}
|
|
||||||
|
|
||||||
export PGPASSWORD=$(read_secret vector_bootstrap_password)
|
export PGPASSWORD=$(read_secret_file /run/secrets/vector_bootstrap_password vector_bootstrap_password)
|
||||||
migrator_password=$(read_secret vector_migrator_password)
|
migrator_password=$(read_secret_file /run/secrets/vector_migrator_password vector_migrator_password)
|
||||||
reader_password=$(read_secret vector_reader_password)
|
reader_password=$(read_secret_file /run/secrets/vector_reader_password vector_reader_password)
|
||||||
writer_password=$(read_secret vector_writer_password)
|
writer_password=$(read_secret_file /run/secrets/vector_writer_password vector_writer_password)
|
||||||
|
|
||||||
psql --set=ON_ERROR_STOP=1 \
|
psql --set=ON_ERROR_STOP=1 \
|
||||||
--set=migrator_user="$THT_VECTOR_MIGRATOR_USER" \
|
--set=migrator_user="$THT_VECTOR_MIGRATOR_USER" \
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ from psycopg2 import sql
|
|||||||
|
|
||||||
|
|
||||||
def read_secret(path: str) -> str:
|
def read_secret(path: str) -> str:
|
||||||
value = Path(path).read_text().rstrip("\r\n")
|
value = Path(path).read_text()
|
||||||
if not value or "\x00" in value:
|
if not value or "\x00" in value or any(character.isspace() for character in value):
|
||||||
raise ValueError("secret must be non-empty and contain no NUL bytes")
|
raise ValueError("secret must be non-empty and contain no whitespace or NUL bytes")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
validate_secret_file() {
|
||||||
|
secret_path=$1
|
||||||
|
secret_name=$2
|
||||||
|
if [ ! -f "$secret_path" ] || [ ! -r "$secret_path" ] || [ ! -s "$secret_path" ]; then
|
||||||
|
echo "$secret_name must be a readable, non-empty regular file" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
if LC_ALL=C grep -q '[[:space:]]' "$secret_path"; then
|
||||||
|
echo "$secret_name must contain no whitespace" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
read_secret_file() {
|
||||||
|
validate_secret_file "$1" "$2" || return
|
||||||
|
cat "$1"
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ export THT_VECTOR_READER_PASSWORD_SECRET_FILE="$secret_dir/reader"
|
|||||||
export THT_VECTOR_WRITER_PASSWORD_SECRET_FILE="$secret_dir/writer"
|
export THT_VECTOR_WRITER_PASSWORD_SECRET_FILE="$secret_dir/writer"
|
||||||
export THT_VECTOR_READER_PASSWORD="smoke-reader-${smoke_project}"
|
export THT_VECTOR_READER_PASSWORD="smoke-reader-${smoke_project}"
|
||||||
export THT_VECTOR_WRITER_PASSWORD="smoke-writer-${smoke_project}"
|
export THT_VECTOR_WRITER_PASSWORD="smoke-writer-${smoke_project}"
|
||||||
|
export THT_VECTOR_BOOTSTRAP_USER=thoth_bootstrap_smoke
|
||||||
export THOTH_SMOKE_OWNER="$smoke_owner"
|
export THOTH_SMOKE_OWNER="$smoke_owner"
|
||||||
|
|
||||||
compose() {
|
compose() {
|
||||||
@@ -167,7 +168,7 @@ migration_status=$(compose run --rm --no-deps vector-migrate)
|
|||||||
printf '%s\n' "$migration_status" | grep -q '"pending": \[\]'
|
printf '%s\n' "$migration_status" | grep -q '"pending": \[\]'
|
||||||
migrator_flags=$(compose run --rm --no-deps --entrypoint sh vector-reconcile -ec '
|
migrator_flags=$(compose run --rm --no-deps --entrypoint sh vector-reconcile -ec '
|
||||||
export PGPASSWORD=$(cat /run/secrets/vector_bootstrap_password)
|
export PGPASSWORD=$(cat /run/secrets/vector_bootstrap_password)
|
||||||
psql -At --host vector-db --username postgres --dbname thoth \
|
psql -At --host vector-db --username "$THT_VECTOR_BOOTSTRAP_USER" --dbname thoth \
|
||||||
--command "SELECT (NOT rolcreaterole) AND (NOT rolcreatedb) AND (NOT rolsuper) FROM pg_roles WHERE rolname = '\''thoth_vector_migrator'\''"
|
--command "SELECT (NOT rolcreaterole) AND (NOT rolcreatedb) AND (NOT rolsuper) FROM pg_roles WHERE rolname = '\''thoth_vector_migrator'\''"
|
||||||
')
|
')
|
||||||
test "$migrator_flags" = t
|
test "$migrator_flags" = t
|
||||||
@@ -199,6 +200,20 @@ old_bootstrap_password=$(cat "$secret_dir/bootstrap")
|
|||||||
printf '%s' "wrong-bootstrap-${smoke_project}" >"$secret_dir/bootstrap-wrong"
|
printf '%s' "wrong-bootstrap-${smoke_project}" >"$secret_dir/bootstrap-wrong"
|
||||||
printf '%s' "next-bootstrap-'quoted-${smoke_project}" >"$secret_dir/bootstrap-next"
|
printf '%s' "next-bootstrap-'quoted-${smoke_project}" >"$secret_dir/bootstrap-next"
|
||||||
cp "$secret_dir/bootstrap" "$secret_dir/bootstrap-before-negative"
|
cp "$secret_dir/bootstrap" "$secret_dir/bootstrap-before-negative"
|
||||||
|
printf 'invalid bootstrap password\n' >"$secret_dir/bootstrap-whitespace"
|
||||||
|
if COMPOSE_PROJECT_NAME="$smoke_project" \
|
||||||
|
./scripts/vector-rotate-bootstrap-password.sh \
|
||||||
|
"$secret_dir/bootstrap" "$secret_dir/bootstrap-whitespace" \
|
||||||
|
>/dev/null 2>&1; then
|
||||||
|
echo "bootstrap rotation accepted whitespace in a secret" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmp "$secret_dir/bootstrap" "$secret_dir/bootstrap-before-negative"
|
||||||
|
compose run --rm --no-deps --entrypoint psql \
|
||||||
|
-e PGPASSWORD="$old_bootstrap_password" vector-reconcile \
|
||||||
|
--host vector-db --username "$THT_VECTOR_BOOTSTRAP_USER" --dbname thoth \
|
||||||
|
--command 'SELECT 1' >/dev/null
|
||||||
|
|
||||||
if COMPOSE_PROJECT_NAME="$smoke_project" \
|
if COMPOSE_PROJECT_NAME="$smoke_project" \
|
||||||
./scripts/vector-rotate-bootstrap-password.sh \
|
./scripts/vector-rotate-bootstrap-password.sh \
|
||||||
"$secret_dir/bootstrap-wrong" "$secret_dir/bootstrap-next" \
|
"$secret_dir/bootstrap-wrong" "$secret_dir/bootstrap-next" \
|
||||||
@@ -215,14 +230,14 @@ new_bootstrap_password=$(cat "$secret_dir/bootstrap")
|
|||||||
test "$new_bootstrap_password" != "$old_bootstrap_password"
|
test "$new_bootstrap_password" != "$old_bootstrap_password"
|
||||||
if compose run --rm --no-deps --entrypoint psql \
|
if compose run --rm --no-deps --entrypoint psql \
|
||||||
-e PGPASSWORD="$old_bootstrap_password" vector-reconcile \
|
-e PGPASSWORD="$old_bootstrap_password" vector-reconcile \
|
||||||
--host vector-db --username postgres --dbname thoth --command 'SELECT 1' \
|
--host vector-db --username "$THT_VECTOR_BOOTSTRAP_USER" --dbname thoth --command 'SELECT 1' \
|
||||||
>/dev/null 2>&1; then
|
>/dev/null 2>&1; then
|
||||||
echo "old bootstrap credential still works after rotation" >&2
|
echo "old bootstrap credential still works after rotation" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
compose run --rm --no-deps --entrypoint psql \
|
compose run --rm --no-deps --entrypoint psql \
|
||||||
-e PGPASSWORD="$new_bootstrap_password" vector-reconcile \
|
-e PGPASSWORD="$new_bootstrap_password" vector-reconcile \
|
||||||
--host vector-db --username postgres --dbname thoth --command 'SELECT 1' \
|
--host vector-db --username "$THT_VECTOR_BOOTSTRAP_USER" --dbname thoth --command 'SELECT 1' \
|
||||||
>/dev/null
|
>/dev/null
|
||||||
compose run --rm vector-reconcile
|
compose run --rm vector-reconcile
|
||||||
bootstrap_rotation_status=$(compose run --rm --no-deps vector-migrate)
|
bootstrap_rotation_status=$(compose run --rm --no-deps vector-migrate)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ log="$tmp/docker.log"
|
|||||||
cat >"$fake" <<'SH'
|
cat >"$fake" <<'SH'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
printf '%s\n' "$*" >>"$FAKE_DOCKER_LOG"
|
printf '%s:%s\n' "${THT_VECTOR_BOOTSTRAP_USER:-unset}" "$*" >>"$FAKE_DOCKER_LOG"
|
||||||
exit "${FAKE_DOCKER_EXIT:-0}"
|
exit "${FAKE_DOCKER_EXIT:-0}"
|
||||||
SH
|
SH
|
||||||
chmod 0755 "$fake"
|
chmod 0755 "$fake"
|
||||||
@@ -19,6 +19,21 @@ printf '%s' old-password >"$tmp/old"
|
|||||||
printf '%s' "new-'quoted-\$-password" >"$tmp/new"
|
printf '%s' "new-'quoted-\$-password" >"$tmp/new"
|
||||||
cp "$tmp/old" "$tmp/original"
|
cp "$tmp/old" "$tmp/original"
|
||||||
|
|
||||||
|
printf 'invalid password\n' >"$tmp/whitespace"
|
||||||
|
: >"$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" \
|
||||||
|
>"$tmp/out" 2>"$tmp/err"; then
|
||||||
|
echo "rotation accepted a whitespace-containing secret" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmp "$tmp/old" "$tmp/original"
|
||||||
|
test ! -s "$log"
|
||||||
|
if find "$tmp" -name 'old.rotate.*' -print | grep -q .; then
|
||||||
|
echo "rotation staged a deployment file before secret validation" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$log" FAKE_DOCKER_EXIT=1 \
|
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 "$tmp/old" "$tmp/new" \
|
||||||
>"$tmp/out" 2>"$tmp/err"; then
|
>"$tmp/out" 2>"$tmp/err"; then
|
||||||
@@ -28,12 +43,13 @@ fi
|
|||||||
cmp "$tmp/old" "$tmp/original"
|
cmp "$tmp/old" "$tmp/original"
|
||||||
|
|
||||||
: >"$log"
|
: >"$log"
|
||||||
PATH="$tmp:$PATH" FAKE_DOCKER_LOG="$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 "$tmp/old" "$tmp/new" \
|
||||||
>"$tmp/out" 2>"$tmp/err"
|
>"$tmp/out" 2>"$tmp/err"
|
||||||
cmp "$tmp/old" "$tmp/new"
|
cmp "$tmp/old" "$tmp/new"
|
||||||
grep -q '/run/secrets/bootstrap-old:ro' "$log"
|
grep -q '/run/secrets/bootstrap-old:ro' "$log"
|
||||||
grep -q '/run/secrets/bootstrap-new: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"
|
grep -q 'atomically replaced only after verified database login' "$tmp/out"
|
||||||
|
|
||||||
echo "bootstrap rotation ordering and no-config-change failure contracts passed."
|
echo "bootstrap rotation ordering and no-config-change failure contracts passed."
|
||||||
|
|||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
. ./deploy/vector/secret-policy.sh
|
||||||
|
|
||||||
|
: >"$tmp/empty"
|
||||||
|
printf 'has newline\n' >"$tmp/newline"
|
||||||
|
printf 'has space' >"$tmp/space"
|
||||||
|
printf 'safe-quoted-\047-dollar-$' >"$tmp/valid"
|
||||||
|
|
||||||
|
for invalid in empty newline space; do
|
||||||
|
if validate_secret_file "$tmp/$invalid" "$invalid" >/dev/null 2>&1; then
|
||||||
|
echo "secret policy accepted $invalid" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
validate_secret_file "$tmp/valid" valid
|
||||||
|
test "$(read_secret_file "$tmp/valid" valid)" = "safe-quoted-'-dollar-$"
|
||||||
|
|
||||||
|
echo "shared vector secret policy contracts passed."
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
. ./deploy/vector/secret-policy.sh
|
||||||
|
|
||||||
if [ "$#" -ne 2 ]; then
|
if [ "$#" -ne 2 ]; then
|
||||||
echo "usage: $0 OLD_SECRET_FILE NEW_SECRET_FILE" >&2
|
echo "usage: $0 OLD_SECRET_FILE NEW_SECRET_FILE" >&2
|
||||||
@@ -15,12 +16,8 @@ absolute_file() {
|
|||||||
|
|
||||||
old_secret=$(absolute_file "$1")
|
old_secret=$(absolute_file "$1")
|
||||||
new_secret=$(absolute_file "$2")
|
new_secret=$(absolute_file "$2")
|
||||||
for secret in "$old_secret" "$new_secret"; do
|
validate_secret_file "$old_secret" old_bootstrap_secret
|
||||||
if [ ! -f "$secret" ] || [ ! -r "$secret" ] || [ ! -s "$secret" ]; then
|
validate_secret_file "$new_secret" new_bootstrap_secret
|
||||||
echo "secret file must be a readable, non-empty regular file: $secret" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$old_secret" -ef "$new_secret" ]; then
|
if [ "$old_secret" -ef "$new_secret" ]; then
|
||||||
echo "old and new secret files must be distinct" >&2
|
echo "old and new secret files must be distinct" >&2
|
||||||
exit 2
|
exit 2
|
||||||
|
|||||||
Reference in New Issue
Block a user