强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Rekor 透明日志完整教程 / 04 - 架构详解

第 4 章:架构详解

本章深入剖析 Rekor 的内部架构,从 Merkle Tree 的数学原理到 Trillian 的工程实现,帮助你理解透明日志为什么能提供密码学级别的不可篡改保证。


4.1 整体架构

4.1.1 Rekor 系统架构图

┌────────────────────────────────────────────────────────────────────────┐
│                          Rekor 系统架构                                │
│                                                                        │
│  ┌─────────────┐     ┌──────────────────────────────────────────────┐ │
│  │  rekor-cli  │────►│           rekor-server (API)                │ │
│  │  cosign     │◄────│                                              │ │
│  │  其他客户端  │     │  ┌──────────┐  ┌──────────┐  ┌───────────┐ │ │
│  └─────────────┘     │  │ API 层   │  │ 条目类型  │  │ 验证逻辑  │ │ │
│                      │  │ (REST)   │  │ 处理器   │  │          │ │ │
│                      │  └────┬─────┘  └──────────┘  └───────────┘ │ │
│                      └───────┼──────────────────────────────────────┘ │
│                              │                                        │
│                              ▼                                        │
│                      ┌───────────────┐                                │
│                      │   Trillian    │  ← Google 透明日志框架         │
│                      │               │                                │
│                      │ ┌───────────┐ │                                │
│                      │ │ Log       │ │  ← Merkle Tree 存储          │
│                      │ │ Server    │ │                                │
│                      │ └─────┬─────┘ │                                │
│                      │       │       │                                │
│                      │ ┌─────▼─────┐ │                                │
│                      │ │ Log       │ │  ← 数据库存储后端              │
│                      │ │ Signer    │ │                                │
│                      │ └─────┬─────┘ │                                │
│                      └───────┼───────┘                                │
│                              │                                        │
│                              ▼                                        │
│                    ┌──────────────────┐                               │
│                    │  MySQL / PgSQL   │  ← 持久化存储                 │
│                    └──────────────────┘                               │
│                                                                        │
│                    ┌──────────────────┐                               │
│                    │      Redis       │  ← 缓存(可选)               │
│                    └──────────────────┘                               │
└────────────────────────────────────────────────────────────────────────┘

4.1.2 组件职责

组件职责关键特性
rekor-cli / cosign客户端,发起请求轻量、跨平台
rekor-serverAPI 服务,处理业务逻辑RESTful API、条目类型路由
Trillian Log ServerMerkle Tree 管理追加操作、哈希计算
Trillian Log Signer签名树头(Tree Head)保证日志的可验证性
MySQL / PostgreSQL持久化存储叶子节点、中间节点
Redis缓存加速读取、速率限制

4.2 Merkle Tree 深入解析

4.2.1 Merkle Tree 的数学基础

Merkle Tree(默克尔树),也称为哈希树(Hash Tree),是一种基于哈希函数的树形数据结构。

定义:给定 n 个数据块 $d_1, d_2, …, d_n$,Merkle Tree 通过以下方式构建:

叶子节点:H(d_i)  (i = 1, 2, ..., n)
内部节点:H(left_child || right_child)
根节点:H(root_left || root_right)

其中 H 是密码学安全的哈希函数(如 SHA-256),|| 表示拼接。

4.2.2 树的构建过程

以 8 个数据块为例:

层次 3 (根):          H(H₀₁ || H₂₃)
                    ┌────────┴────────┐
层次 2:        H(H₀ || H₁)      H(H₂ || H₃)
              ┌────┴────┐      ┌────┴────┐
层次 1:    H(d₀)  H(d₁)   H(d₂)  H(d₃)
            │       │       │       │
层次 0:    d₀      d₁      d₂      d₃

构建算法

import hashlib

def sha256(data: bytes) -> bytes:
    return hashlib.sha256(data).digest()

def build_merkle_tree(leaves: list[bytes]) -> bytes:
    """构建 Merkle Tree 并返回根哈希"""
    if not leaves:
        return sha256(b'')
    
    # 计算叶子哈希
    current_level = [sha256(leaf) for leaf in leaves]
    
    # 自底向上构建树
    while len(current_level) > 1:
        next_level = []
        for i in range(0, len(current_level), 2):
            left = current_level[i]
            # 如果节点数是奇数,复制最后一个节点
            right = current_level[i + 1] if i + 1 < len(current_level) else left
            next_level.append(sha256(left + right))
        current_level = next_level
    
    return current_level[0]

4.2.3 包含证明(Inclusion Proof)

包含证明用于证明某个叶子节点存在于 Merkle Tree 中,复杂度仅为 O(log n)。

证明路径:从叶子节点到根节点的路径上,每个节点的兄弟节点哈希值。

验证 d₁ 存在的证明路径: [H(d₀), H₂₃]

