GitOps 篇 01:用 Gitleaks 阻止凭据进入 Git 历史
本文是全栈电商微服务从 0 到 1 再到 10的 GitOps 系列第 1 篇。上一篇:基础设施篇:Reloader 让 Secret 变更自动滚动 Pod;下一篇(TODO):GitOps 篇 02——用 Trivy fs/config 检查依赖与 IaC 配置。
GitOps 把 Git 中的声明作为部署依据。这个机制提高了可追溯性,也放大了凭据误提交的后果:密码、Token 或私钥一旦进入提交历史,即使后来删除当前文件,旧提交、开发者克隆、CI 缓存和镜像仍可能保留副本。
Gitleaks 是这条 GitOps 流程的第一道门。它在代码构建之前检查敏感信息,不允许「先提交、再删除」成为日常处理方式。
这个系列如何组织
本系列不按工具品牌罗列功能,而是按一次变更从代码到集群的顺序推进。每篇只回答一个阶段的问题,并把输出交给下一阶段:
flowchart LR A[开发者提交变更] --> B[Gitleaks<br/>阻止凭据进入 Git] B --> C[Trivy fs/config<br/>检查依赖与 IaC] C --> D[zizmor<br/>检查 GitHub Actions] D --> E[Buildx<br/>构建不可变镜像] E --> F[Syft<br/>生成 SBOM] F --> G[Trivy image<br/>扫描镜像漏洞] G --> H[Cosign<br/>签名与证明] H --> I[Kyverno<br/>准入验证镜像] I --> J[Argo CD<br/>持续协调目标状态]| 顺序 | 文章 | 解决的问题 | 阶段输出 |
|---|---|---|---|
| 01 | Gitleaks | 变更是否泄露凭据 | 不含新增凭据的提交 |
| 02 | Trivy fs/config | 依赖和 IaC 是否存在高风险问题 | 通过配置门禁的源码 |
| 03 | zizmor | GitHub Actions 是否存在危险写法 | 可信的流水线定义 |
| 04 | Syft | 镜像包含什么 | SPDX SBOM |
| 05 | Trivy image | 最终镜像是否包含可修复的高危漏洞 | 通过漏洞门禁的 digest |
| 06 | Cosign | 谁对镜像和 SBOM 负责 | 签名与 attestation |
| 07 | Kyverno | 集群是否只接受满足策略的镜像 | 准入结果和 PolicyReport |
| 08 | GitOps 自动化收口 | Git 如何成为部署真相源 | GitHub CI → 清单 → Argo CD 的闭环 |
| TODO | GitLab 单独篇 | GitLab 门禁如何与 GitHub 发布职责分离 | 不重复发布权的第二远端门禁 |
这里需要区分两个容易混用的词:
- CI(持续集成):验证代码、工作流和制品,失败时阻止变更继续前进。
- GitOps:以 Git 中的声明为目标状态,由控制器持续协调实际状态。CI 可以更新声明,但不应绕过 Git 直接把临时状态当作最终状态。
Gitleaks 属于 CI 的源码门禁,但它保护的是 GitOps 的真相源,因此放在系列第一篇。
Gitleaks 检查什么
Gitleaks 扫描文件内容和 Git 提交,识别疑似敏感信息,例如:
- 云厂商 Access Key;
- GitHub、GitLab、npm 等平台的 Token;
- 数据库连接串中的用户名和密码;
- OAuth
client_secret; - JWT 和长期 Bearer Token;
- PEM、RSA 等私钥;
- Webhook Secret;
- 项目自定义格式的短弱口令。
它主要依赖规则、关键词、正则表达式和熵值判断。默认规则适合通用 Token,但项目仍要补充自己的泄露形态。
例如,通用规则不一定能识别下面这种连接串里的短口令:
postgresql://postgres:weak-password@postgres.example.internal/ecommerce因此当前电商项目在 .gitleaks.toml 中增加了 url-embedded-credential 和 config-password-assignment 两类规则,覆盖 URL 内嵌凭据,以及 YAML、TOML、Markdown、HTTP、SQL、Shell 等文件中的明文密码赋值。
为什么删除文件还不够
假设第一次提交包含真实 Token:
A -- B(secret) -- C(remove secret)分支最新状态 C 已经看不到 Token,但 B 仍然是 Git 历史的一部分。任何能读取 B 的人都可能取得原始值。
因此处置泄露必须分成两件事:
- 处置凭据:立即撤销或轮换凭据,并检查使用日志。
- 处置仓库:删除当前文件中的凭据;必要时重写历史并协调所有克隆重新同步。
Gitleaks 只能发现风险,不能撤销 Token,也不能证明泄露后的凭据没有被使用。只删除文件、只添加 .gitignore 或只修改最新提交,都不能代替密钥轮换。
当前电商项目的三层门禁
当前项目把同一份 .gitleaks.toml 接到三个位置。规则只有一份,扫描范围根据阶段变化:
flowchart TB Config[.gitleaks.toml<br/>规则与 allowlist 真相源] Config --> Local[pre-commit<br/>扫描暂存区] Config --> Verify[verify-quick<br/>扫描完整历史] Config --> GitHub[GitHub context-gate<br/>扫描完整历史] Local --> Commit[允许创建提交] Verify --> Push[允许继续本地验证] GitHub --> Protected[持续证明远端历史仍然干净]第一层:提交前扫描暂存区
frontend/.vite-hooks/pre-commit 在创建提交前执行:
gitleaks git "$root" \ -c "$root/.gitleaks.toml" \ --pre-commit \ --staged \ --no-banner \ --redact=80这一层只检查即将进入提交的内容,反馈最快。项目采用失败即关闭(fail closed):如果没有安装 Gitleaks,钩子直接拒绝提交,而不是静默跳过。
本地安装:
brew install gitleaks手动检查暂存区:
gitleaks git . -c .gitleaks.toml --pre-commit --staged --no-banner --redact=80预期结果:命令退出码为 0,然后才允许提交。如果命令报告疑似凭据,先判断是真实凭据还是误报,不要直接绕过。
第二层:本地验证完整历史
scripts/verify-quick.sh 执行:
gitleaks git . -c .gitleaks.toml --no-banner --redact=80这一层检查整个可见 Git 历史。它用于发现暂存区扫描无法覆盖的问题,例如旧提交中的凭据,或者开发者绕过本地钩子后留下的记录。
扫描完整历史会比暂存区扫描慢,但它回答了更强的问题:当前仓库的可见历史是否仍然不包含规则命中的凭据?
第三层:GitHub Actions 持续证明完整历史
GitHub 的 context-gate.yml 使用完整克隆:
- name: Checkout repository (full history) uses: actions/checkout@v5 with: fetch-depth: 0然后安装固定版本、校验发布物 SHA-256,再执行:
gitleaks git . -c .gitleaks.toml --no-banner --redact=80fetch-depth: 0 不能省略。浅克隆只包含少量提交,即使命令显示扫描通过,也不能证明完整历史干净。
当前 workflow 还从输出中读取 N commits scanned,并断言扫描数量大于项目设置的最低值。这个断言防止流水线被误改成只扫描最新提交后继续显示绿色。
发布 tag 的供应链 workflow 采用另一种范围:从上一个语义化版本 tag 扫到当前提交。它检查本次发布区间是否引入泄露;context-gate.yml 则负责持续证明完整历史。两种扫描不是重复,而是回答两个不同的问题。
为什么不用“没安装就跳过”
安全门禁常见的错误写法是:
command -v gitleaks >/dev/null || exit 0这个脚本在工具缺失时仍然返回成功,CI 看起来是绿色,但实际上没有发生扫描。
当前项目采用相反规则:
command -v gitleaks >/dev/null 2>&1 || { echo "缺少 gitleaks,凭据门禁无法执行" >&2 exit 1}门禁成功必须表示「扫描已经执行并且没有发现阻断项」,不能只表示「脚本没有崩溃」。GitHub Actions 还固定 Gitleaks 版本并校验下载文件,避免流水线每次运行时取得不同的工具行为。
电商项目为什么需要自定义规则
电商系统连接的外部系统多,凭据类型也多:数据库、支付渠道、对象存储、身份系统、消息系统和镜像仓库都可能有单独的密钥。
当前项目的 .gitleaks.toml 在默认规则上增加项目规则,并显式扫描这些容易被忽略的文件类型:
path = '''(?i)\.(ya?ml|toml|env|ini|json|properties|conf|md|http|sql|sh|txt)$'''这一步很重要,因为凭据不只会出现在 .env:
- Markdown 排障记录可能粘贴真实密码;
.http请求样例可能保存 Bearer Token;sqlc.yaml可能内嵌数据库连接串;- Shell 脚本可能写死访问密钥;
- YAML 可能包含未替换的 Secret 值;
- base64 文本可能包装整份配置,但 base64 不是加密。
规则应来自项目实际出现过的泄露形态,不能只依赖通用的“高熵字符串”判断。
allowlist 应该怎么写
Gitleaks 会产生误报,例如测试占位符、公开默认值、类型声明和 Secret 引用路径。正确做法是只放行已经确认不是凭据的具体内容。
当前项目使用带说明的 allowlist,例如:
[[allowlists]]description = "占位符与已脱敏标记"regexes = [ '''\*\*\*REMOVED-[A-Z-]+\*\*\*''', '''YOUR_JWT_TOKEN''',]推荐顺序:
- 确认命中的值是否真实有效。
- 如果是真实凭据,先轮换,再从代码中移除。
- 如果是误报,收窄到具体路径、规则或正则。
- 在
description中说明为什么可以放行。 - 重新扫描完整历史,确认没有放宽其他风险。
不要使用下面这些方式“修绿”:
- 放行整个配置目录;
- 为真实凭据添加 allowlist;
- 全局关闭默认规则;
- 永久设置
SKIP_GITLEAKS=1; - 把明文改成 base64 后继续提交。
allowlist 是有证据的例外,不是忽略告警的垃圾桶。
凭据应该放在哪里
Gitleaks 只负责防止凭据进入 Git,不负责保存凭据。电商项目需要按使用位置选择存储方式:
| 使用位置 | 推荐位置 | Git 中保存什么 |
|---|---|---|
| GitHub Actions | GitHub Actions Secrets 或 OIDC | Secret 名称、最小权限说明、非敏感配置 |
| Kubernetes 工作负载 | OpenBao/Vault + External Secrets Operator,或受控的 Kubernetes Secret 流程 | ExternalSecret 引用,不保存真实值 |
| 本地开发 | 被 .gitignore 排除的本地配置或密码管理器 | 示例文件和占位符 |
| 应用运行时 | Config Center 或专用 Secret 管理系统 | 配置键与读取规则,不保存明文凭据 |
/Users/lens/lens077/kubernetes/components 提供面向通用集群的组件部署,其中 OpenBao 和 External Secrets Operator 可作为集群侧凭据来源。Gitleaks 本身不需要部署到 Kubernetes:它应在凭据进入 Git 之前运行。
后续 GitOps 文章会为电商项目单独给出 ExternalSecret、命名空间、镜像仓库和准入策略配置,不直接复制通用组件目录中的示例值。
发现泄露后的处理顺序
如果扫描命中真实凭据,先停止发布。不要为了继续流水线而添加 allowlist。
- 撤销或轮换已暴露的凭据。
- 检查提供方日志,确认凭据是否被使用。
- 从当前代码和配置中移除真实值。
- 将凭据迁移到正确的 Secret 管理位置。
- 判断是否需要重写 Git 历史。
- 如果重写历史,通知所有协作者重新同步仓库。
- 运行完整历史扫描。
- 补充规则或负例,防止同类泄露再次发生。
git filter-repo 可以重写历史,但它不能让已经被第三方读取的密钥失效。密钥轮换必须先于“仓库看起来干净”。
GitLab 的位置(TODO)
本系列以 GitHub Actions 为主要 CI 和发布链。GitLab 作为第二远端,只承担代码门禁,不构建、签名、推送镜像或回写部署清单,避免同一个发布 tag 被两个平台写入两次。
GitLab 的 Gitleaks 配置将在单独文章中处理。该篇需要解释:
- 如何复用
.gitleaks.toml; - 如何完整拉取 Git 历史;
- 如何固定工具版本;
- 如何与 GitHub 保持同一道读取门禁;
- 为什么 GitLab 不取得发布权。
本篇不展开 GitLab YAML,以免把 GitHub 主流程和第二远端职责混在一起。
本篇验收清单
完成这一篇对应的工程动作后,应能回答:
- 暂存区中的真实 Token 会让提交失败。
- 未安装 Gitleaks 时,本地门禁失败,而不是跳过。
-
.gitleaks.toml是本地和 GitHub 共用的规则真相源。 - GitHub 使用完整历史检出,而不是浅克隆。
- CI 能证明扫描范围没有退化成单个提交。
- 每个 allowlist 都有具体对象和放行理由。
- 真实凭据存放在 Secret 管理系统,不在 Git 中。
- 泄露处置先轮换凭据,再处理仓库历史。
通过这些检查后,GitOps 的真相源才具备进入下一道门的资格。
下一篇(TODO)将使用 Trivy fs/config 检查源码依赖和 Kubernetes、Dockerfile 等 IaC 配置。Gitleaks 回答「有没有把秘密交给 Git」,Trivy fs/config 接着回答「准备交给构建和集群的配置是否安全」。