后端面试题:如何安全设计 GitHub Actions 可复用工作流?
题干与适用场景
团队把构建、测试和发布流程抽成跨仓库可复用工作流。请设计调用方与被调用方之间的输入、输出、密钥、GITHUB_TOKEN 权限和版本引用契约,并说明如何防止权限扩大、供应链漂移和上下文误用。
面试官考察点
- 是否理解可复用工作流是在 job 级别调用,而非普通 step。
- 是否能说明调用方传入的
GITHUB_TOKEN权限只能被被调用方降级,不能升级。 - 是否能最小化
secrets、环境变量和github上下文暴露面。 - 是否能处理嵌套层数、调用上限、版本固定和组织策略。
回答前需要澄清的问题
- 工作流跨仓库、跨组织还是同仓库复用?可见性和策略允许哪些来源?
- 被调用流程只构建制品,还是拥有部署、发布或写回仓库的权限?
- 密钥是逐项传递、组织级继承,还是需要环境保护规则?
- 版本更新要追踪分支、发布标签还是不可变提交引用?
30 秒回答框架
我会先定义一个窄接口:workflow_call 声明类型化输入、逐项 secrets 和必要输出;调用 job 显式设置最小 permissions,被调用工作流再次收紧权限。发布工作流与构建工作流分离,敏感环境由环境保护规则控制。引用使用经过审查的提交或受控标签,并限制组织允许的 actions 与 reusable workflows。最后用审计日志、失败重跑和权限回归测试验证契约。
分步骤深入解答
1. 可复用工作流的调用边界
可复用工作流由 workflow_call 触发,调用方在一个 job 上使用 uses。调用 job 只能使用文档允许的键,例如 with、secrets、permissions、strategy 和 needs;不能把它当作普通 step 注入任意命令。
2. 输入、输出和类型约束
在被调用工作流中声明必填输入、默认值和类型,让调用方契约在解析阶段失败。输出只暴露发布所需的制品摘要、版本或结果状态,不返回令牌、完整日志或内部路径。
3. 密钥逐项传递
优先使用显式 secrets 映射,只把一个部署动作需要的密钥传入。secrets: inherit 会传递调用方可访问的全部密钥,适合边界明确的同组织场景,却会扩大审计和泄露面,跨信任域时应避免。
4. GITHUB_TOKEN 权限降级
调用 job 应显式声明 permissions,例如构建只读代码和写入制品。GitHub 文档规定,被调用工作流继承的 GITHUB_TOKEN 权限只能相同或更严格,不能由被调用方提升。因而高权限动作必须由调用方显式授予,并在代码评审中记录理由。
5. github 上下文归属
被调用工作流中的 github 上下文关联调用方工作流。不要假设被调用仓库的分支、事件或权限自动替换了调用方信息;需要来源仓库、提交和触发者时,显式传入并在日志中脱敏。
6. 引用和供应链漂移
工作流可引用分支、标签或提交。分支会随时间移动,标签也可能被重新指向;生产路径应使用经过审核的不可变提交,或由组织策略限制允许的引用。GitHub 的 actions 策略还可要求 action 使用完整提交 SHA,并限制可用来源。
7. 嵌套、矩阵和并发
嵌套可复用工作流最多十层,单个工作流文件最多连接五十个唯一可复用工作流。矩阵可调用可复用工作流,但要为每个组合设置资源上限、并发组和取消策略,避免重复部署或互相取消。
8. 审计、重跑和回滚
记录调用工作流的仓库、提交、输入摘要、权限声明和制品摘要。重跑全部任务可能重新解析引用;只重跑失败任务可能沿用第一次尝试的提交,因此审计记录必须包含实际解析到的版本。回滚应切换到已验证引用并撤销高权限环境授权。
设计取舍与边界
inherit减少配置,却把密钥边界变成隐式契约;跨组织或多租户平台应显式映射。- 固定提交提高可复现性,但升级需要自动化更新、审查和回滚窗口。
- 把部署封装进通用工作流可减少重复,却可能隐藏环境保护规则;生产部署应保留清晰的审批边界。
- 令牌权限解决 API 授权,不等于运行器上的文件、网络和第三方 action 已可信;仍需隔离不受信代码。
落地计划与证据
- 为
workflow_call编写输入、输出和逐项 secrets 契约,拒绝未声明字段。 - 对每个调用 job 生成权限矩阵,验证被调用方不能升级
GITHUB_TOKEN。 - 将生产引用固定到审核提交,配置组织允许列表并检查 SHA 策略。
- 用假密钥和受限仓库运行拉取请求、跨仓库调用、嵌套和重跑演练。
- 对照 GitHub 的复用工作流、工作流语法和 Actions 设置文档复核实现。
常见误区与追问
误区一:把 reusable workflow 当作 action step
它在 job 级别调用,支持的字段和上下文不同。把 step 语法直接搬过去会导致解析失败或权限假设错误。
误区二:默认使用 secrets.inherit
继承会把调用方可访问的全部密钥交给被调用工作流。除非信任边界和组织范围明确,否则应逐项传递。
误区三:只在被调用方设置高权限
被调用方不能提升调用方传入的令牌权限。高权限必须在调用 job 中显式声明,并通过评审和审计保留理由。
追问:为什么标签引用仍有风险?
标签可以移动,重跑或未来执行可能解析到不同提交。生产链路应固定审核提交,或由组织策略限制可用引用。
追问:如何验证密钥没有泄露?
使用假密钥和最小权限运行完整链路,检查日志、输出、制品和错误路径;同时审计 inherit、环境变量和第三方 action 的读取范围。