计算过程:
  step 1: H₀₁ = H(H(d₀) || H(d₁))  ← 使用证明中的 H(d₀)
  step 2: root = H(H₀₁ || H₂₃)     ← 使用证明中的 H₂₃
  step 3: 比较 root 是否等于已知的根哈希

验证算法

def verify_inclusion(
    leaf_hash: bytes,          # 叶子节点哈希
    proof_hashes: list[bytes], # 证明路径(兄弟节点哈希)
    leaf_index: int,           # 叶子节点索引
    root_hash: bytes,          # 预期的根哈希
    tree_size: int             # 树大小
) -> bool:
    """验证包含证明"""
    current_hash = leaf_hash
    index = leaf_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

4.2.4 一致性证明(Consistency Proof)

一致性证明用于证明两个不同时间点的 Merkle Tree 状态是一致的(即新的树是旧的树的追加扩展)。

时间 T1 的树 (size=4):    时间 T2 的树 (size=7):
      Root₁                     Root₂
     /    \                    /      \
   H₀₁    H₂₃              H₀₁₃    H₄₆
   / \    / \               / \      / \
  H₀ H₁ H₂ H₃           H₀₁ H₂₃  H₄  H₅₆
                               / \       / \
                              H₂  H₃   H₅  H₆

一致性证明: [H₂₃, H₀₁]
- 证明 T1 的 Root₁ 包含在 T2 的 Root₂ 中

4.2.5 安全性分析

属性保证攻击难度
抗原像攻击给定哈希值,无法找到原始数据O(2^256)
抗第二原像攻击给定数据,无法找到另一个具有相同哈希的数据O(2^256)
抗碰撞攻击无法找到两个具有相同哈希的不同数据O(2^128)
篡改检测任何修改都会改变根哈希即时检测

4.3 Trillian 框架

4.3.1 Trillian 是什么?

Trillian 是 Google 开发的一个透明日志框架,最初用于 Certificate Transparency(CT)日志。Rekor 基于 Trillian 构建,利用其提供的 Merkle Tree 管理能力。

4.3.2 Trillian 架构

┌──────────────────────────────────────────────────────────────┐
│                     Trillian 架构                            │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                  Trillian Log Server                   │  │
│  │                                                       │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌──────────────┐  │  │
│  │  │ Sequencer   │  │ Read API    │  │ Admin API    │  │  │
│  │  │ (排序器)    │  │ (读取接口)  │  │ (管理接口)   │  │  │
│  │  └──────┬──────┘  └──────┬──────┘  └──────────────┘  │  │
│  └─────────┼───────────────┼────────────────────────────┘  │
│            │               │                                │
│  ┌─────────▼───────────────▼────────────────────────────┐  │
│  │              Trillian Storage Interface               │  │
│  │                                                       │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │  │
│  │  │ MySQL    │  │ PostgreSQL│  │ Cloud Spanner    │   │  │
│  │  │ (默认)   │  │           │  │ (Google Cloud)   │   │  │
│  │  └──────────┘  └──────────┘  └──────────────────┘   │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Trillian Log Signer                       │  │
│  │                                                       │  │
│  │  - 定期将待处理的叶子节点合并到 Merkle Tree            │  │
│  │  - 计算新的根哈希                                     │  │
│  │  - 对树头(Tree Head)进行签名                        │  │
│  └───────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

4.3.3 Trillian 核心概念

概念说明
Log Tree透明日志树,只支持追加操作
Map Tree透明映射树,支持键值查询(Rekor 不使用)
Leaf叶子节点,存储实际数据的哈希
Tree Head树的当前状态(根哈希 + 大小 + 签名)
Sequencer排序器,将新叶子节点批量添加到树中
Signed Tree Head (STH)签名的树头,包含根哈希和数字签名

4.3.4 数据库表结构

Trillian 使用以下核心表:

-- 叶子节点表
CREATE TABLE LeafData (
    TreeID    BIGINT NOT NULL,
    LeafID    VARBINARY(255) NOT NULL,
    LeafValue BLOB NOT NULL,
    ExtraData BLOB,
    PRIMARY KEY (TreeID, LeafID)
);

-- Merkle 节点表
CREATE TABLE Subtree (
    TreeID    BIGINT NOT NULL,
    SubtreeID VARBINARY(255) NOT NULL,
    Nodes     BLOB NOT NULL,
    PRIMARY KEY (TreeID, SubtreeID)
);

-- 树头表
CREATE TABLE TreeHead (
    TreeID               BIGINT NOT NULL,
    TreeHeadTimestamp     BIGINT NOT NULL,
    TreeSize             BIGINT,
    RootHash             VARBINARY(255),
    RootSignature        VARBINARY(1024),
    PRIMARY KEY (TreeID, TreeHeadTimestamp)
);

