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,即使不小心提交了,也只会暴露模板结构,不会泄露密钥。
4.3 环境变量 vs 配置文件:怎么选?
你可能会问:到底用环境变量还是配置文件?
我个人的判断标准很简单:
- 环境变量 适合:数量少、不经常变动、启动时必须就绪的 Secrets。
- 配置文件 适合:结构复杂、与环境相关的参数较多、需要团队协作维护的场景。
实际上,两者不冲突,反而适合搭配使用。
我通常的做法是:配置文件只声明“需要哪些变量”,真正的值从环境变量或专门的 Secrets 存储(如 Vault、AWS Secrets Manager)中读取。
说白了,配置文件管结构,环境变量管值。
4.4 实战:从配置文件到环境变量的最佳实践
下面是我总结的一套最佳实践,你可以直接用到项目里:
- 永远不要硬编码密钥——不管是代码还是配置文件。
- 使用模板文件(如
config.example.yaml),并提交到 Git。 - 真实的配置文件(如
config.prod.yaml) 必须加入.gitignore。 - 从环境变量读取 所有敏感值,即使配置文件里也要用占位符。
- 启动时做验证:一启动就检查所有必要的 Secrets 是否就绪,不要等到运行时才报错。
- 限制文件权限:配置文件建议设为
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 与代码分离。
代码负责逻辑,配置文件负责结构,环境变量(或专门的 Secrets 管理工具)负责值。
只要这条线划得清楚,你的安全性就已经比大部分项目高出一截了。