密钥与环境配置:Vercel、Cloudflare 环境变量与密钥轮换实战

密钥与环境配置深度实战:构建时/运行时/绑定的环境变量分类、Vercel 与 Cloudflare 的环境变量与 Secret 管理、多环境与团队治理、密钥轮换与失效策略、加密存储与引用方式,帮你在 Serverless 平台安全地管理配置与密钥。

一、引言

密钥管理是 Serverless 应用最容易「先松后紧」的环节:起步时把 API Key 写进 .env 就算完事,等密钥泄露、误提交到 GitHub、或者离职同事还握着生产密钥时,才意识到早就该建立规范。

本文讲透 Serverless 平台的配置体系:环境变量的三种形态(构建时 / 运行时 / 绑定)、Vercel 与 Cloudflare 各自的 Secret 管理、多环境与团队治理、密钥轮换与失效策略,最后给出「从裸 .env 到安全密钥体系」的迁移路径。

二、先分清楚环境变量的三种形态

2.1 构建时 vs 运行时 vs 绑定

形态注入时机典型场景风险
构建时构建阶段注入,打进产物构建 URL、SSG 页面数据进产物 = 泄露面变大
运行时函数执行时注入API Key、数据库地址不可进客户端代码
绑定平台对象直接绑定KV / R2 / D1 / Secret最安全,代码无密钥

2.2 三条铁律

铁律一:客户端代码里不能有运行时密钥
  浏览器能拿到的一切都是公开的——NEXT_PUBLIC_* 只放「本来就公开」的东西

铁律二:密钥不进产物
  构建时注入密钥到 SSG 产物 = 把密钥写进每个访问者都能下的文件里

铁律三:能绑定就不放变量
  Workers 的 KV/R2/D1 绑定、Secret 绑定,比放环境变量更安全
// 反模式:把密钥打进构建产物(SSG 页面里被所有人看到)
export async function getStaticProps() {
  const key = process.env.STRIPE_SECRET_KEY   // 会进 HTML/JS 产物!
  return { props: { key } }
}

// 正解:运行时在服务端用,绝不进 props
export default async function handler(req, res) {
  const charge = await stripe.paymentIntents.create({
    amount: 100,
    currency: 'usd',
    confirm: true,
  }, { apiKey: process.env.STRIPE_SECRET_KEY })  // 只在服务端运行时读取
  res.json({ id: charge.id })
}

三、Vercel 的环境变量管理

3.1 按环境拆分

Vercel 把环境变量按 Environment 分组:Production / Preview / Development。

# 通过 CLI 管理
vercel env add DATABASE_URL production
vercel env add DATABASE_URL preview
vercel env add DATABASE_URL development

# 查看已有变量
vercel env ls

# 删除(离职/泄露时)
vercel env rm DATABASE_URL production

3.2 在项目里安全引用

// 服务端运行时读取:安全
const dbUrl = process.env.DATABASE_URL

// 客户端公开变量:前缀约定,只放公开数据
const apiBase = process.env.NEXT_PUBLIC_API_BASE

3.3 Vercel 的 Secret 能力

Vercel 用「环境变量 = 引用 + 加密存储」的方式管理密钥,vercel env pull 可以把远程变量拉到本地 .env:

# 把线上配置拉到本地(仅开发环境,注意别提交)
vercel env pull .env.development.local

# 检查 .gitignore:本地 env 必须忽略
# .env*
# !.env.example

心法:.env.example 只放变量名与占位说明(无真实值),是团队「配置即文档」的最小实践——新成员照着它就能跑起来。

四、Cloudflare:Secret 与绑定

4.1 wrangler 管理 Secrets

# 普通环境变量(非敏感,明文可见)
npx wrangler secret put API_KEY
# 交互式输入,或
echo "sk-xxx" | npx wrangler secret put API_KEY --env production
# 列出 / 删除
npx wrangler secrets list
npx wrangler secret delete API_KEY --env production

4.2 在代码里读取

// Workers 里通过 env 参数访问绑定
export default {
  async fetch(request, env: { API_KEY: string; KV: KVNamespace }) {
    // 运行时才拿到密钥
    return Response.json({ masked: env.API_KEY.slice(0, 4) + '****' })
  },
}

4.3 本地开发体验

# 本地开发:用 .dev.vars 注入开发密钥(.gitignore 必须忽略)
# .dev.vars
# API_KEY=sk-local-only
npx wrangler dev
安全要点:
- .dev.vars 绝不进 git(wrangler 默认模板已忽略)
- 生产 Secret 用 wrangler secret 管理,不出现在代码里
- 绑定(KV/R2/D1)本身不带密钥,用 Secret 存凭证更稳

五、多环境与团队治理

5.1 环境变量命名与作用域

命名规范:<APP>_<资源>_<用途>
  例:STRIPE_SECRET_KEY、SENTRY_DSN、DATABASE_URL

作用域规范:
  公开可进客户端 → NEXT_PUBLIC_ 前缀
  服务端私有 → 无前缀,只在运行时读取
  构建元数据 → 明确标注,禁止含密钥

5.2 团队权限最小化

