Rekor 透明日志完整教程 / 06 - 验证机制
第 6 章:验证机制
本章详细介绍 Rekor 的各种验证机制,包括签名验证、日志状态验证、包含证明验证、一致性审计以及批量验证等核心能力。
6.1 验证层次概览
Rekor 的验证分为多个层次,每层提供不同级别的保证:
┌─────────────────────────────────────────────────────────────┐
│ 验证层次金字塔 │
│ │
│ ┌───────────────┐ │
│ │ 一致性证明 │ ← 证明日志在不同 │
│ │ (Consistency)│ 时间点之间一致 │
│ └───────┬───────┘ │
│ ┌────────┴────────┐ │
│ │ 包含证明 │ ← 证明条目确实 │
│ │ (Inclusion) │ 存在于日志中 │
│ └────────┬────────┘ │
│ ┌───────────┴───────────┐ │
│ │ 日志状态验证 │ ← 验证树头 │
│ │ (Tree Head) │ 签名 │
│ └───────────┬───────────┘ │
│ ┌──────────────┴──────────────┐ │
│ │ 签名验证 │ ← 验证数字 │
│ │ (Signature) │ 签名 │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| 层次 | 验证内容 | 保证 |
|---|---|---|
| 签名验证 | 数字签名是否有效 | 签名者身份正确,数据未被篡改 |
| 日志状态验证 | 树头签名是否有效 | 日志状态由可信方签名 |
| 包含证明 | 条目是否在 Merkle Tree 中 | 条目确实被记录在日志中 |
| 一致性证明 | 两个树状态是否兼容 | 日志未被回滚或篡改 |
6.2 签名验证
6.2.1 验证 cosign 签名
# 验证容器镜像签名(密钥模式)
cosign verify \
--key cosign.pub \
ghcr.io/myorg/myimage:v1.0.0
# 预期输出:
# Verification for ghcr.io/myorg/myimage:v1.0.0 --
# The following checks were performed:
# - The cosign claims were validated
# - The signatures were verified against the specified public key
# - Any certificates were verified against the Fulcio roots.
#
# [{"critical":{"identity":{"docker-reference":"ghcr.io/myorg/myimage"},...}}]
6.2.2 验证无密钥签名
# 验证无密钥签名(需要指定身份信息)
cosign verify \
--certificate-identity=[email protected] \
--certificate-oidc-issuer=https://accounts.google.com \
ghcr.io/myorg/myimage:v1.0.0
# 验证 GitHub Actions 签名
cosign verify \
--certificate-identity=https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/myorg/myimage:v1.0.0
# 验证带通配符的证书身份
cosign verify \
--certificate-identity-regexp=".*@mycompany.com$" \
--certificate-oidc-issuer-regexp="https://accounts.google.com" \
ghcr.io/myorg/myimage:v1.0.0
6.2.3 验证 Blob 文件签名
# 验证文件签名
cosign verify-blob \
--key cosign.pub \
--signature myartifact.sig \
--certificate myartifact.cert \
myartifact.tar.gz
# 使用无密钥签名验证 Blob
cosign verify-blob \
--certificate-identity=[email protected] \
--certificate-oidc-issuer=https://accounts.google.com \
--signature myartifact.sig \
--certificate myartifact.cert \
myartifact.tar.gz
6.2.4 签名验证检查项
| 检查项 | 说明 | 失败原因 |
|---|---|---|
| 签名数学验证 | 签名是否由对应私钥生成 | 签名被篡改或使用错误的公钥 |
| 证书有效期 | 证书是否在有效期内 | 签名时间超出证书有效期 |
| 证书链验证 | 证书是否由可信 CA 颁发 | CA 不可信或证书链不完整 |
| 身份匹配 | 证书中的身份是否匹配 | 预期身份与实际身份不符 |
| OIDC 颁发者匹配 | 证书中的 OIDC 颁发者是否匹配 | 颁发者不匹配 |
| Rekor 日志记录 | 签名是否已记录在 Rekor 中 | 签名未上传到透明日志 |
6.3 日志状态验证
6.3.1 获取日志状态
# 获取公共 Rekor 的日志信息
rekor-cli loginfo
# 输出示例:
# [rekor root hash]: 4b8e2b5a45e3abc...
# [tree size]: 534,218,392
# [tree ID]: 3747744012
# 获取 JSON 格式
rekor-cli loginfo --format=json
# 使用 API
curl -s https://rekor.sigstore.dev/api/v1/log | jq '{
rootHash: .rootHash,
treeSize: .treeSize,
signedTreeHead: .signedTreeHead
}'
6.3.2 验证树头签名
树头(Tree Head)由 Trillian Log Signer 签名,确保日志状态未被篡改:
# 使用 API 获取签名的树头
curl -s https://rekor.sigstore.dev/api/v1/log | jq '.signedTreeHead'
# 验证树头签名(使用 Rekor 公钥)
# Rekor 的公钥可以通过 TUF 获取
cosign initialize
# 公钥缓存在 ~/.sigstore/root/ 目录下
6.3.3 树头验证内容
{
"treeSize": 534218392,
"rootHash": "4b8e2b5a45e3abc...",
"timestampNanos": 1704067200000000000,
"signature": {
"keyId": "rekor-signer-key",
"algorithm": "ecdsa-sha2-nistp256",
"value": "MEUCIQDx..."
}
}
| 字段 | 说明 |
|---|---|
treeSize | 树中叶子节点总数 |
rootHash | 当前根哈希 |
timestampNanos | 树头生成时间(纳秒精度) |
signature | 对树头数据的数字签名 |
6.4 包含证明验证
6.4.1 获取包含证明
# 通过 rekor-cli 验证条目包含
rekor-cli verify --log-index=12345678
# 通过 API 获取包含证明
curl -s "https://rekor.sigstore.dev/api/v1/log/entries/<ENTRY_UUID>/proof" | jq '.'
# 获取条目详情(包含包含证明)
rekor-cli get --log-index=12345678 --format=json | jq '.verification.inclusionProof'
6.4.2 包含证明的结构
{
"hashes": [
"a1b2c3d4e5f6...",
"b2c3d4e5f6a1...",
"c3d4e5f6a1b2...",
"d4e5f6a1b2c3...",
"e5f6a1b2c3d4..."
],
"logIndex": 12345678,
"rootHash": "4b8e2b5a45e3abc...",
"treeSize": 534218392,
"checkpoint": {
"envelope": "rekor.sigstore.dev ...\ntree size: 534218392\n..."
}
}
6.4.3 手动验证包含证明
使用 Python 脚本手动验证包含证明的正确性:
#!/usr/bin/env python3
"""
手动验证 Rekor 包含证明
"""
import hashlib
import json
import sys
import requests
def sha256(data: bytes) -> bytes:
return hashlib.sha256(data).digest()
def verify_inclusion_proof(
leaf_hash: bytes,
proof_hashes: list[bytes],
log_index: int,
root_hash: bytes,
) -> bool:
"""验证包含证明"""
current_hash = leaf_hash
index = log_index
for sibling_hash in proof_hashes:
if index % 2 == 0:
current_hash = sha256(current_hash + sibling_hash)
else:
current_hash = sha256(sibling_hash + current_hash)
index //= 2
return current_hash == root_hash
def main():
# 获取条目
entry_uuid = sys.argv[1] if len(sys.argv) > 1 else None
if not entry_uuid:
print("Usage: python verify_inclusion.py <entry_uuid>")
sys.exit(1)
# 从 Rekor 获取条目
resp = requests.get(
f"https://rekor.sigstore.dev/api/v1/log/entries/{entry_uuid}"
)
entry = resp.json()
# 提取包含证明
proof = entry["verification"]["inclusionProof"]
leaf_hash = bytes.fromhex(entry["body"]) # 简化处理
# 验证
proof_hashes = [bytes.fromhex(h) for h in proof["hashes"]]
root_hash = bytes.fromhex(proof["rootHash"])
result = verify_inclusion_proof(
leaf_hash=sha256(leaf_hash.encode()),
proof_hashes=proof_hashes,
log_index=proof["logIndex"],
root_hash=root_hash,
)
if result:
print("✅ 包含证明验证成功")
else:
print("❌ 包含证明验证失败")
sys.exit(1)
if __name__ == "__main__":
main()
6.4.4 包含证明验证流程图
输入: entry_hash, proof_hashes[], log_index, expected_root_hash
entry_hash
│
▼
┌───────────────────┐
│ log_index = 12 │ (二进制: 1100)
│ current = entry │
└───────┬───────────┘
│
┌───────▼───────────┐
│ Step 1: index=12 │
│ 12 % 2 == 0 │
│ 12 / 2 = 6 │
│ current = H(current + hash[0])
└───────┬───────────┘
│
┌───────▼───────────┐
│ Step 2: index=6 │
│ 6 % 2 == 0 │
│ 6 / 2 = 3 │
│ current = H(current + hash[1])
└───────┬───────────┘
│
┌───────▼───────────┐
│ Step 3: index=3 │
│ 3 % 2 == 1 │
│ 3 / 2 = 1 │
│ current = H(hash[2] + current)
└───────┬───────────┘
│
┌───────▼───────────┐
│ Step 4: index=1 │
│ 1 % 2 == 1 │
│ 1 / 2 = 0 │
│ current = H(hash[3] + current)
└───────┬───────────┘
│
┌───────▼───────────┐
│ current == root? │
│ ✅ 或 ❌ │
└───────────────────┘
6.5 一致性证明
6.5.1 什么是一致性证明?
一致性证明用于验证两个不同时间点的 Merkle Tree 状态是否兼容,即新树是旧树的超集(只追加操作)。
6.5.2 获取一致性证明
# 获取一致性证明(从树大小 firstSize 到 lastSize)
curl -s "https://rekor.sigstore.dev/api/v1/log/proof?firstSize=1000&lastSize=2000" | jq '.'
# 预期输出:
# {
# "hashes": ["...", "...", ...],
# "firstSize": 1000,
# "lastSize": 2000
# }
6.5.3 一致性证明验证
def verify_consistency_proof(
first_hash: bytes, # 旧树根哈希
second_hash: bytes, # 新树根哈希
first_size: int, # 旧树大小
second_size: int, # 新树大小
proof_hashes: list[bytes], # 一致性证明哈希
) -> bool:
"""验证一致性证明"""
# 使用 RFC 6962 中的验证算法
# 简化实现,实际验证逻辑更复杂
if first_size == second_size:
return first_hash == second_hash
if first_size > second_size:
return False
# ... 完整验证逻辑参见 RFC 6962
# https://datatracker.ietf.org/doc/html/rfc6962#section-8.1
pass
6.5.4 一致性证明的使用场景
| 场景 | 说明 |
|---|---|
| 监控器 | 定期获取树头,验证与上次状态的一致性 |
| 审计 | 证明日志在审计期间未被篡改 |
| 故障恢复 | 验证系统恢复后日志状态的正确性 |
| 多实例同步 | 验证不同 Rekor 实例的日志一致性 |
6.6 审计流程
6.6.1 完整审计流程
#!/bin/bash
# Rekor 日志审计脚本
set -e
REKOR_SERVER="https://rekor.sigstore.dev"
AUDIT_DIR="./rekor-audit-$(date +%Y%m%d)"
mkdir -p "$AUDIT_DIR"
echo "=== Rekor 日志审计 ==="
echo "审计目录: $AUDIT_DIR"
# 步骤 1: 记录当前日志状态
echo ">>> 步骤 1: 记录当前日志状态"
curl -s "$REKOR_SERVER/api/v1/log" | jq '.' > "$AUDIT_DIR/log-status.json"
cat "$AUDIT_DIR/log-status.json" | jq '{treeSize, rootHash}'
# 步骤 2: 获取日志信息
echo ">>> 步骤 2: 获取日志信息"
rekor-cli loginfo > "$AUDIT_DIR/loginfo.txt"
cat "$AUDIT_DIR/loginfo.txt"
# 步骤 3: 验证特定条目
echo ">>> 步骤 3: 验证条目"
ENTRY_INDEX=12345678
rekor-cli get --log-index=$ENTRY_INDEX --format=json > "$AUDIT_DIR/entry-$ENTRY_INDEX.json"
rekor-cli verify --log-index=$ENTRY_INDEX && echo "✅ 条目 $ENTRY_INDEX 包含证明验证通过" || echo "❌ 验证失败"
# 步骤 4: 记录审计时间戳
echo ">>> 步骤 4: 记录审计时间"
echo "审计时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" > "$AUDIT_DIR/audit-timestamp.txt"
echo "审计员: $(whoami)" >> "$AUDIT_DIR/audit-timestamp.txt"
# 步骤 5: 生成审计报告
echo ">>> 步骤 5: 生成审计报告"
cat > "$AUDIT_DIR/audit-report.md" << EOF
# Rekor 审计报告
## 基本信息
- 审计时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')
- Rekor 服务器: $REKOR_SERVER
- 树大小: $(jq -r '.treeSize' "$AUDIT_DIR/log-status.json")
- 根哈希: $(jq -r '.rootHash' "$AUDIT_DIR/log-status.json")
## 验证结果
- 条目 $ENTRY_INDEX: $(rekor-cli verify --log-index=$ENTRY_INDEX 2>/dev/null && echo "✅ 通过" || echo "❌ 失败")
EOF
echo "=== 审计完成 ==="
echo "审计文件保存在: $AUDIT_DIR/"
6.6.2 审计检查清单
| 检查项 | 命令/方法 | 预期结果 |
|---|---|---|
| 日志可访问 | rekor-cli loginfo | 返回树大小和根哈希 |
| 树头签名有效 | API 获取 + 公钥验证 | 签名验证通过 |
| 条目包含证明 | rekor-cli verify --log-index=N | 证明验证通过 |
| 条目签名有效 | cosign verify | 签名验证通过 |
| 根哈希一致 | 对比不同时间点的根哈希 | 只增不减 |
| 树大小增长 | 对比不同时间点的树大小 | 只增不减 |
6.7 批量验证
6.7.1 批量验证多个镜像
#!/bin/bash
# 批量验证容器镜像签名
IMAGES=(
"ghcr.io/myorg/app:v1.0.0"
"ghcr.io/myorg/app:v1.1.0"
"ghcr.io/myorg/service:latest"
"ghcr.io/myorg/api:v2.0.0"
)
CERTIFICATE_IDENTITY="https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main"
CERTIFICATE_OIDC_ISSUER="https://token.actions.githubusercontent.com"
PASS_COUNT=0
FAIL_COUNT=0
for image in "${IMAGES[@]}"; do
echo -n "验证 $image ... "
if cosign verify \
--certificate-identity="$CERTIFICATE_IDENTITY" \
--certificate-oidc-issuer="$CERTIFICATE_OIDC_ISSUER" \
"$image" > /dev/null 2>&1; then
echo "✅ 通过"
((PASS_COUNT++))
else
echo "❌ 失败"
((FAIL_COUNT++))
fi
done
echo ""
echo "=== 验证结果 ==="
echo "通过: $PASS_COUNT"
echo "失败: $FAIL_COUNT"
echo "总计: ${#IMAGES[@]}"
if [ $FAIL_COUNT -gt 0 ]; then
exit 1
fi
6.7.2 批量验证 Blob 文件
#!/bin/bash
# 批量验证文件签名
KEY_FILE="cosign.pub"
ARTIFACT_DIR="./artifacts"
SIG_DIR="./signatures"
PASS_COUNT=0
FAIL_COUNT=0
for artifact in "$ARTIFACT_DIR"/*.tar.gz; do
basename=$(basename "$artifact")
sig_file="$SIG_DIR/${basename}.sig"
cert_file="$SIG_DIR/${basename}.cert"
if [ ! -f "$sig_file" ]; then
echo "⚠️ 签名文件不存在: $sig_file"
continue
fi
echo -n "验证 $basename ... "
if cosign verify-blob \
--key "$KEY_FILE" \
--signature "$sig_file" \
--certificate "$cert_file" \
"$artifact" > /dev/null 2>&1; then
echo "✅"
((PASS_COUNT++))
else
echo "❌"
((FAIL_COUNT++))
fi
done
echo ""
echo "=== 结果: $PASS_COUNT 通过, $FAIL_COUNT 失败 ==="
6.7.3 批量验证 Rekor 条目
#!/bin/bash
# 批量验证 Rekor 条目包含证明
INDICES=(12345678 12345679 12345680 12345681 12345682)
for index in "${INDICES[@]}"; do
echo -n "验证条目 $index ... "
if rekor-cli verify --log-index=$index > /dev/null 2>&1; then
echo "✅"
else
echo "❌"
fi
done
6.8 验证错误处理
6.8.1 常见验证错误
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
no matching signatures | 签名不匹配 | 检查公钥是否正确 |
certificate identity mismatch | 证书身份不匹配 | 检查 --certificate-identity 参数 |
certificate OIDC issuer mismatch | 颁发者不匹配 | 检查 --certificate-oidc-issuer 参数 |
entry not found in the transparency log | 条目不在日志中 | 检查条目 UUID 或索引 |
inclusion proof verification failed | 包含证明无效 | 条目可能已被篡改或日志不一致 |
certificate has expired | 证书已过期 | 对于无密钥签名,检查 integratedTime |
6.8.2 调试验证问题
# 启用详细日志
COSIGN_EXPERIMENTAL=1 cosign verify \
--certificate-identity=[email protected] \
--certificate-oidc-issuer=https://accounts.google.com \
--output=text \
ghcr.io/myorg/myimage:v1.0.0 2>&1 | head -50
# 查看证书详情
cosign verify \
--certificate-identity=[email protected] \
--certificate-oidc-issuer=https://accounts.google.com \
--output-certificate=/tmp/cert.pem \
ghcr.io/myorg/myimage:v1.0.0
# 解析证书
openssl x509 -in /tmp/cert.pem -text -noout
6.8.3 验证失败的安全影响
| 失败类型 | 安全影响 | 建议操作 |
|---|---|---|
| 签名验证失败 | 镜像可能被篡改 | 阻止部署,调查来源 |
| 包含证明失败 | 日志可能不一致 | 切换到其他 Rekor 实例 |
| 证书过期 | 验证时间晚于证书有效期 | 检查集成时间戳 |
| 身份不匹配 | 镜像来源不可信 | 确认构建流程 |
6.9 验证策略
6.9.1 分层验证策略
# Kubernetes 验证策略示例
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: image-verification-policy
spec:
images:
- glob: "ghcr.io/myorg/**"
authorities:
# 策略 1: 无密钥签名验证
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: ".*myorg/myrepo.*"
ctlog:
url: https://rekor.sigstore.dev
tuf:
url: https://tuf-repo-cdn.sigstore.dev
# 策略 2: 密钥签名验证(备选)
- key:
data: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
6.9.2 验证策略对比
| 策略 | 安全性 | 灵活性 | 适用场景 |
|---|---|---|---|
| 仅验证签名 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 开发环境 |
| 签名 + Rekor | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 测试环境 |
| 签名 + 身份 + Rekor | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 生产环境 |
| 多重签名 | ⭐⭐⭐⭐⭐ | ⭐⭐ | 关键系统 |
6.10 注意事项
时钟同步:包含证明和证书验证依赖准确的时间。确保验证系统与 NTP 服务器同步。
公共实例依赖:验证公共 Rekor 的条目需要能够访问
rekor.sigstore.dev。内网环境应部署私有实例或缓存。
证书有效期理解:无密钥签名的 Fulcio 证书只有 10 分钟有效期,但签名记录在 Rekor 中后可以通过
integratedTime验证签名时证书有效。
批量验证的速率限制:公共实例有速率限制,大量验证请考虑使用本地缓存或私有实例。
6.11 本章小结
| 验证类型 | 工具/命令 | 用途 |
|---|---|---|
| 签名验证 | cosign verify | 验证数字签名的有效性 |
| 日志状态验证 | rekor-cli loginfo | 验证树头签名 |
| 包含证明 | rekor-cli verify | 证明条目在日志中 |
| 一致性证明 | API /log/proof | 验证日志未被回滚 |
| 批量验证 | 自定义脚本 | 批量验证多个构件 |
扩展阅读
下一章:07 - CI/CD 集成 — 将 Rekor 集成到 GitHub Actions、GitLab CI 等 CI/CD 流水线中。