Test deployment public URL (Linux) / test-linux (push) Canceled after 0s
Test start.ps1 (Windows) / test-windows (push) Canceled after 0s
Test deployment public URL (Linux) / test-linux (pull_request) Canceled after 0s
Test start.ps1 (Windows) / test-windows (pull_request) Canceled after 0s
首次安装默认生成独立加密密钥,更新旧部署保留原配置,通过显式开关进入迁移窗口。增加生产迁移包装入口和完整密钥环备份恢复工具,并说明旧镜像升级、严格迁移、轮换及还原流程。 已通过 Bash 安全环境及密钥备份回归;对应 PowerShell 5.1 和 7 脚本已在 Windows 原生环境各通过 49 项验收。宁波服务器验收尚未执行。
145 lines
5.7 KiB
Bash
Executable File
145 lines
5.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=scripts/init-security-env.sh
|
|
. "${SCRIPT_DIR}/init-security-env.sh"
|
|
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
write_env() {
|
|
local file="$1"
|
|
shift
|
|
printf '%s\n' "$@" > "$file"
|
|
}
|
|
|
|
assert_min_length() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local minimum="$3"
|
|
local value
|
|
value="$(read_env_value "$file" "$key")"
|
|
[ "${#value}" -ge "$minimum" ] || fail "$key must contain at least $minimum characters"
|
|
}
|
|
|
|
assert_value() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local expected="$3"
|
|
local actual
|
|
actual="$(read_env_value "$file" "$key")"
|
|
[ "$actual" = "$expected" ] || fail "$key expected '$expected', got '$actual'"
|
|
}
|
|
|
|
assert_single_key() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local count
|
|
count="$(grep -c "^${key}=" "$file" || true)"
|
|
[ "$count" -eq 1 ] || fail "$key must appear exactly once"
|
|
}
|
|
|
|
file_sha256() {
|
|
local file="$1"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$file" | awk '{print $1}'
|
|
else
|
|
shasum -a 256 "$file" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
upgrade_env="$TMP_DIR/upgrade.env"
|
|
upgrade_log="$TMP_DIR/upgrade.log"
|
|
write_env "$upgrade_env" \
|
|
"CONFIG_JWT_SECRET='this is a very secret secret'" \
|
|
"CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY=" \
|
|
"WS_AUTH_WS_TICKET_SECRET=" \
|
|
"WS_AUTH_METHODS=none,bearer"
|
|
init_security_env "$upgrade_env" upgrade > "$upgrade_log"
|
|
|
|
assert_min_length "$upgrade_env" "CONFIG_JWT_SECRET" 32
|
|
assert_min_length "$upgrade_env" "WS_AUTH_WS_TICKET_SECRET" 32
|
|
assert_min_length "$upgrade_env" "MODEL_CONVERSION_WORKER_TOKEN" 32
|
|
assert_value "$upgrade_env" "CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY" ""
|
|
assert_value "$upgrade_env" "WS_AUTH_METHODS" "none,bearer,ws_ticket"
|
|
assert_single_key "$upgrade_env" "WS_AUTH_WS_TICKET_SECRET"
|
|
assert_single_key "$upgrade_env" "WS_AUTH_METHODS"
|
|
|
|
upgrade_jwt="$(read_env_value "$upgrade_env" "CONFIG_JWT_SECRET")"
|
|
upgrade_ws="$(read_env_value "$upgrade_env" "WS_AUTH_WS_TICKET_SECRET")"
|
|
upgrade_worker="$(read_env_value "$upgrade_env" "MODEL_CONVERSION_WORKER_TOKEN")"
|
|
if grep -Fq "$upgrade_jwt" "$upgrade_log" || grep -Fq "$upgrade_ws" "$upgrade_log" || grep -Fq "$upgrade_worker" "$upgrade_log"; then
|
|
fail "security initialization output must not reveal generated secrets"
|
|
fi
|
|
|
|
before_repeat="$(file_sha256 "$upgrade_env")"
|
|
init_security_env "$upgrade_env" upgrade > "$TMP_DIR/repeat.log"
|
|
after_repeat="$(file_sha256 "$upgrade_env")"
|
|
[ "$before_repeat" = "$after_repeat" ] || fail "repeated upgrade must preserve generated values byte-for-byte"
|
|
|
|
preserved_secret="existing-ws-ticket-secret-that-is-long-enough"
|
|
preserved_env="$TMP_DIR/preserved.env"
|
|
write_env "$preserved_env" \
|
|
"CONFIG_JWT_SECRET=existing-jwt-secret-that-is-long-enough" \
|
|
"WS_AUTH_WS_TICKET_SECRET=$preserved_secret" \
|
|
"MODEL_CONVERSION_WORKER_TOKEN=$preserved_secret" \
|
|
"WS_AUTH_METHODS=bearer,ws_ticket"
|
|
init_security_env "$preserved_env" upgrade > "$TMP_DIR/preserved.log"
|
|
assert_value "$preserved_env" "WS_AUTH_WS_TICKET_SECRET" "$preserved_secret"
|
|
assert_value "$preserved_env" "MODEL_CONVERSION_WORKER_TOKEN" "$preserved_secret"
|
|
assert_value "$preserved_env" "WS_AUTH_METHODS" "bearer,ws_ticket"
|
|
|
|
new_env="$TMP_DIR/new.env"
|
|
write_env "$new_env" \
|
|
"CONFIG_JWT_SECRET=" \
|
|
"CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY=" \
|
|
"CONFIG_OTP_HASH_SECRET=" \
|
|
"CONFIG_AUDIT_HASH_PEPPER=" \
|
|
"CONFIG_AUDIT_INTEGRITY_KEY=" \
|
|
"CONFIG_INITIAL_ADMIN_PASSWORD=" \
|
|
"WS_AUTH_WS_TICKET_SECRET=" \
|
|
"WS_AUTH_METHODS="
|
|
init_security_env "$new_env" new > "$TMP_DIR/new.log"
|
|
for key in \
|
|
CONFIG_JWT_SECRET \
|
|
CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY \
|
|
CONFIG_OTP_HASH_SECRET \
|
|
CONFIG_AUDIT_HASH_PEPPER \
|
|
CONFIG_AUDIT_INTEGRITY_KEY \
|
|
WS_AUTH_WS_TICKET_SECRET; do
|
|
assert_min_length "$new_env" "$key" 32
|
|
done
|
|
assert_min_length "$new_env" "CONFIG_INITIAL_ADMIN_PASSWORD" 12
|
|
assert_value "$new_env" "WS_AUTH_METHODS" "none,bearer,ws_ticket"
|
|
|
|
mode="$(stat -c '%a' "$new_env" 2>/dev/null || stat -f '%Lp' "$new_env")"
|
|
[ "$mode" = "600" ] || fail "generated environment file mode must be 600, got $mode"
|
|
|
|
assert_value "$upgrade_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING ''
|
|
assert_value "$new_env" CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT false
|
|
new_ring="$(read_env_value "$new_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING)"
|
|
new_key="${new_ring#*:\"}"; new_key="${new_key%\"\}}"
|
|
[ "$(printf '%s' "$new_key" | openssl base64 -d -A | wc -c | tr -d ' ')" = 32 ] || fail 'credential key length must be 32 bytes'
|
|
if grep -Fq "$new_key" "$TMP_DIR/new.log"; then fail 'credential key must not appear in logs'; fi
|
|
DEPLOY_ENABLE_CREDENTIAL_ENCRYPTION=true init_security_env "$upgrade_env" upgrade > "$TMP_DIR/opt-in.log"
|
|
assert_value "$upgrade_env" CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT true
|
|
opted_ring="$(read_env_value "$upgrade_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING)"
|
|
init_security_env "$upgrade_env" upgrade > "$TMP_DIR/opt-repeat.log"
|
|
assert_value "$upgrade_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING "$opted_ring"
|
|
write_env "$TMP_DIR/partial.env" CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=v1 CONFIG_CREDENTIAL_ENCRYPTION_KEYRING=
|
|
if init_security_env "$TMP_DIR/partial.env" upgrade > "$TMP_DIR/partial.log" 2>&1; then fail 'partial keyring must fail'; fi
|
|
rotate_key="$(openssl rand -base64 32 | tr -d '\r\n')"
|
|
rotated="${new_ring%\}},\"next\":\"${rotate_key}\"}"
|
|
write_env_value "$new_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING "'$rotated'"
|
|
write_env_value "$new_env" CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID next
|
|
init_security_env "$new_env" upgrade > "$TMP_DIR/rotation.log"
|
|
assert_value "$new_env" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING "$rotated"
|
|
echo "Security environment shell tests passed"
|