Files
vps-manager/scripts/recover_master_key.py
T
gouki d711ad5827 feat(vault): 凭据库(密码+2FA 动态码)与 MASTER_KEY 密钥托管
credentials 表为登录凭据唯一事实源(站点×登录方式,含 oauth/2FA);账号密码读写重定向凭据层并幂等迁移历史数据;TOTP 按 RFC6238 零依赖自实现,绑定需当前动态码校验;Key Escrow 防 MASTER_KEY 遗失;前端新增凭据库视图与账号 2FA 联动。64 pytest + 16 浏览器端到端验证通过。
2026-09-05 15:54:41 +00:00

93 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""MASTER_KEY 丢失恢复(一次性脚本,日常运行不接触)
用法:
RESTORE_KEY=<钥匙> .venv/bin/python scripts/recover_master_key.py # 仅打印找回的 MASTER_KEY
RESTORE_KEY=<钥匙> .venv/bin/python scripts/recover_master_key.py --write # 备份 .env 后回写 MASTER_KEY
也支持 --key 传参(会残留 shell 历史,建议用环境变量)。
恢复后重启服务即可:全部密文零迁移、照常解密。
"""
import argparse
import os
import re
import shutil
import sys
from pathlib import Path
# 确保能导入 app 包(脚本位于 scripts/ 子目录)
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.core import crypto
BASE_DIR = Path(__file__).resolve().parent.parent
DEFAULT_ESCROW = BASE_DIR / "data" / "master_key.escrow"
DEFAULT_ENV = BASE_DIR / ".env"
def _write_env(master_key: str, env_path: Path) -> Path | None:
"""备份 .env 后回写 MASTER_KEY 行(不存在则追加),返回备份路径"""
backup: Path | None = None
if env_path.exists():
backup = env_path.parent / (env_path.name + ".bak-pre-recover")
shutil.copy2(env_path, backup)
text = env_path.read_text(encoding="utf-8")
if re.search(r"^MASTER_KEY=.*$", text, flags=re.MULTILINE):
text = re.sub(
r"^MASTER_KEY=.*$", f"MASTER_KEY={master_key}", text, count=1, flags=re.MULTILINE
)
else:
text = text.rstrip("\n") + f"\nMASTER_KEY={master_key}\n"
env_path.write_text(text, encoding="utf-8")
else:
env_path.write_text(f"MASTER_KEY={master_key}\n", encoding="utf-8")
return backup
def main() -> int:
parser = argparse.ArgumentParser(description="凭 RESTORE_KEY 找回 MASTER_KEY")
parser.add_argument(
"--key", help="RESTORE_KEY(建议改用环境变量 RESTORE_KEY,避免 shell 历史残留)"
)
parser.add_argument(
"--write", action="store_true", help="找回后直接回写 .env(先备份为 .env.bak-pre-recover"
)
parser.add_argument(
"--escrow", default=str(DEFAULT_ESCROW),
help="escrow 文件路径(默认 data/master_key.escrow",
)
parser.add_argument("--env", default=str(DEFAULT_ENV), help=".env 路径(默认项目根 .env")
args = parser.parse_args()
restore_key = args.key or os.environ.get("RESTORE_KEY")
if not restore_key:
print("[恢复] 缺少 RESTORE_KEY--key 传入或设置环境变量 RESTORE_KEY", file=sys.stderr)
return 2
escrow_path = Path(args.escrow)
if not escrow_path.exists():
print(f"[恢复] 未找到托管文件 {escrow_path}(尚未建立托管?)", file=sys.stderr)
return 2
try:
master_key = crypto.recover_master_key(
restore_key.strip(), escrow_path.read_text(encoding="utf-8").strip()
)
except ValueError as e:
print(f"[恢复] 失败:{e}", file=sys.stderr)
return 1
if args.write:
backup = _write_env(master_key, Path(args.env))
where = f"(原文件已备份至 {backup.name}" if backup else ""
print(f"[恢复] MASTER_KEY 已找回并回写 {args.env}{where}")
print("[恢复] 请重启服务使 key 生效:sudo systemctl restart vps-manager")
else:
print("[恢复] MASTER_KEY 已找回(未回写 .env):")
print(f" MASTER_KEY={master_key}")
print("[恢复] 确认无误后加 --write 自动回写 .env,或手动粘贴到 .env 的 MASTER_KEY= 行")
return 0
if __name__ == "__main__":
sys.exit(main())