你以为 GitHub Actions 的 Secrets 就是一个 Key-Value 配置?发布环境写错过一次就懂了
刚接手一个前后端分离项目的时候,我发现前端的 CI/CD 配置里,所有环境的 AWS 密钥都存在同一个 repository secret 里——生产、预发、测试混在一起,只靠 workflow 文件里一个 if: github.ref == ‘refs/heads/main’ 做隔离。后来有一次 merge conflict 手动解决时把密钥轮换了,没注意改环境判断,生产直接跑出了预发代码。
这个故事说明一件事:GitHub Actions 的 Secrets 不只是一个”存敏感值的地方”,它是权限隔离的第一道门。配置方式不同,安全的底裤完全不同。
三层存储,你用的是哪层
GitHub Actions Secrets 分三个存储层级,每个层级的可见性和访问控制都不一样。
Repository Secrets:只对当前仓库有效,适合数据库密码、项目专用的第三方 token。优点是隔离清晰,缺点是多个仓库要用同一套密钥时,每仓库都要单独维护一份。
Organization Secrets:可以跨多个指定仓库共享,适合团队通用的云服务凭证。配置时可以选择”所有仓库”或者”指定仓库白名单”,变更只需要改一处。缺点是如果某个仓库被攻破,同一 org 下的其他仓库的密钥理论上也会面临更大风险面。
Environment Secrets:最精细的一层,绑定到特定 GitHub Environment。可以在仓库 Settings → Environments 里创建环境(比如 production、staging),每个环境有独立的 secrets,且可以配置”保护规则”——比如必须要有特定审批者批准才能触发 workflow,或者限制可以运行 workflow 的分支。生产密钥存在这里,workflow 文件里用 environment: production 声明,密钥只会在满足保护规则时注入。
一个容易踩的坑:Secrets 写在 shell 命令参数里
最常见的 Secrets 暴露事故,不是密钥丢了,而是日志把值打印出来了。比如这样:
- name: Configure AWS
run: aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
这个写法在大多数情况下能工作,但一旦 AWS_ACCESS_KEY_ID 里有特殊字符,或者命令执行失败导致错误信息被打印,日志里就会看到密钥值。GitHub 会尝试做日志净化(::add-mask::),但净化依赖精确匹配,特殊字符转义之后就不匹配了。
正确写法是通过环境变量传递:
- name: Configure AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
环境变量在日志里会显示为 ***,GitHub 的净化机制对环境变量比对 shell 参数的处理更可靠。
Environment 保护规则不只是”审批”
很多人知道 Environment 可以设置 required reviewers,但保护规则的价值远不止审批这一项。
你可以限制哪个分支的 workflow 可以访问某个 Environment——比如只有 main 分支的 workflow 才能读取 production 环境的密钥,其他分支跑 CI 时这些密钥根本不存在于运行环境里。
你还可以为每个 Environment 设置等待时间(wait timer),防止在某些发布窗口(比如深夜、节假日)意外触发部署 workflow。
另外,如果你用了 self-hosted runners,给不同的 Environment 绑定不同的 runner 组,也能物理隔离不同环境的执行机器,防止跨环境信息残留。
审计日志能看见什么
GitHub Enterprise Cloud 的审计日志里记录了 Actions Secrets 的所有变更操作,包括:
org.create_actions_secret / org.update_actions_secret / org.remove_actions_secret(组织级)
repo.create_actions_secret / repo.update_actions_secret / repo.remove_actions_secret(仓库级)
但需要注意:日志里记录的是操作行为,不是密钥值本身——GitHub 存储密钥用的是 libsodium sealed box,客户端加密,服务器端不掌握明文。所以一旦发现异常变更,第一时间应该是轮换密钥而不是去日志里找泄露值。
如果你用的是 GitHub Enterprise Server,可以通过 REST API 主动导出审计日志来做合规留存,默认保留 30 天,生产环境建议按合规要求配置到 90 天。
下一步
打开你的仓库 Settings → Environments,看一下现在有哪些 Environment,它们各自绑定了哪些 secrets,保护规则是什么。如果发现所有环境共用一套密钥,这是最容易改进的第一步——按环境拆分密钥,加环境保护规则,即使现在不用审批功能,至少保证生产密钥和其他环境物理隔离。
评论区
登录后可评论。