4. 代码仓库安全基线配置
代码仓库是 DevSecOps 流水线的源头,一旦源头被污染,后续的 SAST、SCA、镜像扫描都只是在“检测已被投毒的结果”。仓库安全基线不是零散开关的集合,而是一组互相咬合的强制约束:分支保护决定“谁能改主干”,提交签名决定“改动是否可追溯到可信身份”,访问控制决定“谁在什么范围内能做什么”,Webhook 安全决定“事件能否被伪造触发流水线”,审计日志决定“出事之后能否还原现场”。本节以 GitLab 与 GitHub 的通用能力为参照,给出可落地的配置方式与判断依据。
分支保护:把主干变成只能通过合并请求进入的单向门
分支保护的核心不是“禁止推送”,而是把变更路径收敛为唯一通道:特性分支 → 合并请求(MR/PR)→ 评审 + 流水线通过 → 合并。以 main 与 release/* 为例,建议配置如下约束:禁止直接 push 与 force push;合并必须至少 1 名非作者审批;要求流水线成功后才允许合并;要求分支与目标分支保持最新;对 release/* 额外要求 2 名审批并禁止绕过。
关键细节在于“谁能绕过”。很多团队给管理员保留了 bypass 权限,结果保护形同虚设。工程化做法是把 bypass 收敛到极少数应急账号,并让绕过行为产生审计事件与告警。另一个易被忽略的点是“合并后删除源分支”与“要求线性历史”:前者减少残留分支被误用的风险,后者让审计时的提交链更清晰。判断保护是否生效,不看配置页面,而看一次真实尝试:用普通开发者账号直接 push main,应被拒绝并返回 protected branch 类错误;用未通过流水线的 MR 合并,应被阻塞。
提交签名:让每个提交都携带可验证的身份凭证
提交签名解决的是“提交者字段可以随便填”的问题。Git 支持 GPG 与 SSH 两种签名方式,工程上更推荐 SSH 签名,因为密钥往往已用于代码拉取,复用成本低。开发者侧配置示例:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git commit -S -m "fix: validate webhook signature"
仓库侧需要把公钥登记为“签名密钥”,并在分支保护中开启“要求签名提交”。验证方式很直接:git log --show-signature -1 应显示 Good "git" signature;在 GitLab/GitHub 的提交列表中,已签名提交会带 Verified 标记。若出现 gpg: Can't check signature: No public key,说明验证方缺少公钥,属于配置缺失而非签名无效。需要强调的是,签名只证明“提交由持有该私钥的人创建”,不证明代码安全,因此它必须与评审、流水线门禁叠加使用。
访问控制:最小权限与角色分层
访问控制要回答三个问题:谁能读、谁能写、谁能改配置。建议按角色分层:Guest 仅可读议题;Reporter 可读代码;Developer 可推特性分支、开 MR;Maintainer 可合并、管理分支保护;Owner 仅限平台团队,负责成员与集成配置。对 CI/CD 使用的机器人账号,应使用 Project Access Token 或 Deploy Key,并限定为 write_repository 等最小 scope,禁止使用个人长期令牌。
工程化落地的两个硬要求:一是开启强制双因素认证(2FA),否则令牌泄露即等于仓库失守;二是对 main 与密钥文件路径设置 CODEOWNERS,使涉及 .gitlab-ci.yml、Dockerfile、k8s/ 的改动必须由安全或平台团队审批。判断访问控制是否合理,可用“离职演练”检验:移除一名成员后,其令牌、Deploy Key、Webhook 是否全部失效。
Webhook 安全:防止伪造事件触发流水线
Webhook 是外部系统触发流水线的入口,若只校验 URL 而不校验来源,攻击者可伪造 push 事件触发构建,甚至借助流水线环境变量窃取密钥。通用防护是校验签名:GitLab 使用 X-Gitlab-Token 共享密钥,GitHub 使用 X-Hub-Signature-256 的 HMAC-SHA256。接收端必须使用常量时间比较,避免时序侧信道:
import hmac, hashlib
def verify(secret: bytes, body: bytes, sig_header: str) -> bool:
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header)
此外应限制来源 IP 或经由反向代理白名单,Webhook 密钥存入密钥管理服务而非明文写在配置里,并定期轮换。判断是否生效:用错误签名发送请求,应返回 401/403;用正确签名发送,应返回 2xx 且流水线被触发。
仓库审计日志:把“发生过什么”变成可查询的证据
审计日志需要覆盖推送、合并、权限变更、保护规则修改、令牌创建与 Webhook 变更等事件。GitLab 可在项目与实例级查看 Audit Events,GitHub 使用 Organization Audit Log,并可通过 API 导出到 SIEM。工程化要点是:日志集中采集、保留期不少于 180 天、对高危事件(如关闭分支保护、创建管理员令牌)设置实时告警。判断审计是否可用,看能否在 5 分钟内回答“谁在何时把 main 的保护关掉了”。
把五项配置串成一条可执行的基线
上述五项不应各自为政。推荐的最小可用基线是:main 开启分支保护并要求签名提交与流水线通过;所有成员强制 2FA;CI 使用最小 scope 的项目令牌;Webhook 全部启用签名校验;审计日志导出到集中平台并对高危事件告警。落地顺序建议先做访问控制与 2FA(成本低、收益大),再做分支保护与签名(影响开发习惯,需要沟通),最后补齐 Webhook 校验与审计告警(偏平台侧)。
main 加保护却忽略 release/* 与 hotfix/*;给管理员保留 bypass 且不记录;Webhook 只校验 URL 不校验签名;审计日志只保留 30 天。这些都会让基线在真实攻击面前失效。判断基线是否真正落地,不靠配置截图,而靠可复现的验证动作:用普通账号尝试直推主干、用错误签名调用 Webhook、用离职账号的旧令牌访问仓库、在审计平台检索一次保护规则变更。只有这些动作都给出预期结果,仓库安全基线才算从“配置项”变成了“工程能力”。