还记得刚入行那会儿,我把数据库密码直接写在代码里。后来项目被审计,领导指着那行明文密码问我知不知道什么叫安全。当时脸都红了。这种事儿干过的人不在少数,甚至有些人至今还在这么做。
把密钥直接写死在代码里,意味着你把整个系统的安全都交到了这份代码能接触到的每一个人手里。一旦代码泄露,密钥就全完蛋。更麻烦的是,不同环境要用不同的配置,每次部署都要改代码,改完还有可能忘记改回来。
正确做法是把配置和代码分开。配置放在外部,程序启动时去读取。这样代码可以公开,密钥却掌握在少数人手里。
环境变量是最简单也最常用的方式。你的操作系统本身就带着环境变量这个功能。在Python里用os.getenv就能取到值。
import os db_password = os.getenv('DB_PASSWORD')
if not db_password:
raise ValueError('DB_PASSWORD未设置')
部署的时候,在服务器上设置好环境变量就行。Docker里用-e参数,Kubernetes里用env字段,Linux服务器上往.bashrc里写export。这样做的好处是密钥永远不会出现在代码仓库里。
还有一种方法是用配置文件,但配置文件不能进版本库。比如大家都用.env文件,然后在.gitignore里把它忽略掉。Python里有个python-dotenv库,可以帮你读取这个文件。
from dotenv import load_dotenv import os
load_dotenv()
db_password = os.getenv('DB_PASSWORD')
.env文件长这样:
DB_HOST=localhost DB_PORT=5432
DB_PASSWORD=your_actual_password
每个开发者在本地自己创建这个文件。线上服务器由运维人员手动维护。谁也不会看到别人的密钥。
如果要管理大量配置,可以用专门的配置中心。比如Consul、etcd或者后面云厂商提供的配置管理服务。Python程序启动时从这些中心拉取配置。配置变了,程序能感知到。
import consul c = consul.Consul(host='localhost', port=8500)
index, data = c.kv.get('myapp/database/password')
db_password = data['Value'].decode('utf-8')
使用配置中心的好处是配置变更不需要重新部署。改一下配置,程序自动就拿到了新值。
使用密钥管理服务是最保险的方式。AWS的Secrets Manager、Azure Key Vault、Google Secret Manager都提供了专门的密钥存储。密钥存在云端,程序通过SDK取用。密钥轮转由服务商负责,你只需要指定轮转周期。
import boto3 from botocore.exceptions import ClientError
def get_secret(secret_name):
session = boto3.session.Session()
client = session.client('secretsmanager')
try:
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
except ClientError as e:
raise e
密钥管理器还支持细粒度的权限控制。只有特定的机器、特定的人才能读取特定密钥。
不管用哪种方式,核心原则就一个:密钥不要出现在代码里。代码可以提交到GitHub,可以分享给别人看,可以存入CI/CD系统的版本库。但密钥必须待在它自己的安全空间里。
我们在项目里一般这样组织代码。建一个config模块,里面根据环境写不同的加载逻辑。开发环境读.env,测试环境读环境变量,生产环境读密钥管理器。统一入口,谁调用都方便。
import os class Config:
@classmethod
def get(cls, key, default=None):
优先取环境变量
val = os.getenv(key)
if val:
return val
然后取配置文件
try:
from dotenv import load_dotenv
load_dotenv()
return os.getenv(key, default)
except ImportError:
return default
这样做的好处很明显。代码里只写Config.get('DB_PASSWORD'),具体密码从哪来由配置决定。测试的时候可以轻松地替换成测试环境的值。
有个细节很多人会忽略。日志里不要把密钥打出来。有时候调试问题,顺手打印个配置信息,密钥就泄漏了。建议在生产环境中把配置打印功能直接关掉,或者把敏感字段自动替换成。
还有一个坑是硬编码的默认值。有人写os.getenv('DB_PASSWORD', 'default_password'),这个默认值就成了安全黑洞。不设置环境变量时,程序就用的默认密码,有人知道了就能直接登录。默认值只应该有None,然后程序检查到None就报错退出。
团队里可以约定一个规则。每次代码审查,如果看到明文密钥直接打回。建议给项目配置一个静态检查工具,比如truffleHog或git-secrets,它们能自动检测代码中是否含有类似密钥的字符串。推代码之前先跑一遍检查。
说了这么多,其实就是一件事。把密钥和代码分开已经是行业标准了。没这么做的项目,迟早要出问题。现在就动手,把代码里的密钥抽出来放到该放的地方去。别等到被审计的那天再改。