mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-15 21:35:34 +00:00
security(deploy): 生产凭据轮换 + 镜像不再携带口令
轮换(MySQL 应用用户 / MySQL root / Redis,实测旧值被拒) - 旧 MySQL app 口令 → ERROR 1045 (28000) Access denied;旧 root 同被拒 - 旧 Redis 口令 → AUTH failed: WRONGPASS - 新口令:容器内直连成功;应用健康 db_status=1 / redis_status=1;四端 200;容器全 healthy - .env 保持 600,轮换前备份 .env.bak-rotate-* 镜像卫生(审计 B3 根治) - deploy-artifacts.sh 构建时剔除 DATABASE_PASSWORD / REDIS_PASSWORD / SECRET_KEY / DATA_ENCRYPTION_KEY / DATA_ENCRYPTION_OLD_KEYS / MYSQL_* 口令键, 仅保留主机、端口等非敏感项;口令全部由 compose 运行时注入 - 重建后镜像内 /home/env/.env.prod 口令类键命中 0 条;容器 env 仍持有 4 个密钥且应用正常 - 本地 backend/env/.env.prod 中泄露值清空并 chmod 600 文档 - DEPLOYMENT.md 新增「凭据轮换」标准步骤(含 mysql/redis 健康检查需一并重建、 docker exec -i 会吞脚本 stdin 的排查提示) - AUDIT-REPORT.md 记录公开仓库历史暴露面与待用户侧吊销项(OpenAI Key) 说明:仓库为 public,历史中真实出现过 OpenAI Key 与旧口令;旧口令本轮已失效, OpenAI Key 需到控制台吊销(第三方无法代办)。是否用 git-filter-repo 清洗历史见报告 §10。
This commit is contained in:
+11
-2
@@ -146,8 +146,9 @@ deploy_backend() {
|
||||
rm -rf /tmp/fa-ctx/requirements && mkdir -p /tmp/fa-ctx/requirements
|
||||
cp -R backend/requirements/. /tmp/fa-ctx/requirements/
|
||||
|
||||
# 镜像内的 .env.prod 只是兜底:数据库/Redis 真实地址由 compose 运行时注入。
|
||||
# 这里抹掉本地私网地址与所有密钥,避免把开发凭据烤进镜像。
|
||||
# 镜像内的 .env.prod 只是兜底:数据库/Redis 真实地址与口令由 compose 运行时注入。
|
||||
# 这里抹掉本地私网地址与所有密钥/口令,避免把任何凭据烤进镜像层
|
||||
# (审计 B3:旧镜像里曾烘进真实 DATABASE_PASSWORD / REDIS_PASSWORD)。
|
||||
python3 - "$db" <<'PY'
|
||||
import re, pathlib, sys
|
||||
db = sys.argv[1]
|
||||
@@ -156,6 +157,12 @@ force = {"DATABASE_HOST": "localhost", "REDIS_HOST": "localhost"}
|
||||
if db == "postgres":
|
||||
force.update({"DATABASE_TYPE": "postgres", "DATABASE_PORT": "5432", "DATABASE_USER": "postgres"})
|
||||
drop_prefixes = ("OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_MODEL")
|
||||
# 口令类:运行时全部由 compose 注入,镜像内保留会成为泄露面
|
||||
drop_keys = {
|
||||
"DATABASE_PASSWORD", "REDIS_PASSWORD",
|
||||
"SECRET_KEY", "DATA_ENCRYPTION_KEY", "DATA_ENCRYPTION_OLD_KEYS",
|
||||
"MYSQL_PASSWORD", "MYSQL_ROOT_PASSWORD",
|
||||
}
|
||||
out = []
|
||||
for line in p.read_text().splitlines():
|
||||
m = re.match(r"^(\s*)([A-Z_]+)(\s*=\s*)(.*)$", line)
|
||||
@@ -163,6 +170,8 @@ for line in p.read_text().splitlines():
|
||||
out.append(f"{m.group(1)}{m.group(2)}{m.group(3)}{force[m.group(2)]}")
|
||||
elif m and m.group(2).startswith(drop_prefixes):
|
||||
continue
|
||||
elif m and m.group(2) in drop_keys:
|
||||
continue
|
||||
else:
|
||||
out.append(line)
|
||||
p.write_text("\n".join(out) + "\n")
|
||||
|
||||
Reference in New Issue
Block a user