310 lines
14 KiB
Bash
Executable File
310 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 账号报告采集器回归:使用假 SSH/MySQL 输出,不连接真实内网主机。
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
SCRIPT="$ROOT_DIR/deploy/collect-dogfood-account-report.sh"
|
||
TMP_DIR="$(mktemp -d)"
|
||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||
|
||
fail() {
|
||
printf 'TEST_FAIL %s\n' "$*" >&2
|
||
exit 1
|
||
}
|
||
|
||
openssl genpkey -algorithm ED25519 -out "$TMP_DIR/private.pem" >/dev/null 2>&1
|
||
openssl pkey -in "$TMP_DIR/private.pem" -pubout -out "$TMP_DIR/public.pem" >/dev/null 2>&1
|
||
|
||
{
|
||
for index in $(seq 1 10); do
|
||
printf 'ACCOUNT\t%d\tdogfoodc%02d\tcreator\tACTIVE\t%d\tCLAIMED\t6849315\t%d\t%d\n' \
|
||
"$((1000 + index))" "$index" "$((2000 + index))" "$((4000 + index))" "$((5000 + index))"
|
||
done
|
||
for index in $(seq 1 30); do
|
||
printf 'ACCOUNT\t%d\tdogfoodp%02d\tplayer\tACTIVE\t%d\tCLAIMED\t6849315\t%d\t%d\n' \
|
||
"$((1100 + index))" "$index" "$((2100 + index))" "$((4100 + index))" "$((5100 + index))"
|
||
done
|
||
for index in $(seq 1 12); do
|
||
printf 'RESERVE\t%d\t%d\t%d\tFREE\t6849315\t\t\t\t\n' \
|
||
"$((3000 + index))" "$((4200 + index))" "$((5200 + index))"
|
||
done
|
||
} >"$TMP_DIR/mysql.tsv"
|
||
|
||
{
|
||
for index in $(seq 1 10); do
|
||
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
|
||
"$((4000 + index))" "$index" "$((5000 + index))"
|
||
done
|
||
for index in $(seq 1 30); do
|
||
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
|
||
"$((4100 + index))" "$((10 + index))" "$((5100 + index))"
|
||
done
|
||
for index in $(seq 1 12); do
|
||
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
|
||
"$((4200 + index))" "$((40 + index))" "$((5200 + index))"
|
||
done
|
||
} >"$TMP_DIR/newapi.tsv"
|
||
|
||
cat >"$TMP_DIR/fake-ssh" <<'SH'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
printf '%s\n' "$*" >>"$DOGFOOD_TEST_SSH_ARGS"
|
||
remote_command="${!#}"
|
||
if [[ "$remote_command" == *DOGFOOD_RUNTIME_STATE_V1* ]]; then
|
||
state_call_count=0
|
||
if [ -f "$DOGFOOD_TEST_STATE_CALL_COUNT" ]; then
|
||
state_call_count="$(cat "$DOGFOOD_TEST_STATE_CALL_COUNT")"
|
||
fi
|
||
state_call_count="$((state_call_count + 1))"
|
||
printf '%s\n' "$state_call_count" >"$DOGFOOD_TEST_STATE_CALL_COUNT"
|
||
if [ "$state_call_count" -eq 2 ] && [ -n "${DOGFOOD_TEST_DRIFT_TARGET:-}" ]; then
|
||
ln -sfn "$DOGFOOD_TEST_DRIFT_TARGET" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
|
||
fi
|
||
bash -c "$remote_command"
|
||
else
|
||
cat >"$DOGFOOD_TEST_SQL_CAPTURE"
|
||
cat "$DOGFOOD_TEST_MYSQL_ROWS"
|
||
fi
|
||
SH
|
||
chmod +x "$TMP_DIR/fake-ssh"
|
||
|
||
cat >"$TMP_DIR/fake-docker" <<'SH'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
printf '%s\n' "$*" >"$DOGFOOD_TEST_DOCKER_ARGS"
|
||
cat >"$DOGFOOD_TEST_PG_SQL_CAPTURE"
|
||
cat "$DOGFOOD_TEST_NEWAPI_ROWS"
|
||
SH
|
||
chmod +x "$TMP_DIR/fake-docker"
|
||
|
||
export DOGFOOD_COLLECTOR_TEST_MODE=1
|
||
export DOGFOOD_COLLECTOR_TEST_SSH_BIN="$TMP_DIR/fake-ssh"
|
||
export DOGFOOD_COLLECTOR_TEST_SIGNER_KEY="$TMP_DIR/private.pem"
|
||
export DOGFOOD_COLLECTOR_TEST_OPENSSL_BIN="$(command -v openssl)"
|
||
export DOGFOOD_COLLECTOR_TEST_DOCKER_BIN="$TMP_DIR/fake-docker"
|
||
export DOGFOOD_TEST_SSH_ARGS="$TMP_DIR/ssh-args"
|
||
export DOGFOOD_TEST_SQL_CAPTURE="$TMP_DIR/mysql.sql"
|
||
export DOGFOOD_TEST_MYSQL_ROWS="$TMP_DIR/mysql.tsv"
|
||
export DOGFOOD_TEST_DOCKER_ARGS="$TMP_DIR/docker-args"
|
||
export DOGFOOD_TEST_PG_SQL_CAPTURE="$TMP_DIR/newapi.sql"
|
||
export DOGFOOD_TEST_NEWAPI_ROWS="$TMP_DIR/newapi.tsv"
|
||
export DOGFOOD_TEST_STATE_CALL_COUNT="$TMP_DIR/state-call-count"
|
||
export DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT="$TMP_DIR/releases"
|
||
mkdir -p "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT"
|
||
export DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT="$(cd "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT" && pwd -P)"
|
||
|
||
active_commit=1111111111111111111111111111111111111111
|
||
active_run_id=20260723T010203Z
|
||
active_release="$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/$active_commit"
|
||
active_evidence="$active_release/evidence/activation-$active_run_id"
|
||
mkdir -p "$active_evidence"
|
||
ln -s "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
|
||
printf 'active commit=%s at=2026-07-23T01:02:03Z evidence=%s\n' \
|
||
"$active_commit" "$active_evidence" >"$active_release/RC_STATUS"
|
||
cat >"$active_evidence/activation.log" <<EOF
|
||
[2026-07-23T01:02:03Z] RC_ACTIVATION_PASS commit=$active_commit active=$active_release
|
||
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
|
||
EOF
|
||
chmod 600 "$active_release/RC_STATUS" "$active_evidence/activation.log"
|
||
|
||
reset_state_probe() {
|
||
rm -f "$DOGFOOD_TEST_STATE_CALL_COUNT"
|
||
unset DOGFOOD_TEST_DRIFT_TARGET
|
||
}
|
||
|
||
assert_refused_without_output() {
|
||
local name="$1"
|
||
shift
|
||
reset_state_probe
|
||
if "$SCRIPT" "$@" --output "$TMP_DIR/$name.json" >/dev/null 2>&1; then
|
||
fail "$name 未被拒绝"
|
||
fi
|
||
[ ! -e "$TMP_DIR/$name.json" ] && [ ! -e "$TMP_DIR/$name.json.sig" ] || \
|
||
fail "$name 失败后遗留半成品"
|
||
}
|
||
|
||
report="$TMP_DIR/account-report.json"
|
||
reset_state_probe
|
||
"$SCRIPT" \
|
||
--activation-run-id "$active_run_id" \
|
||
--rc-commit "$active_commit" \
|
||
--output "$report"
|
||
|
||
[ -f "$report" ] && [ -f "$report.sig" ] || fail '没有生成报告及 detached signature'
|
||
[ "$(stat -c '%a' "$report" 2>/dev/null || stat -f '%Lp' "$report")" = 600 ] || fail '报告权限不是 0600'
|
||
[ "$(stat -c '%a' "$report.sig" 2>/dev/null || stat -f '%Lp' "$report.sig")" = 600 ] || fail '签名权限不是 0600'
|
||
openssl pkeyutl -verify -pubin -inkey "$TMP_DIR/public.pem" -rawin \
|
||
-in "$report" -sigfile "$report.sig" >/dev/null 2>&1 || fail 'detached signature 无法验证'
|
||
grep -q 'root@100.64.0.7' "$TMP_DIR/ssh-args" || fail 'SSH 目标不是固定 mini-desktop'
|
||
grep -q '^START TRANSACTION READ ONLY;' "$TMP_DIR/mysql.sql" || fail 'MySQL 查询未进入服务端只读事务'
|
||
grep -q 'FROM game_player p' "$TMP_DIR/mysql.sql" || fail '固定账号查询缺失'
|
||
grep -q 'FROM newapi_quota_pool q' "$TMP_DIR/mysql.sql" || fail '固定额度池查询缺失'
|
||
if grep -Eqi 'newapi_token_key|password|access_token|refresh_token' "$TMP_DIR/mysql.sql"; then
|
||
fail '固定查询选择了敏感列'
|
||
fi
|
||
grep -q 'infra-postgres' "$TMP_DIR/docker-args" || fail 'new-api PostgreSQL 容器不是固定值'
|
||
grep -q 'new-api' "$TMP_DIR/docker-args" || fail 'new-api PostgreSQL 数据库不是固定值'
|
||
grep -q '^BEGIN TRANSACTION READ ONLY;' "$TMP_DIR/newapi.sql" || fail 'new-api 查询未进入只读事务'
|
||
grep -q 'FROM users u' "$TMP_DIR/newapi.sql" || fail 'new-api users 固定查询缺失'
|
||
grep -q 'JOIN tokens t' "$TMP_DIR/newapi.sql" || fail 'new-api tokens 固定查询缺失'
|
||
if grep -Eqi 'SELECT[^;]*(key|access_token|password)' "$TMP_DIR/newapi.sql"; then
|
||
fail 'new-api 固定查询选择了敏感凭据'
|
||
fi
|
||
grep -q "SIGNER_PRIVATE_KEY='/root/dogfood-r1-signer/r1-ed25519-private.pem'" "$SCRIPT" || \
|
||
fail '生产 signer 私钥路径不是固定值'
|
||
grep -q "STAGING_RELEASES_ROOT='/root/game-staging/releases'" "$SCRIPT" || \
|
||
fail '生产 staging releases 根不是固定值'
|
||
grep -q 'realpath.*signer_key' "$SCRIPT" || fail '生产 signer 私钥未校验 canonical 路径'
|
||
grep -q "stat -c '%u'.*signer_key" "$SCRIPT" || fail '生产 signer 私钥未校验 root 所有权'
|
||
|
||
python3 - "$report" <<'PY'
|
||
import json
|
||
import sys
|
||
|
||
report = json.load(open(sys.argv[1], encoding="utf-8"))
|
||
assert report["schema"] == "dogfood-account-database-report/2"
|
||
assert report["issuer"] == "r1-mini-infra"
|
||
assert report["queryId"] == "dogfood-account-readiness-v2"
|
||
assert report["querySchema"] == "dogfood-account-readiness-row/2"
|
||
assert report["rcCommit"] == "1" * 40
|
||
assert report["activationRunId"] == "20260723T010203Z"
|
||
assert report["activationStatus"] == "PASS"
|
||
assert report["activationCompletedAt"] == "2026-07-23T01:02:03Z"
|
||
assert report["activeReleaseRef"].endswith("/" + "1" * 40)
|
||
assert report["activationEvidenceRef"].endswith(
|
||
"/1111111111111111111111111111111111111111/evidence/activation-20260723T010203Z"
|
||
)
|
||
assert report["activationEvidenceRef"].startswith(report["activeReleaseRef"] + "/evidence/")
|
||
assert len(report["accounts"]) == 40
|
||
assert len(report["reserves"]) == 12
|
||
for item in report["accounts"] + report["reserves"]:
|
||
assert item["newapiUsername"].startswith("neice_")
|
||
assert item["tokenStatus"] == "ENABLED"
|
||
assert item["tokenRemainQuota"] > 0
|
||
assert item["tokenUnlimitedQuota"] is False
|
||
assert item["tokenExpiredTime"] == -1
|
||
serialized = json.dumps(report)
|
||
for secret_name in ("tokenKey", "password", "accessToken", "refreshToken"):
|
||
assert secret_name not in serialized
|
||
PY
|
||
|
||
# 参数只是 expected:与 mini-desktop 当前 active 事实不一致时,签名器必须拒绝。
|
||
assert_refused_without_output forged-commit \
|
||
--activation-run-id "$active_run_id" \
|
||
--rc-commit 2222222222222222222222222222222222222222
|
||
assert_refused_without_output forged-run-id \
|
||
--activation-run-id 20260723T111111Z \
|
||
--rc-commit "$active_commit"
|
||
|
||
# active、RC_STATUS、activation evidence 任一缺失都不能退回使用命令参数。
|
||
rm "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
|
||
assert_refused_without_output missing-active \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
ln -s "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
|
||
|
||
mv "$active_release/RC_STATUS" "$active_release/RC_STATUS.missing"
|
||
assert_refused_without_output missing-rc-status \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
mv "$active_release/RC_STATUS.missing" "$active_release/RC_STATUS"
|
||
|
||
mv "$active_evidence" "$active_evidence.missing"
|
||
assert_refused_without_output missing-activation-evidence \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
mv "$active_evidence.missing" "$active_evidence"
|
||
|
||
# RC_STATUS 与 evidence 自身必须绑定 active commit、规范 run-id 和成功终态。
|
||
cp "$active_release/RC_STATUS" "$TMP_DIR/RC_STATUS.valid"
|
||
sed "s/commit=$active_commit/commit=2222222222222222222222222222222222222222/" \
|
||
"$TMP_DIR/RC_STATUS.valid" >"$active_release/RC_STATUS"
|
||
assert_refused_without_output rc-status-commit-drift \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
cp "$TMP_DIR/RC_STATUS.valid" "$active_release/RC_STATUS"
|
||
|
||
malformed_evidence="$active_release/evidence/activation-manual"
|
||
mv "$active_evidence" "$malformed_evidence"
|
||
sed "s#evidence=$active_evidence#evidence=$malformed_evidence#" \
|
||
"$TMP_DIR/RC_STATUS.valid" >"$active_release/RC_STATUS"
|
||
sed "s#ACTIVATION_EVIDENCE=$active_evidence#ACTIVATION_EVIDENCE=$malformed_evidence#" \
|
||
"$malformed_evidence/activation.log" >"$TMP_DIR/activation.log.malformed"
|
||
mv "$TMP_DIR/activation.log.malformed" "$malformed_evidence/activation.log"
|
||
chmod 600 "$malformed_evidence/activation.log"
|
||
assert_refused_without_output malformed-evidence-run-id \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
mv "$malformed_evidence" "$active_evidence"
|
||
cp "$TMP_DIR/RC_STATUS.valid" "$active_release/RC_STATUS"
|
||
cat >"$active_evidence/activation.log" <<EOF
|
||
[2026-07-23T01:02:03Z] ACTIVATION FAIL rc=1
|
||
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
|
||
EOF
|
||
chmod 600 "$active_evidence/activation.log"
|
||
assert_refused_without_output failed-activation-terminal \
|
||
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
|
||
cat >"$active_evidence/activation.log" <<EOF
|
||
[2026-07-23T01:02:03Z] RC_ACTIVATION_PASS commit=$active_commit active=$active_release
|
||
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
|
||
EOF
|
||
chmod 600 "$active_evidence/activation.log"
|
||
|
||
# 数据库采集前后必须观测同一 active release,防止切版窗口产生错绑报告。
|
||
drift_commit=2222222222222222222222222222222222222222
|
||
drift_run_id=20260723T020304Z
|
||
drift_release="$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/$drift_commit"
|
||
drift_evidence="$drift_release/evidence/activation-$drift_run_id"
|
||
mkdir -p "$drift_evidence"
|
||
printf 'active commit=%s at=2026-07-23T02:03:04Z evidence=%s\n' \
|
||
"$drift_commit" "$drift_evidence" >"$drift_release/RC_STATUS"
|
||
cat >"$drift_evidence/activation.log" <<EOF
|
||
[2026-07-23T02:03:04Z] RC_ACTIVATION_PASS commit=$drift_commit active=$drift_release
|
||
[2026-07-23T02:03:04Z] ACTIVATION_EVIDENCE=$drift_evidence
|
||
EOF
|
||
chmod 600 "$drift_release/RC_STATUS" "$drift_evidence/activation.log"
|
||
reset_state_probe
|
||
export DOGFOOD_TEST_DRIFT_TARGET="$drift_release"
|
||
if "$SCRIPT" --activation-run-id "$active_run_id" --rc-commit "$active_commit" \
|
||
--output "$TMP_DIR/pointer-drift.json" >/dev/null 2>&1; then
|
||
fail '采集期间 active 指针漂移仍被签发'
|
||
fi
|
||
[ ! -e "$TMP_DIR/pointer-drift.json" ] && [ ! -e "$TMP_DIR/pointer-drift.json.sig" ] || \
|
||
fail '指针漂移失败后遗留半成品'
|
||
ln -sfn "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
|
||
unset DOGFOOD_TEST_DRIFT_TARGET
|
||
|
||
# 精确规模是签发前置条件,39 个 CLAIMED 账号必须失败且不得留下半成品。
|
||
head -n 51 "$TMP_DIR/mysql.tsv" >"$TMP_DIR/incomplete.tsv"
|
||
export DOGFOOD_TEST_MYSQL_ROWS="$TMP_DIR/incomplete.tsv"
|
||
if "$SCRIPT" --activation-run-id "$active_run_id" \
|
||
--rc-commit "$active_commit" \
|
||
--output "$TMP_DIR/incomplete.json" >/dev/null 2>&1; then
|
||
fail '不完整 40+12 数据仍被签发'
|
||
fi
|
||
[ ! -e "$TMP_DIR/incomplete.json" ] && [ ! -e "$TMP_DIR/incomplete.json.sig" ] || fail '失败后遗留半成品'
|
||
|
||
# new-api 权威状态异常、ID 关联漂移或一名用户匹配多行时都不得签发。
|
||
for mutation in disabled zero id-drift multi-row; do
|
||
case "$mutation" in
|
||
disabled)
|
||
awk -F'|' 'BEGIN{OFS="|"} NR==1{$5=0} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
|
||
;;
|
||
zero)
|
||
awk -F'|' 'BEGIN{OFS="|"} NR==1{$8=0} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
|
||
;;
|
||
id-drift)
|
||
awk -F'|' 'BEGIN{OFS="|"} NR==1{$4=999999} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
|
||
;;
|
||
multi-row)
|
||
cp "$TMP_DIR/newapi.tsv" "$TMP_DIR/newapi-$mutation.tsv"
|
||
head -n 1 "$TMP_DIR/newapi.tsv" >>"$TMP_DIR/newapi-$mutation.tsv"
|
||
;;
|
||
esac
|
||
export DOGFOOD_TEST_NEWAPI_ROWS="$TMP_DIR/newapi-$mutation.tsv"
|
||
if "$SCRIPT" --activation-run-id "$active_run_id" \
|
||
--rc-commit "$active_commit" \
|
||
--output "$TMP_DIR/$mutation.json" >/dev/null 2>&1; then
|
||
fail "new-api 异常仍被签发 mutation=$mutation"
|
||
fi
|
||
[ ! -e "$TMP_DIR/$mutation.json" ] && [ ! -e "$TMP_DIR/$mutation.json.sig" ] || \
|
||
fail "new-api 异常后遗留半成品 mutation=$mutation"
|
||
done
|
||
|
||
printf 'TEST_PASS 账号报告固定 SSH/MySQL、40+12、签名、权限与脱敏校验通过\n'
|