-- 待处理的叶子节点
CREATE TABLE Unsequenced (
    TreeID    BIGINT NOT NULL,
    Bucket    INT NOT NULL,
    LeafID    VARBINARY(255) NOT NULL,
    QueueTimestampNanos BIGINT NOT NULL,
    PRIMARY KEY (TreeID, Bucket, QueueTimestampNanos, LeafID)
);

4.4 Rekor Server 详解

4.4.1 请求处理流程

客户端请求                   Rekor Server                     Trillian
    │                            │                                │
    │  POST /api/v1/log/entries  │                                │
    │ ─────────────────────────► │                                │
    │                            │                                │
    │                            │  1. 解析请求                    │
    │                            │  2. 识别条目类型                │
    │                            │  3. 验证签名格式                │
    │                            │  4. 计算条目哈希                │
    │                            │                                │
    │                            │  QueueLeaf (添加叶子)           │
    │                            │ ─────────────────────────────► │
    │                            │                                │
    │                            │  ◄─────────────────────────────│
    │                            │  返回叶子状态                   │
    │                            │                                │
    │                            │  5. 等待包含到树中              │
    │                            │  6. 获取包含证明                │
    │                            │                                │
    │  201 Created + 条目        │                                │
    │ ◄───────────────────────── │                                │

4.4.2 条目类型处理

Rekor 支持多种条目类型,每种类型都有对应的处理器:

// 简化的条目类型注册逻辑
package types

var TypeMap = map[string]EntryFactory{
    "hashedrekord": NewHashedRekord,
    "intoto":       NewIntoto,
    "dsse":         NewDSSE,
    "alpine":       NewAlpine,
    "cose":         NewCose,
    "jar":          NewJar,
    "helm":         NewHelm,
    "rfc3161":      NewRFC3161,
    "rpm":          NewRPM,
    "tuf":          NewTUF,
    "x509":         NewX509,
}

4.4.3 条目哈希计算

每个条目在写入前会被序列化并计算哈希,作为 Merkle Tree 的叶子值:

// 条目哈希计算(简化)
func EntryToLeaf(entry Entry) (*trillian.LogLeaf, error) {
    // 1. 序列化条目
    payload, err := entry.Canonicalize()
    if err != nil {
        return nil, err
    }
    
    // 2. 计算哈希
    hash := sha256.Sum256(payload)
    
    // 3. 创建叶子节点
    return &trillian.LogLeaf{
        LeafValue: payload,
        LeafIdentityHash: hash[:],
    }, nil
}

4.5 不可篡改性分析

4.5.1 为什么 Rekor 是不可篡改的?

Rekor 的不可篡改性来源于以下几个层面的保护:

┌─────────────────────────────────────────────────────────────┐
│                    不可篡改性保证层级                        │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  层级 1: Merkle Tree 结构                              │ │
│  │  - 修改任何叶子节点都会改变根哈希                       │ │
│  │  - 根哈希变化会被所有验证者检测到                       │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  层级 2: 签名的树头 (Signed Tree Head)                  │ │
│  │  - Trillian Log Signer 对每个树头进行数字签名           │ │
│  │  - 攻击者需要伪造签名才能修改历史                       │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  层级 3: 公开见证 (Public Witnessing)                   │ │
│  │  - 多个独立的见证者(Witness)保存树头快照              │ │
│  │  - 任何不一致都会被检测到                               │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  层级 4: 监控器 (Monitor)                               │ │
│  │  - 独立监控器持续监控日志状态                           │ │
│  │  - 检测任何回滚或篡改                                   │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

4.5.2 攻击场景分析

攻击类型可行性原因
修改历史条目❌ 不可行根哈希会变化,签名会失效
删除条目❌ 不可行树大小会减少,验证会失败
插入虚假条目⚠️ 部分可行条目写入后无法删除,但可被标记为无效
篡改数据库❌ 不可行树头签名独立于数据库
替换整个日志⚠️ 理论可行需要所有见证者和监控器同时被攻击
DDoS 攻击⚠️ 可行可通过私有部署和多实例缓解

4.5.3 Signed Tree Head (STH)

树头(Tree Head)是 Merkle Tree 当前状态的快照,由 Trillian Log Signer 进行数字签名:

{
  "timestamp": 1704067200000,
  "tree_size": 534218392,
  "root_hash": "4b8e2b5a45e3...",
  "signature": {
    "algorithm": "ECDSA",
    "value": "MEUCIQDx..."
  }
}

STH 的安全保证

  • 根哈希绑定了所有历史数据
  • 数字签名保证 STH 未被篡改
  • 时间戳证明 STH 的生成时间

4.6 读写路径分析

4.6.1 写入路径

