Fulcio、Rekor 与 Sigstore 私有信任服务手册
一次 CA 轮换后,新发布的镜像都能签名,半年前的生产镜像却突然无法验证。平台只把新的 Fulcio 链和 Rekor 公钥覆盖进客户端配置,既没有为旧材料保留有效窗口,也没有封存历史 checkpoint;回滚人员又把 TUF root.json 和 Sigstore trusted_root.json 当成同一种文件,结果越修越乱。
另一次隐私事件来自一份内部 SBOM。团队以为 Rekor 是“可信制品仓”,把带客户名和私有包名的 attestation 写入公共链路,随后才意识到透明日志具有公开可发现与追加写入属性,普通删除工单无法让历史消失。信任服务不仅是几组公钥和 URL,它同时是一条身份、证书、透明日志、可信时间、更新分发和长期留存链。
六类服务怎样组成验证机制
OIDC issuer 先证明账号或工作负载身份,并签发 audience 受限的 ID token。Cosign 在内存中生成临时密钥,用 token 与持钥证明请求 Fulcio;Fulcio 校验 issuer、audience、claim 和 proof of possession,把身份与公钥写入短期 X.509 证书。证书进入 Certificate Transparency log,SCT 证明证书已被日志接收。
签名产生后,Rekor 记录与制品摘要、签名、证书或公钥有关的透明日志条目,并返回 SET、包含证明或 checkpoint 相关材料。TSA 为签名提供独立的 RFC 3161 可信时间。标准 bundle 把这些材料连同签名内容交给验证者,验证者再从 TUF 发布的 TrustedRoot 取得 Fulcio、CT、Rekor、TSA 的可信材料,从 SigningConfig 发现当前可写服务。
每个组件妥协后的后果不同。OIDC 被攻破会让攻击者以合法账号获得证书;Fulcio CA 被攻破会伪造身份绑定;CT 或 Rekor 操作方可拒绝服务、隐瞒或尝试 split view;TSA 被攻破会伪造时间判断;TUF 根角色被攻破会替换整组信任材料。透明日志提高可发现性,不会自动阻断恶意发布,独立 monitor、checkpoint 传播、消费策略和 compromise-time 决策仍是安全模型的一部分。Sigstore 威胁模型明确把身份源安全、日志监控和软件本身是否安全放在不同责任层。
先分清 TUF 根与 Sigstore 可信根
TUF root.json 保存 root 角色公钥、阈值、角色委派和元数据版本,用来验证后续 TUF 元数据更新。它是更新系统的根。Sigstore protobuf trusted_root.json 是 TUF targets 中的一项业务目标,保存 Fulcio CA、CT log、Rekor 分片和 TSA 的验证材料及有效窗口。它是签名验证所需的聚合对象,不负责验证 TUF 自己的 root 更新。
SigningConfig 是另一项 target,描述签名客户端当前应联系的 Fulcio、Rekor、OIDC 和 TSA 地址、API 主版本、operator 与有效时段。活动 Rekor v2 分片 URL 应由它动态下发;活动和历史分片公钥则由 TrustedRoot 下发。把当前 URL 写死在流水线,会在分片切换时中断;只保留活动公钥,会在历史验证时失败。
公有实例由root-signing 仓库组织信任材料与签名事件,并通过生产 TUF 仓库分发。私有实例应复制这种职责分离:初始 root.json 通过带外渠道交付,offline root/targets 关键角色采用阈值签名,在线 timestamp/snapshot 角色受限运行;目标文件先进入 staging 验证,再发布到生产镜像。不能从 Git 仓库主分支随手下载 JSON 就宣称建立了信任。
Fulcio 把身份绑定到短期公钥
Fulcio 的核心配置是允许的 OIDC issuer、client ID/audience、claim 映射、证书 subject/SAN 规则、CA 链与 CT log。issuer URL 改动会让验证策略中的 issuer 全部失配;audience 过宽会让为其他服务签发的 token 被拿来申请代码签名证书;claim 映射不稳定会使同一 workflow 在升级后变成另一 identity。
私有部署应优先采用稳定的 workload identity。人员邮箱会受改名、离职和恢复流程影响,自动化 identity 更适合绑定仓库、workflow、ref 和受保护环境。Fulcio 证明“某 issuer 在某时刻把该 identity 绑定给某公钥”,不证明该身份有权发布某仓库;授权必须由验证端策略表达。
CA root 和 intermediate 应分层托管。根私钥离线或放入高保护等级 HSM,在线 Fulcio 使用受限 intermediate/KMS signer;Pod 不持有可导出的长期根私钥。CA、CT、Rekor、TSA 和 TUF 各用独立 key 与 IAM,不能共用一个 Kubernetes Secret 或一个 KMS key。证书有效期是私有部署参数,不应照抄公共实例的时长;过长增加泄露窗口,过短会放大签名时钟与可用性故障。
Fulcio 必须把证书提交 CT log 并返回 SCT。若验证端出现 SCT verification failed,先比较证书链、CT 公钥和有效窗口,再检查 Fulcio 是否连到正确 CT 实例;禁止用 --insecure-ignore-sct 把生产故障改成放行。
Rekor v1 与 v2 是不同运维模型
Rekor v1 仓库已明确标记 maintenance mode。它的已发布 API 仍可维护现有负载,但新建长期私有平台应先评估 Rekor v2,而不是因为熟悉 Trillian 路径就默认继续扩建 v1。迁移期间客户端与 bundle 样本必须同时覆盖 v1/v2,退役时间不能凭猜测写死。
Rekor v2采用 tile-backed log,并提供面向 GCP、AWS、POSIX、GCP CloudSQL 等后端的不同二进制。其容量面不再只是一个 API Pod:写入排序层、tile 对象存储、checkpoint 签名、服务实例与 KMS/HSM 都有独立可用性和一致性要求。后端选择会改变故障域、写入延迟、对象数量、备份方式和云成本。
v2 通过分片控制单日志规模。当前写入分片 URL、API 主版本、operator 和生效窗口由 SigningConfig 下发;历史分片公钥和有效窗口保留在 TrustedRoot。客户端不得把分片 URL、年份或关闭时间写死。旧分片停止写入后仍需长期提供 tile、checkpoint 和公钥,否则历史 bundle 即使完整也无法建立可信验证链。
Cosign 3.1.1 对大型 DSSE attestation 的 Rekor v2 写入使用 PAE 哈希条目,避免把整份大型声明塞入日志。这降低日志载荷,不改变证据仓责任:完整 SBOM、provenance 或声明仍保存在 OCI referrer、bundle 或受治理的证据仓中。
TSA 与可信时间决定历史证据
短期证书到期后仍能验证历史签名,依赖的是“签名发生在证书有效且信任材料未被判定失守的时段”这一事实。Rekor 集成时间、日志证明和 RFC 3161 timestamp 提供不同层次的时间证据,不能用客户端本地时钟代替。Fulcio 与 Rekor v2 链路需要把 TSA URL 放入签名配置,把 TSA 证书链和有效窗口放入 trusted root。
TSA key 泄露与 Fulcio CA 泄露的响应不同。前者影响时间断言,后者影响身份到公钥的绑定;都要记录可辩护的 compromise time,发布新材料,并按签名时段判断哪些历史制品需重签、重建或隔离。简单删除旧公钥会让泄露前的合法历史证据一起失效。
时钟监控应覆盖 Fulcio、Rekor、TSA、KMS 和验证客户端,告警关注偏移趋势而不是单个固定数字。部署前用未来/过去偏移测试证书窗口、timestamp 与 TUF metadata 到期行为;任何时钟错误都应 fail closed 并给出可分型证据。
用官方 Helm Charts 进入私有部署
官方 Helm charts 仓库提供 Fulcio、Rekor、CT log、TSA、TUF、scaffold 等入口。scaffold/scaffolding 适合联调和端到端测试,不是已经补齐密钥托管、HA、灾备、监控与轮换的生产拓扑。
helm repo add sigstore https://sigstore.github.io/helm-charts
helm repo update sigstore
export CHART=<FULCIO_OR_REKOR_CHART>
export CHART_VERSION=<APPROVED_CHART_VERSION>
helm show values "sigstore/$CHART" \
--version "$CHART_VERSION" > "$CHART.values.reference.yaml"
helm upgrade --install "$CHART" "sigstore/$CHART" \
--namespace sigstore-system --create-namespace \
--version "$CHART_VERSION" \
--values "deploy/$CHART.values.yaml"具体 chart 与版本从仓库复核后固定。helm show values 只用于差异分析,真正配置应显式声明镜像 digest、replica、resources、PDB、Service、Ingress、NetworkPolicy、ServiceAccount、外部存储、KMS/HSM、Secret 引用和监控。渲染清单要检查是否把私钥或 token 写进 ConfigMap、注解和 Helm release 历史。
部署顺序从依赖向入口推进:先建立 OIDC、KMS/HSM、数据库/对象存储和 TUF 初始根,再部署 CT/Rekor/TSA,随后部署 Fulcio,最后发布 SigningConfig 与 TrustedRoot 给测试客户端。生产切流前用隔离 identity 和测试 Registry 验证完整签发、日志、时间和历史读取链。
配置字段的生产影响
OIDC 侧至少固定 issuer、audience/clientID 和 claim 规则。issuer 必须与 token 的 iss 精确一致;audience 应只接受 Sigstore 客户端用途;claim 映射应输出稳定 URI,而不是显示名。启用任意 issuer 动态注册会把 CA 变成公共代理,必须拒绝。
Fulcio 侧的 CA chain、certificate lifetime、CT URL 和 signer 决定证书链、过期窗口与签发可用性。Rekor 侧的 API version、log ID、signer、storage backend 和 checkpoint 间隔决定条目格式、日志身份、持久化与监控延迟。TSA 的 signer、chain 和 policy OID 决定时间戳验证材料。TUF 侧的 root/targets/snapshot/timestamp 版本、阈值与 expiry 决定客户端是否接受更新。
有效时间窗口要以旧新重叠方式迁移:SigningConfig 在切换时只把新写入指向新服务,TrustedRoot 同时保留旧新验证材料,并为旧材料设置合理 end-time。先改 trusted root,再切 signing config;先让所有客户端读到双信任,再开始新签名。反过来操作会制造“新签名已产生,旧客户端却不认识新 key”的窗口。
所有配置文件进入版本库时只保存公开材料和 Secret 引用。CA/TSA/log/TUF 私钥、OIDC client secret、数据库口令、对象存储密钥、KMS token、mTLS client key 均由专用秘密系统注入,日志和诊断包必须脱敏。
正向实验:签发、日志与 bundle 闭环
在测试身份、测试 Registry 和私有信任服务上创建一份 blob。客户端使用私有 SigningConfig,验证端使用对应 TrustedRoot,并精确约束 identity 与 issuer:
set -euo pipefail
printf 'private-sigstore-smoke\n' > artifact.bin
cosign sign-blob artifact.bin \
--signing-config ./signing-config.json \
--bundle artifact.sigstore.json \
--yes
cosign verify-blob artifact.bin \
--bundle artifact.sigstore.json \
--trusted-root ./trusted-root.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"退出码 0 只是第一层。还要解析 bundle,确认内容摘要与 artifact.bin 一致、证书链命中私有 Fulcio、SCT 命中预期 CT log、透明日志条目命中预期 Rekor operator/log ID、时间证据命中 TSA,并记录 inclusion proof/checkpoint。服务端日志应能用请求关联 ID 串起 Fulcio、CT、Rekor 和 TSA,但不得记录 OIDC token。
再查询测试期的日志状态与对象存储,确认条目在 API 副本重启后仍可读取,checkpoint 被独立归档,tile 或 v1 存储备份包含该条目。只有客户端、服务端与持久化三类证据一致,才证明完整链路生效。
反向实验:错根、错身份与日志缺口
先保留成功样本,再只改变一个变量。错 identity 应在授权策略处失败,错 trusted root 应在证书或日志材料处失败;两者的错误分类必须不同:
set -euo pipefail
if cosign verify-blob artifact.bin \
--bundle artifact.sigstore.json \
--trusted-root ./trusted-root.json \
--certificate-identity 'urn:example:unauthorized-workload' \
--certificate-oidc-issuer "$EXPECTED_ISSUER"; then
echo 'ERROR: unauthorized identity was accepted' >&2
exit 1
fi
if cosign verify-blob artifact.bin \
--bundle artifact.sigstore.json \
--trusted-root ./unrelated-trusted-root.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"; then
echo 'ERROR: unrelated trust root was accepted' >&2
exit 1
fi接着构造日志缺口:复制 trusted root,移除签名所用历史 Rekor key 或缩短其有效窗口,再验证同一 bundle。预期非零退出,并明确指向日志 key/有效窗口,而不是退化为“证书签名有效即可”。若客户端自动联网取得了另一份 root,实验环境要断开 egress 后重跑,确保测试的确使用指定材料。
# 用隔离副本模拟旧分片材料被错误删除
jq 'del(.tlogs[0])' trusted-root.json > trusted-root-without-old-log.json
if cosign verify-blob artifact.bin \
--bundle artifact.sigstore.json \
--trusted-root ./trusted-root-without-old-log.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"; then
echo 'ERROR: missing historical log material was ignored' >&2
exit 1
fiprotobuf 字段随规范版本演进,jq 路径应先用目标 trusted_root.json 样本确认;实验意图是删除历史日志验证材料,不是宣称固定 schema。任何 --insecure-ignore-tlog 或 --insecure-ignore-sct 的成功都只能证明跳过检查,不能作为修复结果。
根轮换必须保持历史可验证
轮换先生成新 Fulcio intermediate、CT/Rekor/TSA key 和相应公开材料,在 staging 完成签发与验证。TUF 发布一个同时包含旧新材料的 trusted_root.json:旧材料带原始 start 与计划 end,新材料带新 start;所有 verifier 更新并证明能验证旧制品 A。随后更新 SigningConfig,把新写入指向新服务,签出制品 B。
# T0:保留旧样本和证据
cosign verify-blob artifact-A.bin --bundle A.sigstore.json \
--trusted-root trusted-root-dual.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"
# T1:新配置签出 B,双信任根同时验证 A/B
cosign sign-blob artifact-B.bin --signing-config signing-config-new.json \
--bundle B.sigstore.json --yes
for name in A B; do
cosign verify-blob "artifact-$name.bin" --bundle "$name.sigstore.json" \
--trusted-root trusted-root-dual.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"
done正向证据应显示 A 命中旧 key/window,B 命中新 key/window。保持双信任至少跨过客户端更新周期和回滚窗口,再停止旧服务写入;停止写入不等于删除旧验证材料。
# 反例:只发布新材料会破坏 A 的历史验证
if cosign verify-blob artifact-A.bin --bundle A.sigstore.json \
--trusted-root trusted-root-new-only.json \
--certificate-identity "$EXPECTED_IDENTITY" \
--certificate-oidc-issuer "$EXPECTED_ISSUER"; then
echo 'ERROR: rotation fixture did not exercise the old key' >&2
exit 1
fi错误发布后不能把旧 TUF metadata 直接覆盖回去,因为客户端会执行版本与回滚保护。应发布版本号递增、内容恢复为双信任的新 metadata,或按既定 TUF root 恢复流程完成阈值签名。恢复后重验 A/B,并确认所有镜像站和缓存拿到同一 metadata 版本。
流水线与客户端矩阵如何接入
平台仓库维护签名配置、可信根和策略三类版本。签名 job 只读取当前 SigningConfig,验证 job 只读取审批过的 TrustedRoot,消费策略再限定 issuer/identity/subject/predicate。配置发布必须有摘要、TUF target 版本、生效窗口、审批和回滚版本,不能让 Helm release 的 ConfigMap 成为唯一事实源。
客户端矩阵至少覆盖当前 Cosign 3.x、仍在组织支持窗口的旧客户端、策略控制器/SDK、Rekor v1/v2 bundle、keyless/KMS 与断网验证。每次服务升级都跑成功样本、摘要篡改、身份错配、旧分片 key 缺失、TUF 过期和 TSA 缺失样本。只跑“新客户端签新制品”无法发现历史断裂。
OIDC runner 仅获短期 token,Fulcio/CT/Rekor/TSA ServiceAccount 只访问各自 KMS key 与存储。TUF offline key 不进入集群,online key 不得修改 root/targets 阈值。日志读取与 checkpoint 镜像可以公开或广泛只读,写入、分片切换和 root 发布必须是独立权限域。
透明日志的隐私与敏感数据
证书可能暴露邮箱、workflow URI、仓库、ref、issuer 和组织信息;透明日志还会暴露 artifact hash、签名时间和发布频率。即使不上传 SBOM 全文,这些元数据也可能泄露未公开项目活动。采用公共实例前应完成数据分类、跨境与留存评估;高度敏感工作负载更适合私有日志或经过设计的身份映射。
禁止把源码、客户名、内部包名、私有仓库 URL、漏洞细节和 secret 放入 annotation 或 attestation 后写公共日志。Bundle 会复制证书、日志条目、时间戳和声明,同样不是“只有公钥”的无敏感文件。证据仓访问权限要按 subject 与环境隔离,下载与导出纳入审计。
OIDC ID token 是 bearer credential,不得出现在 CLI history、set -x、环境转储、进程参数、缓存和失败 artifact。CA、Rekor、CT、TSA、TUF key 采用不同密钥域和轮换审批;数据库、对象存储、mTLS 与 Registry 凭证也不得混放。日志系统要对 Authorization、token、cookie 和证书申请载荷做字段级脱敏。
透明日志条目通常不能按普通数据库方式删除。隐私事件的响应是停止继续写入、界定已公开字段、轮换受影响身份或密钥、通知数据责任方并更新策略;不能承诺“删掉 Registry referrer 就从 Rekor 消失”。
排障要按身份、证书、日志、时间、更新分型
签发失败先看 OIDC:核对 token 的 iss、aud、稳定 identity claim 与时钟,不输出完整 token。unauthorized issuer 与 audience mismatch 是身份配置错误,浏览器登录成功不能证明 Fulcio 会接受该 token。再看 proof of possession 与 Fulcio signer,确认临时公钥确实由请求方持有。
证书成功但签名失败时,检查 CT SCT、Fulcio 链、TSA 和 Rekor 写入。CT 失败关注证书链和 log key;Rekor 4xx 关注 entry 格式/API 版本,429/5xx 关注限流和服务可用性;TSA 失败关注 signer、chain、policy 与时钟。Rekor 返回 UUID/SET 不足以证明内容正确,还要比较 entry 内 digest、签名、证书/公钥并验证 inclusion proof/checkpoint。
验证失败时打印使用中的 trusted root 摘要、TUF metadata 版本、target 版本、有效窗口与 log ID。若只有旧制品失败,优先查历史 key 或分片被删;若所有新制品失败,优先查 signing config 已切换而 verifier 尚未更新;若不同网络结果不同,检查 TUF 镜像、CDN 缓存和 split-view 监控。
日志一致性由独立 monitor/witness 或至少独立 checkpoint 拉取与比对承担。仅监控 API 200 无法发现日志分叉。告警应关联 checkpoint 树大小单调性、tile 可读性、签名验证、分片窗口、TUF 到期和对象存储版本,故障恢复后用历史样本重放而不是只看 Pod Ready。
容量、成本与高可用设计
Fulcio、TSA 和 TUF 在线服务可通过无状态副本、PDB、跨故障域调度与多入口实现 HA,但 signer/KMS、CT/Rekor 存储和 TUF 元数据发布仍是共享关键依赖。API 副本增加不能修复单区 KMS、单桶对象存储或单个 offline keyholder 的风险。每个依赖都要有 RTO/RPO、备份恢复和降级语义。
容量模型从签名事件率、证书率、attestation 大小、Rekor entry/tile 数、checkpoint 频率、查询率、分片周期与保留年限计算。v2 大型 DSSE 记录 PAE 哈希能降低日志载荷,但完整证据仍占 Registry/证据仓容量。对象存储的小对象请求、跨区域复制、KMS 签名、数据库排序写入、CDN 出口和 monitor 扫描都构成成本。
压测至少包含稳态签名、发布峰值、历史验证扫描、分片切换和后端恢复。观察 p50/p95/p99、拒绝率、队列年龄、tile/checkpoint 滞后、KMS 限流、对象存储错误和 TUF 缓存命中。演示阈值只能用于初始实验,生产阈值应由发布 SLO、容量预算和基线测量决定。
Rekor v1 现有平台的升级重点是安全修复、Trillian/数据库兼容和迁移样本;新投资应评估 v2。v2 后端之间并非无状态切换,迁移要封存旧 shard、保留公钥/checkpoint、建立新 shard、发布双材料并验证历史。不能把复制 tile 当成自动获得一致日志身份。
升级、迁移、清理与退出
组件升级按“客户端兼容样本→staging 服务→双材料发布→小流量签名→历史验证→全量切换”推进。Fulcio、Rekor、TSA、TUF、protobuf 和 Cosign 是独立版本轴,Helm chart 版本也不等于应用版本。每次升级保存渲染清单、镜像 digest、配置摘要、数据库迁移记录、TUF targets 和成功/失败样本。
停止私有服务时先发布新的 SigningConfig,让签名客户端不再选择待退役端点;再等待活跃任务排空并封存最后 checkpoint。验证者继续持有旧 TrustedRoot 材料、Rekor shard、CT 证明、TSA chain、bundle 和 TUF metadata 历史。只有在隔离环境能重放代表性历史制品后,才销毁在线服务与可恢复私钥。
Helm 卸载前先导出 release manifest,识别 CRD、PVC、对象存储、数据库、KMS key 和外部 DNS。先撤销写权限和入口,再卸 API workload;存储与验证材料按保留策略转为只读封存。不要用 helm uninstall 代替证据归档,也不要在确认 Registry GC 策略前删除 OCI referrer。
迁往其他签名体系时建立双验证窗口:新系统产生新证据,旧系统只读验证;消费端用固定 digest 样本比较身份、时间、日志与声明语义。完成后撤销 OIDC audience、KMS Sign、TUF online 发布和服务账号权限,保留最小历史验证工具链与文档。退出成功的判据不是 Pod 清空,而是新制品只走新链、旧制品在保留期内仍可独立验证、错误样本继续 fail closed。
Sigstore Certificate Authority 概览。Sigstore Transparency Log 概览。Rekor v1 仓库与维护状态
Rekor v2 rekor-tiles。Sigstore Helm Charts。Sigstore root-signing 与 TUF 信任根
