4. 环境变量与配置文件中的Secrets管理

说起来,密钥到底该放哪儿?这是每个做安全加固的人都绕不开的问题。
我见过不少项目,密钥直接硬编码在代码里,或者丢在一个谁都能读的配置文件中。
嗯,这两种做法都是在给自己挖坑。
今天我们就聊聊环境变量和配置文件这两种常见方案,看看怎么用才算真正“管好”了 Secrets。

4.1 环境变量:最简单的防线

环境变量是我个人非常推荐的第一道防线。
它简单、通用,几乎所有的操作系统和语言都支持。
你不需要引入额外的库,也不需要复杂的配置管理工具。

举个例子,你可以在启动服务前这样设置:

export DB_PASSWORD="s3cr3t!Pass"
export ="sk-abc123xyz"
python app.py

然后在 Python 代码里直接读取:

import os

db_password = os.environ.get("DB_PASSWORD")
 = os.environ.get("")

if not db_password or not :
    raise RuntimeError("缺少必要的环境变量!")
小提示: 我个人习惯在代码中先用 os.environ.get() 读取环境变量,如果变量不存在就抛出一个明确的错误。这样启动时就能快速发现问题,而不是运行时才报错。

但环境变量也有短板。最明显的就是难以管理多个环境(开发、测试、生产)的差异。
而且环境变量里的值,一旦设置了,就全局可见。
我记得有一次,某个同事在终端里 export 了生产环境的 Secret,然后截图发给别人看日志——结果照片里环境变量一览无余,那叫一个尴尬。

4.2 配置文件:更灵活,但风险更高

配置文件通常比环境变量更方便——你可以把不同环境的值写在不同的文件里。
但方便归方便,配置文件的权限管理和版本控制,常常被忽视。

我见过最典型的错误是这样的:

# config.py — 直接提交到 Git 仓库
DB_HOST = "prod-db.example.com"
DB_USER = "admin"
DB_PASSWORD = "P@ssw0rd!"  # 明文,永久留在 Git 历史里

相信我,一旦密钥被提交到 Git 仓库,就算你后来删掉了,它还是会永远留在 .git 的历史记录里。
攻击者只要翻看你的提交历史,就能拿到所有 Secrets。
我在项目中遇到过多次类似情况,最后不得不重置所有密钥,并清理整个 Git 历史——工作量巨大。

正确的做法,是把配置文件分为两类:

文件类型 用途 是否提交到 Git
config.example.yaml 模板文件,只包含占位符 ✅ 必须提交
config.prod.yaml 实际的生产配置,包含真实的 Secrets ❌ 永远不要提交

举个例子,你可以这样写模板文件:

# config.example.yaml
database:
  host: "localhost"
  port: 5432
  user: "your_user"
  password: "${DB_PASSWORD}"     # 从环境变量读取,而非硬编码

api:
  key: "${}"
  endpoint: "https://api.example.com/v1"

然后在加载配置时,主动去读取环境变量来填充这些占位符。
这样一来,配置文件本身没有真正的 Secrets,即使不小心提交了,也只会暴露模板结构,不会泄露密钥。

⚠ 特别注意: 我曾经做过一个测试,从一个被遗忘的 WordPress 备份文件中,找到了整整 47 组有效的数据库密码和 。这些文件被备份到公开的 S3 存储桶里,任何人都能访问。配置文件的备份,同样需要严格加密和权限控制。

4.3 环境变量 vs 配置文件:怎么选?

你可能会问:到底用环境变量还是配置文件?
我个人的判断标准很简单:

  • 环境变量 适合:数量少、不经常变动、启动时必须就绪的 Secrets。
  • 配置文件 适合:结构复杂、与环境相关的参数较多、需要团队协作维护的场景。

实际上,两者不冲突,反而适合搭配使用。
我通常的做法是:配置文件只声明“需要哪些变量”,真正的值从环境变量或专门的 Secrets 存储(如 Vault、AWS Secrets Manager)中读取。
说白了,配置文件管结构,环境变量管值。

4.4 实战:从配置文件到环境变量的最佳实践

下面是我总结的一套最佳实践,你可以直接用到项目里:

  1. 永远不要硬编码密钥——不管是代码还是配置文件。
  2. 使用模板文件(如 config.example.yaml),并提交到 Git。
  3. 真实的配置文件(如 config.prod.yaml) 必须加入 .gitignore。
  4. 从环境变量读取 所有敏感值,即使配置文件里也要用占位符。
  5. 启动时做验证:一启动就检查所有必要的 Secrets 是否就绪,不要等到运行时才报错。
  6. 限制文件权限:配置文件建议设为 600,只允许 Owner 读写。

举个例子,一个标准的加载流程可以是:

import os
import json

def load_config():
    # 先从环境变量读取 Secret
    db_password = os.environ.get("DB_PASSWORD")
     = os.environ.get("")

    if not db_password or not :
        raise RuntimeError("数据库密码或未设置!")

    # 加载配置文件模板(不含真实值)
    with open("config.json", "r") as f:
        config = json.load(f)

    # 用环境变量填充占位符
    config["database"]["password"] = db_password
    config["api"]["key"] = 

    return config
核心思路: 配置文件只负责“组织数据”,环境变量负责“携带秘密”。两者分离,任何一个单独泄露都不至于导致全部密钥暴露。

4.5 为什么要画好这条线?

下图直观地展示了从“代码 & 配置文件”到“环境变量 & 外部存储”的迁移路径。这也是我建议你尽快完成的步骤。

从代码到环境变量:Secrets 管理迁移流程 代码硬编码密码 配置文件提交到 Git 备份文件未加密 迁移到环境变量 环境变量注入 .gitignore 保护 加密存储 ⚠ 目标:所有 Secrets 都不落入代码、配置文件、备份文件

说白了,无论是环境变量还是配置文件,归根结底都是一个原则:Secrets 与代码分离。
代码负责逻辑,配置文件负责结构,环境变量(或专门的 Secrets 管理工具)负责值。
只要这条线划得清楚,你的安全性就已经比大部分项目高出一截了。