From baa41d370bcce61fb5fa779e6930c3454b7c0924 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Wed, 8 Jul 2026 10:56:01 +0200 Subject: [PATCH] =?UTF-8?q?fix(devops):=20.env.example=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=8F=AF=E7=94=A8=20Fernet=20key=20=E6=8D=A2=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=E5=8D=A0=E4=BD=8D=E7=AC=A6=20+=20=E8=BD=AE?= =?UTF-8?q?=E6=8D=A2=E6=8F=90=E7=A4=BA=EF=BC=88CR-H8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 5 ++-- tests/test_env_example_no_secret.py | 38 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/test_env_example_no_secret.py diff --git a/.env.example b/.env.example index ece2a33..7d867b4 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ DATABASE_URL_SYNC=postgresql+psycopg://writer:writer@localhost:5432/writer APP_ENV=dev LOG_JSON=false # 凭据加密密钥(Fernet, urlsafe-base64 32B)— **必填**:API 启动即校验,缺失/非法则拒绝启动。 -# 下方为开发占位 key(可直接用于本地);生产请换新值: +# ⚠️ 下方为**不可用占位符**,必须替换:`cp .env.example .env` 后用下方命令生成真实 key 填入。 +# 切勿把真实 key 提交进版本库;任何曾经泄露过的 key 必须立即轮换(重新加密已存凭据)。 # uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" -CREDENTIAL_ENC_KEY=cnMpG6QQxJejuDLHTe_S-nq2snoKgXCqWFfsctEHB-4= +CREDENTIAL_ENC_KEY=replace-me-with-generated-fernet-key diff --git a/tests/test_env_example_no_secret.py b/tests/test_env_example_no_secret.py new file mode 100644 index 0000000..b8d92f0 --- /dev/null +++ b/tests/test_env_example_no_secret.py @@ -0,0 +1,38 @@ +"""回归守卫:`.env.example` 不得携带可用的真实密钥(CR-H8)。 + +`.env.example` 是入库模板;若其中的 `CREDENTIAL_ENC_KEY` 是一把真实可用的 +Fernet key,任何照抄 `.env.example` 的环境都会共用同一把泄露过的密钥。此测试 +确保模板里只放**不可用占位符**——照抄到 `.env` 会在启动校验(main._lifespan) +处快速失败,逼迫运维生成真实 key。 +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest +from cryptography.fernet import Fernet + +_ENV_EXAMPLE = Path(__file__).resolve().parent.parent / ".env.example" +_PLACEHOLDER = "replace-me-with-generated-fernet-key" + + +def _credential_enc_key() -> str: + text = _ENV_EXAMPLE.read_text(encoding="utf-8") + match = re.search(r"^CREDENTIAL_ENC_KEY=(.*)$", text, flags=re.MULTILINE) + assert match is not None, ".env.example 缺少 CREDENTIAL_ENC_KEY 行" + return match.group(1).strip() + + +def test_env_example_credential_key_is_non_functional_placeholder() -> None: + value = _credential_enc_key() + assert value, "CREDENTIAL_ENC_KEY 不能为空" + # 真实可用的 Fernet key 会构造成功——那正是要杜绝的泄露面。 + with pytest.raises(ValueError): + Fernet(value.encode()) + + +def test_env_example_credential_key_is_the_expected_placeholder() -> None: + # 更紧的锁:值必须恰是约定占位符,防止未来又塞进别的真实 key。 + assert _credential_enc_key() == _PLACEHOLDER