Do not use secrets in environment variables and here’s how to do it better 的阅读笔记
为什么不应该在环境变量中存储敏感信息#
Reason 1: 环境变量中的敏感信息通常管理不善#
泄露后难以更改: 存储在环境变量中的敏感信息一旦被泄露,通常需要重启或重新部署整个应用才能进行更换,并且我们需要知道哪些服务正在使用被泄露的密钥
未定期轮换: 密钥通常需要定期轮换,因为可能早已在你未察觉的情况下泄露,导致安全隐患。而环境变量通常在基础设施部署时被设置好,密钥长期保持不变
难以审计: 环境变量缺乏内置的审计功能。无法追踪谁访问了某个密钥、它上次被修改的时间,或者它在系统中的具体使用情况
未加密存储: 环境变量通常以明文存储,可以通过
/proc/PID/environ
读取出来
Reason 2: SSR 架构下泄露敏感信息#
同一代码库可以同时运行在客户端和服务器端,配置错误可能导致密钥暴露给前端。
Reason 3: .env
文件易泄露#
违反关注点分离原则(Separation of Concerns): 配置 和 密钥 是两种不同的概念,不应该混在一起管理。将敏感信息存入
.env
文件,会导致二者的界限模糊,使得管理和维护变得更加复杂引入安全风险:
.env
文件很容易被意外提交到 Git 仓库
Reason 4: 日志泄露敏感信息#
服务器的错误日志
详细的调试输出
异常崩溃报告
应用程序的日志可能无意中记录环境变量中的敏感信息。被记录到日志后,可能存储在本地磁盘、日志管理系统或日志服务中,而这些存储位置一旦发生数据泄露或未授权访问,敏感信息也会随之暴露。比如 CVE-2019-5483
Reason 5: 子进程默认继承环境变量#
子进程默认继承环境变量,任何存储在环境变量中的密钥都会自动被所有子进程访问,即使它们实际上并不需要这些信息。这种行为违反了安全中的最小权限原则(Principle of Least Privilege)
Reason 6: 环境变量可在进程列表中被查看#
在大多数类 Unix 操作系统(包括 Linux 和 macOS)上,系统上的任何用户都可以查看正在运行的进程的环境变量。这意味着存储在环境变量中的机密可能对系统上的所有用户都可见,而不仅仅是进程的所有者。比如执行 ps eww
或者 ps auxwe
Reason 7: 构建参数(Build Arguments)和 .env
文件泄露到 Docker 镜像#
…
更好的密钥管理方案#
三条原则:
- 始终将应用程序的配置与密钥分离
- 密钥应该由外部资源注入到应用程序中,并且应采用临时且短生命周期的访问方式,或者由应用程序在运行时动态请求。这可以分为以下几种方式:
- 基础设施编排机制(如 Kubernetes sidecar 容器)执行安全的数据获取,并通过临时文件提供给应用程序,在应用程序启动时进行读取,或者将密钥以秘密卷挂载的方式提供给应用容器(这种方法在 Docker Swarm 和 Kubernetes 中较为流行)。另一种选择是通过环境变量提供一次性、短生命周期的密钥(通常称为 Token)。
- 使用外部的密钥管理服务,应用程序首先通过临时的、短生命周期的 Token 进行身份验证,然后在运行时主动从该服务获取密钥。
- 使用云厂商的密钥管理服务,允许应用程序在代码中直接获取密钥,并依赖云基础设施的身份认证与授权(IAM) 来确保安全性。
- 密钥应该通过密钥管理系统(Key Management Service, KMS)进行管理
Secret Zero 问题: 我们需要一个密钥来访问密钥管理服务,而这个初始密钥本身也是一种密钥。
为了解决这个问题,区分存储密钥和提供密钥很重要。如果应用程序需要一个单一的密钥来启动并访问密钥管理服务,那么你可以通过环境变量提供初始密钥,只要这个初始密钥(在许多产品中通常称为 Token,如 Hashicorp 的 Vault)是短生命周期且使用次数有限的。一旦应用程序已经启动并和密钥管理服务验证成功,应用程序后续将使用临时令牌进行通信以获取所需要的密钥,而初始密钥则会被过期消毁