客户端 ──► rekor-server ──► 验证条目
                             │
                             ▼
                        计算条目哈希
                             │
                             ▼
                     Trillian: QueueLeaf
                             │
                             ▼
                   写入 Unsequenced 表
                             │
                             ▼
                  Sequencer 批量读取
                             │
                             ▼
               更新 Merkle Tree (Subtree 表)
                             │
                             ▼
               计算新的根哈希
                             │
                             ▼
               Log Signer 签名树头
                             │
                             ▼
               写入 TreeHead 表

4.6.2 读取路径

客户端 ──► rekor-server ──► 读取叶子数据
                             │
                             ▼
                    从 LeafData 表获取
                             │
                             ▼
                    构建包含证明
                             │
                             ▼
                    获取当前树头
                             │
                             ▼
                    返回条目 + 包含证明

4.6.3 性能特征

操作时间复杂度典型延迟
写入条目O(log n)50-200ms
读取条目O(1)10-50ms
包含证明O(log n)20-100ms
一致性证明O(log n)20-100ms

4.7 数据流详解

4.7.1 完整数据流

┌──────────┐     ┌──────────────┐     ┌────────────┐     ┌──────────┐
│ 客户端   │     │ rekor-server │     │  Trillian  │     │ Database │
│          │     │              │     │            │     │          │
│ 签名数据 │────►│ 解析 + 验证  │────►│ QueueLeaf  │────►│ 存储     │
│          │     │              │     │            │     │          │
│          │     │ 计算哈希     │     │ Sequencer  │     │ 更新树   │
│          │     │              │     │ 批量合并   │◄───►│          │
│          │     │              │     │            │     │          │
│          │     │ 获取包含证明 │◄────│ GetEntryAnd│     │ 查询     │
│          │     │              │     │ Inclusion  │◄────│          │
│          │◄────│ 返回响应     │     │ Proof      │     │          │
└──────────┘     └──────────────┘     └────────────┘     └──────────┘

4.7.2 条目生命周期

阶段说明存储位置
提交客户端发送条目到 rekor-server内存缓冲区
排队条目被提交到 TrillianUnsequenced 表
排序Sequencer 分配序号内存
合并条目被合并到 Merkle TreeSubtree + LeafData 表
签名新的树头被签名TreeHead 表
返回包含证明返回给客户端

4.8 扩展性设计

4.8.1 水平扩展

Rekor 支持通过以下方式扩展:

┌────────────────────────────────────────────────────────┐
│                    负载均衡器                           │
└───────────────┬────────────┬───────────────────────────┘
                │            │
        ┌───────▼───┐ ┌──────▼────┐ ┌────────────┐
        │rekor-     │ │rekor-     │ │rekor-      │
        │server-01  │ │server-02  │ │server-03   │
        └───────┬───┘ └──────┬────┘ └──────┬─────┘
                │            │             │
                └────────────┼─────────────┘
                             │
                      ┌──────▼──────┐
                      │  Trillian   │ (单主实例)
                      │  Cluster    │
                      └──────┬──────┘
                             │
                      ┌──────▼──────┐
                      │  Database   │ (主从复制)
                      │  Cluster    │
                      └─────────────┘

4.8.2 读写分离

  • 读操作:可以水平扩展到多个 rekor-server 实例
  • 写操作:通过 Trillian 的 Sequencer 串行化,保证顺序一致性
  • 查询操作:通过索引数据库加速

4.9 与 Certificate Transparency 的对比

方面Certificate Transparency (CT)Rekor
目的审计 SSL/TLS 证书审计软件签名和构件
数据类型X.509 证书多种签名格式
后端Google TrillianGoogle Trillian
标准化RFC 6962Sigstore 规范
验证者浏览器 / CT Monitorcosign / 自定义客户端
公开程度完全公开完全公开(公共实例)
包含证明SCT (Signed Certificate Timestamp)包含证明(Inclusion Proof)

4.10 注意事项

单点故障:公共 Rekor 实例由 Sigstore 项目维护,对于高可用场景建议同时使用多个日志实例或部署私有实例。

Sequencer 延迟:Trillian 的 Sequencer 是批量处理的,条目从提交到实际包含在树中可能有几秒延迟。

存储增长:随着条目数量增长,数据库和 Merkle Tree 节点会持续增加。公共实例已有数十亿条目。

密码学更新:如果 SHA-256 被认为不再安全,需要迁移整个日志。目前 SHA-256 仍被认为安全。


4.11 本章小结

概念说明
Merkle Tree哈希二叉树,提供 O(log n) 的包含证明
TrillianGoogle 的透明日志框架,Rekor 的后端
Sequencer批量将新叶子合并到树中
Signed Tree Head对树头的数字签名,防篡改保证
包含证明从叶子到根的路径上兄弟节点哈希
一致性证明证明两个树状态的兼容性
不可篡改Merkle Tree + 签名树头 + 公开见证

扩展阅读


下一章05 - 签名集成 — 学习如何使用 cosign 和 Sigstore 进行签名集成。