谁能看值?谁能改值?谁能删?
  - 开发者:可看预览环境变量
  - 核心维护者:可改生产密钥
  - 运维/安全:负责轮换与审计

平台支持:
  Vercel:Member 角色按环境授权
  Cloudflare:Workers 与账号级权限分离

5.3 配置审计与变更记录

每次密钥变更要有记录:谁、何时、为什么
定期审计:哪些环境变量还活着?谁在引用?

心法:密钥是「高权限资产」,管理它的流程要像管理权限一样严格——最小权限 + 变更留痕 + 定期审计。

六、密钥轮换与失效策略

6.1 轮换的触发时机

强制轮换时机:
  - 疑似泄露(误提交到 GitHub、被第三方看到)
  - 相关人员离职
  - 合规要求(PCI / SOC2 / 年度轮换)
  - 服务商安全通告

6.2 无中断轮换流程

轮换步骤(关键:新旧并存 → 切换 → 撤销旧):
  1. 生成新密钥,保存为变量(如 STRIPE_SECRET_KEY_V2)
  2. 部署使用新变量的版本,灰度观察
  3. 确认无报错后,删除旧密钥
  4. 旧密钥不留「宽限」,能删则删
# 示例:GitHub Actions + Vercel CLI 自动化轮换(片段)
vercel env add STRIPE_SECRET_KEY_V2 production
# 部署新版本使用 V2
vercel --prod
# 验证通过后删除旧值
vercel env rm STRIPE_SECRET_KEY production

6.3 轮换失败与回滚

- 新密钥在部分地域/功能失败 → 先回滚变量到旧值
- 因此:旧密钥至少保留一个发布周期再删
- 有「失效保护」的密钥服务(如自动过期)优先选择

细节:真正的密钥服务(Vault、云 KMS)支持「版本 + 过期时间」,轮换可自动化;平台 env 的轮换是「手动换值」,流程上要有人把关。

七、加密存储与引用方式

7.1 密钥该放在哪

优先级(安全度从高到低):
  ① 平台 Secret 管理(Vercel/Cloudflare wrangler secret)——首选
  ② 外部密钥服务(Vault / AWS Secrets Manager)——企业级
  ③ 平台环境变量(明文可见)——只放非敏感配置
  ④ 仓库内 .env(加密且不进 git)——本地开发兜底
  ⑤ 代码里硬编码——永远禁止

7.2 用外部密钥服务统一管理

// 例:从 Vault 拉取密钥(运行时缓存 + 兜底)
let cachedSecrets: Record<string, string> | null = null

async function getSecret(name: string): Promise<string> {
  if (cachedSecrets?.[name]) return cachedSecrets[name]
  const res = await fetch(VAULT_URL + '/v1/secret/data/' + name, {
    headers: { Authorization: 'Bearer ' + process.env.VAULT_TOKEN },
  })
  const data = await res.json()
  cachedSecrets = data.data.data
  return cachedSecrets[name]
}

export default async function handler(req: Request) {
  const stripeKey = await getSecret('stripe/live')   // 按需获取
  return Response.json({ ok: !!stripeKey })
}

7.3 密钥永不落盘的最佳实践

- CI/CD 里用平台的 Secrets(GitHub Actions secrets),不 echo 明文
- 日志/异常信息里对密钥脱敏(只打前 4 位 + ****)
- 密钥禁止进入打包产物、错误堆栈、Sourcemap
- 定期扫描仓库历史,找历史提交里漏网的密钥
// 日志脱敏:异常信息里可能带密钥,统一清洗
function sanitize(str) {
  return str.replace(/(sk-[A-Za-z0-9_]{8})[A-Za-z0-9_]+/g, '$1****')
}
console.error(sanitize(err.message))

八、总结

密钥与环境配置的核心要点:

  1. 分清三种形态:构建时 / 运行时 / 绑定,密钥只进运行时,绝不打进客户端产物。
  2. 能绑定就不放变量:Workers 的 KV/R2/D1 与 Secret 绑定,比裸环境变量安全。
  3. 平台 Secret 优先:Vercel env、wrangler secret 是第一选择,外部 Vault/KMS 是企业级选择。
  4. 多环境隔离:Production / Preview / Development 分开,NEXT_PUBLIC_ 前缀只放公开值。
  5. 团队最小权限:谁看、谁改、谁删,分级授权 + 变更留痕。
  6. 轮换要无中断:新旧并存 → 切换 → 撤销旧,至少保留一个发布周期。
  7. 永不落盘:密钥不进 git、不进产物、不打日志,定期扫历史提交。

配置与密钥是 Serverless 应用的「供水管线」——平时看不见,一出事就是大事。把环境变量、Secret、轮换、审计这套流程制度化,再配合 部署与回滚策略 的发布规范与 全球部署与合规 的数据保护要求,你的配置体系才算真正安全可控。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「tools」更多文章

  1. AI 网关与模型路由:多模型统一入口、fallback、限流与成本控制
  2. Web 安全加固:CSP、HSTS、安全响应头与 XSS 防护实战
  3. 图片与媒体优化:Image CDN、AVIF-WebP 与响应式图片实战