修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
+132
-14
@@ -17,15 +17,25 @@ QA_DB_ADMIN_DB="${QA_DB_ADMIN_DB:-postgres}"
|
||||
QA_DB_CLIENT="${QA_DB_CLIENT:-host}"
|
||||
QA_DB_DOCKER_CONTAINER="${QA_DB_DOCKER_CONTAINER:-hyb-oa-trialrun-db}"
|
||||
QA_DOCKER_USE_SG="${QA_DOCKER_USE_SG:-0}"
|
||||
QA_DB_PREFIX="${QA_DB_PREFIX:-hyb_oa_webgate}"
|
||||
if [[ "$MODE" == "auth-security-local" ]]; then
|
||||
DEFAULT_QA_DB_PREFIX="hyb_oa_auth"
|
||||
else
|
||||
DEFAULT_QA_DB_PREFIX="hyb_oa_webgate"
|
||||
fi
|
||||
QA_DB_PREFIX="${QA_DB_PREFIX:-$DEFAULT_QA_DB_PREFIX}"
|
||||
QA_KEEP_DB="${QA_KEEP_DB:-0}"
|
||||
QA_SERVER_LOG="${QA_SERVER_LOG:-/tmp/hyb-oa-web-gates-server.log}"
|
||||
QA_SERVER_PID_FILE="${QA_SERVER_PID_FILE:-/tmp/hyb-oa-web-gates-server.pid}"
|
||||
QA_RUN_ID="$(date +%Y%m%d%H%M%S)_$$_$RANDOM"
|
||||
QA_SERVER_LOG="${QA_SERVER_LOG:-/tmp/hyb-oa-web-gates-server-${QA_RUN_ID}.log}"
|
||||
QA_SERVER_PID_FILE="${QA_SERVER_PID_FILE:-/tmp/hyb-oa-web-gates-server-${QA_RUN_ID}.pid}"
|
||||
QA_SERVER_ENTRY="src/index.js"
|
||||
QA_READY_TIMEOUT_SECONDS="${QA_READY_TIMEOUT_SECONDS:-90}"
|
||||
QA_BASE_URL=""
|
||||
QA_DB_NAME="${QA_DB_NAME:-${QA_DB_PREFIX}_$(date +%Y%m%d%H%M%S)_$RANDOM}"
|
||||
QA_DB_CREATED=0
|
||||
QA_SERVER_STARTED=0
|
||||
QA_SERVER_OWNED=0
|
||||
DATABASE_URL="postgresql://${QA_DB_USER}:${QA_DB_PASSWORD}@${QA_DB_HOST}:${QA_DB_PORT}/${QA_DB_NAME}?schema=public"
|
||||
TEST_FIELD_ENCRYPTION_KEY_DEFAULT="${FIELD_ENCRYPTION_KEY:-MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=}"
|
||||
TEST_FIELD_ENCRYPTION_KEY_DEFAULT="MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
|
||||
|
||||
qa_require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
@@ -36,7 +46,7 @@ qa_require_cmd() {
|
||||
|
||||
qa_usage() {
|
||||
cat <<USAGE
|
||||
Usage: bash scripts/qa/run-web-gates.sh <smoke-local|full-local|business-flow-local|project-wide-visual-local|qg6-visual-matrix-local|r70-assist-local|r106-visual-local|r108-mobile-local|r109-mobile-local|r110-mobile-local|smoke-live>
|
||||
Usage: bash scripts/qa/run-web-gates.sh <smoke-local|full-local|auth-security-local|business-flow-local|project-wide-visual-local|qg6-visual-matrix-local|r70-assist-local|r106-visual-local|r108-mobile-local|r109-mobile-local|r110-mobile-local|smoke-live>
|
||||
|
||||
可选环境变量:
|
||||
QA_PORT / QA_HOST
|
||||
@@ -213,17 +223,32 @@ qa_dropdb() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
qa_psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${QA_DB_NAME}' AND pid <> pg_backend_pid();" >/dev/null || true
|
||||
if ! qa_psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${QA_DB_NAME}' AND pid <> pg_backend_pid();" >/dev/null; then
|
||||
echo "[ERROR] 临时数据库连接清理失败" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$QA_DB_CLIENT" == "docker" ]]; then
|
||||
qa_docker exec -e "PGPASSWORD=$QA_DB_PASSWORD" "$QA_DB_DOCKER_CONTAINER" \
|
||||
dropdb --if-exists -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null || true
|
||||
if ! qa_docker exec -e "PGPASSWORD=$QA_DB_PASSWORD" "$QA_DB_DOCKER_CONTAINER" \
|
||||
dropdb --if-exists -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null; then
|
||||
echo "[ERROR] 临时数据库删除失败" >&2
|
||||
return 1
|
||||
fi
|
||||
QA_DB_CREATED=0
|
||||
return
|
||||
fi
|
||||
|
||||
PGPASSWORD="$QA_DB_PASSWORD" dropdb --if-exists -h "$QA_DB_HOST" -p "$QA_DB_PORT" -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null || true
|
||||
if ! PGPASSWORD="$QA_DB_PASSWORD" dropdb --if-exists -h "$QA_DB_HOST" -p "$QA_DB_PORT" -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null; then
|
||||
echo "[ERROR] 临时数据库删除失败" >&2
|
||||
return 1
|
||||
fi
|
||||
QA_DB_CREATED=0
|
||||
}
|
||||
|
||||
qa_cleanup_server() {
|
||||
if [[ "$QA_SERVER_STARTED" != "1" || "$QA_SERVER_OWNED" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$QA_SERVER_PID_FILE" ]]; then
|
||||
local pid
|
||||
pid="$(cat "$QA_SERVER_PID_FILE")"
|
||||
@@ -242,6 +267,8 @@ qa_cleanup_server() {
|
||||
fi
|
||||
rm -f "$QA_SERVER_PID_FILE"
|
||||
fi
|
||||
QA_SERVER_STARTED=0
|
||||
QA_SERVER_OWNED=0
|
||||
|
||||
if qa_wait_port_free 15; then
|
||||
return 0
|
||||
@@ -251,10 +278,23 @@ qa_cleanup_server() {
|
||||
}
|
||||
|
||||
qa_cleanup() {
|
||||
qa_cleanup_server
|
||||
if [[ -n "${QA_DB_NAME:-}" && "$MODE" != "smoke-live" ]]; then
|
||||
qa_dropdb
|
||||
local original_status=$?
|
||||
local cleanup_status=0
|
||||
trap - EXIT
|
||||
|
||||
if ! qa_cleanup_server; then
|
||||
cleanup_status=1
|
||||
fi
|
||||
if [[ "$QA_DB_CREATED" == "1" && "$MODE" != "smoke-live" ]]; then
|
||||
if ! qa_dropdb; then
|
||||
cleanup_status=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$original_status" != "0" ]]; then
|
||||
exit "$original_status"
|
||||
fi
|
||||
exit "$cleanup_status"
|
||||
}
|
||||
|
||||
qa_export_app_env() {
|
||||
@@ -267,6 +307,7 @@ qa_export_app_env() {
|
||||
export BASE_URL="$QA_BASE_URL"
|
||||
export DATABASE_URL="$DATABASE_URL"
|
||||
export JWT_SECRET="${JWT_SECRET:-qa_web_gates_secret_key}"
|
||||
export AUTH_CODE_PEPPER="${AUTH_CODE_PEPPER:-qa_auth_code_pepper_with_at_least_32_bytes}"
|
||||
export OCR_PROVIDER="${OCR_PROVIDER:-mock}"
|
||||
export OCR_MAX_ATTEMPTS="${OCR_MAX_ATTEMPTS:-3}"
|
||||
export OCR_POLL_CRON="${OCR_POLL_CRON:-*/5 * * * * *}"
|
||||
@@ -290,7 +331,64 @@ qa_export_app_env() {
|
||||
export ERP_SYNC_ENABLED="${ERP_SYNC_ENABLED:-true}"
|
||||
export PAYMENT_BATCH_ENABLED="${PAYMENT_BATCH_ENABLED:-true}"
|
||||
export IM_NOTIFIER_ENABLED="true"
|
||||
export FIELD_ENCRYPTION_KEY="$TEST_FIELD_ENCRYPTION_KEY_DEFAULT"
|
||||
export FIELD_ENCRYPTION_KEY="${FIELD_ENCRYPTION_KEY:-$TEST_FIELD_ENCRYPTION_KEY_DEFAULT}"
|
||||
}
|
||||
|
||||
qa_run_auth_security_local() {
|
||||
case "$QA_HOST" in
|
||||
127.0.0.1|localhost|::1) ;;
|
||||
*)
|
||||
echo "[ERROR] auth-security-local 只允许 loopback API" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$QA_DB_HOST" in
|
||||
127.0.0.1|localhost|::1) ;;
|
||||
*)
|
||||
echo "[ERROR] auth-security-local 只允许 loopback PostgreSQL" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [[ ! "$QA_DB_NAME" =~ ^hyb_oa_auth_[A-Za-z0-9_]+$ ]]; then
|
||||
echo "[ERROR] auth-security-local 数据库名必须使用 hyb_oa_auth_ 前缀" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
qa_select_port
|
||||
export JOBS_ENABLED=false
|
||||
export RATE_LIMIT_ENABLED=false
|
||||
export DEV_SHOW_PASSWORD_RESET_CODE=false
|
||||
unset JWT_SECRET AUTH_CODE_PEPPER FIELD_ENCRYPTION_KEY
|
||||
qa_export_app_env
|
||||
export EMAIL_ENABLED=false
|
||||
export SMTP_HOST=""
|
||||
export SMTP_PORT=""
|
||||
export SMTP_SECURE=false
|
||||
export SMTP_USER=""
|
||||
export SMTP_PASS=""
|
||||
export IM_NOTIFIER_ENABLED=false
|
||||
export WECOM_ENABLED=false
|
||||
export WECOM_AGENT_SECRET=""
|
||||
export OIDC_ENABLED=false
|
||||
export OIDC_CLIENT_SECRET=""
|
||||
unset JWT_SECRET AUTH_CODE_PEPPER FIELD_ENCRYPTION_KEY
|
||||
JWT_SECRET="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("hex"))')"
|
||||
AUTH_CODE_PEPPER="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("hex"))')"
|
||||
FIELD_ENCRYPTION_KEY="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64"))')"
|
||||
export JWT_SECRET AUTH_CODE_PEPPER FIELD_ENCRYPTION_KEY
|
||||
export TRUST_PROXY_HOPS=0
|
||||
export SERVE_CLIENT_DIST=false
|
||||
export RUN_AUTH_SECURITY_REGRESSION=1
|
||||
export AUTH_SECURITY_DATABASE_CONFIRM="$QA_DB_NAME"
|
||||
QA_SERVER_ENTRY="src/scripts/start-auth-security-test-server.js"
|
||||
qa_prepare_db
|
||||
qa_start_server
|
||||
|
||||
echo ">>> 执行认证安全与真实 PostgreSQL 并发回归"
|
||||
(
|
||||
cd "$SERVER_DIR"
|
||||
RUN_AUTH_POSTGRES_CONCURRENCY=1 ./scripts/regression-auth-security.sh
|
||||
)
|
||||
}
|
||||
|
||||
qa_prepare_db() {
|
||||
@@ -303,6 +401,7 @@ qa_prepare_db() {
|
||||
|
||||
echo ">>> 创建临时数据库: $QA_DB_NAME"
|
||||
qa_createdb
|
||||
QA_DB_CREATED=1
|
||||
|
||||
echo ">>> 执行 Prisma migrate"
|
||||
(
|
||||
@@ -318,6 +417,10 @@ qa_prepare_db() {
|
||||
}
|
||||
|
||||
qa_start_server() {
|
||||
if [[ "$QA_SERVER_STARTED" == "1" || "$QA_SERVER_OWNED" == "1" ]]; then
|
||||
echo "[ERROR] 本轮 server 已启动,拒绝重复 spawn" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! qa_wait_port_free 15; then
|
||||
echo "[ERROR] 端口 ${QA_PORT} 已被占用,请改用 QA_PORT=<port> 后重试。" >&2
|
||||
exit 1
|
||||
@@ -328,9 +431,15 @@ qa_start_server() {
|
||||
echo ">>> 启动本地 server: $QA_BASE_URL"
|
||||
(
|
||||
cd "$SERVER_DIR"
|
||||
nohup node src/index.js > "$QA_SERVER_LOG" 2>&1 &
|
||||
nohup node "$QA_SERVER_ENTRY" > "$QA_SERVER_LOG" 2>&1 &
|
||||
echo $! > "$QA_SERVER_PID_FILE"
|
||||
)
|
||||
if [[ ! -s "$QA_SERVER_PID_FILE" ]]; then
|
||||
echo "[ERROR] 未取得本轮 server PID" >&2
|
||||
exit 1
|
||||
fi
|
||||
QA_SERVER_STARTED=1
|
||||
QA_SERVER_OWNED=1
|
||||
|
||||
qa_wait_ready "$QA_READY_TIMEOUT_SECONDS" || {
|
||||
echo "[ERROR] server 未就绪,最近日志如下:" >&2
|
||||
@@ -607,6 +716,15 @@ case "$MODE" in
|
||||
trap qa_cleanup EXIT
|
||||
qa_run_full_local
|
||||
;;
|
||||
auth-security-local)
|
||||
qa_require_cmd curl
|
||||
qa_require_cmd jq
|
||||
qa_require_db_client
|
||||
qa_require_cmd node
|
||||
qa_require_cmd npm
|
||||
trap qa_cleanup EXIT
|
||||
qa_run_auth_security_local
|
||||
;;
|
||||
business-flow-local)
|
||||
qa_require_cmd curl
|
||||
qa_require_db_client
|
||||
|
||||
在新工单中引用
屏蔽一个用户