transparency-dev/tessera
GitHub: transparency-dev/tessera
Tessera 是一个用于构建基于瓦片的透明日志的 Go 库,是 Trillian v1 的继任者,旨在以更低的运维成本提供可验证的仅追加日志存储能力。
Stars: 161 | Forks: 42
# Tessera
[](https://goreportcard.com/report/github.com/transparency-dev/tessera)
[](https://scorecard.dev/viewer/?uri=github.com/transparency-dev/tessera)
[](https://transparency-dev.github.io/tessera/dev/bench/)
[](https://transparency-dev.slack.com/)
Tessera 是一个用于构建[基于瓦片的透明日志 https://c2sp.org/tlog-tiles] 的 Go 库。
它是 [Trillian v1][] 在构建和操作日志方面所采用方法的逻辑继承者。
该实现及其 API 融入了
[基于过去十年在生产环境中大规模构建和操作透明日志所获经验得出的当前最佳实践](https://transparency.dev/articles/tile-based-logs/)
。
Tessera 于 2024 年 10 月的 Transparency.Dev 峰会上推出。
观看 [Introducing Tessera](https://www.youtube.com/watch?v=9j_8FbQ9qSc) 了解所有细节,
但以下是高级目标的摘要:
* [tlog-tiles API][] 和存储
* 支持云和本地基础设施
* [GCP](./storage/gcp/)
* [AWS](./storage/aws/)
* [MySQL](./storage/mysql/)
* [POSIX](./storage/posix/)
* 使在受支持的基础设施上构建和部署新的透明日志变得容易
* 库而非微服务架构
* 无需管理额外的服务
* 与 Trillian v1 相比,运营商的总拥有成本 (TCO) 更低
* 快速的条目排序 和集成
* 可选功能,可针对需要它们的生态系统/日志启用(仅为您需要的功能付费):
* 条目的“尽力而为”去重
* 同步集成
* 与 Trillian v1 相比,写入吞吐量和写入可用性大致相似,而读取吞吐量和读取可用性可能_远_高(取决于底层基础设施)
* 支持构建任意的日志个性化,包括支持符合
[Static CT API][] 规范的日志的特殊性。
主要的非目标是支持使用除 [tlog-tiles API][] 以外任何方式的透明日志。
虽然可以在 Tessera 前部署自定义个性化,将 tlog-tiles API 适配
为任何其他 API,但这种策略将失去 Tessera 设计旨在实现的许多读取扩展能力。
## 目录
- [状态](#status)
- [路线图](#roadmap)
- [概念](#concepts)
- [用法](#usage)
- [快速入门](#getting-started)
- [编写个性化](#writing-personalities)
- [功能](#features)
- [生命周期](#lifecycles)
- [贡献](#contributing)
- [许可证](#license)
- [联系](#contact)
## 状态
Tessera 正处于活跃开发中,并且自
[Beta 发布](https://github.com/transparency-dev/tessera/releases/tag/v0.2.0) 起
被认为是生产就绪的。
有关详细信息,请参阅下表。
### 存储驱动
| Driver | Appender | Migration | Antispam | Garbage Collection | Notes |
| ----------------------- | :------: | :-------: | :------: | :----------------: | --------------------------------------------- |
| Amazon Web Services | ✅ | ⚠️ | ✅ | ✅ | |
| Google Cloud Platform | ✅ | ⚠️ | ✅ | ✅ | |
| POSIX filesystem | ✅ | ⚠️ | ✅ | ✅ | |
| MySQL | ⚠️ | ⚠️ | ❌ | N/A | MySQL 暂时将保持 BETA 状态。 |
欢迎 GCP、AWS、MySQL 和 POSIX 的用户尝试相应的 [快速入门](#getting-started) 指南。
## 路线图
预计 2025 年中期生产就绪。
| # | Step | Status |
| :-: | --------------------------------------------------------- | :----: |
| 1 | GCP、AWS、MySQL 和 POSIX 的驱动程序 | ✅ |
| 2 | [tlog-tiles API][] 支持 | ✅ |
| 3 | 示例代码和 terraform 脚本以便于上手 | ✅ |
| 4 | 稳定的 API | ✅ |
| 5 | 版本间的数据迁移 | ✅ |
| 6 | 驱动程序间的数据迁移 | ✅ |
| 7 | 见证 支持 | ✅ |
| 8 | 监控和指标 | ✅ |
| 9 | 生产就绪 | ✅ |
| 10 | 镜像日志 (#576) | ⚠️ |
| 11 | 预排序日志 (#575) | ❌ |
| 12 | Trillian v1 到 Tessera 的迁移 (#577) | ❌ |
| N | 高级功能(稍后详述) | ❌ |
当前的 API 不太可能发生重大变化,但在我们标记 1.0 版本之前,API 可能会有轻微的破坏性变更。
### Trillian v1 会怎样?
[Trillian v1][] 仍在多个生态系统的多个组织中的
生产环境中使用,并且在中期内很可能保持这种状态。
新的生态系统,或希望演进的现有生态系统,应强烈考虑规划
迁移到 Tessera 并采用它鼓励的模式。
## 概念
本节介绍将在整个用户指南中使用的概念和术语。
### 排序
当数据添加到日志时,它首先在内存中存储一段时间(可以通过 [batching options](https://pkg.go.dev/github.com/transparency-dev/tessera#WithBatching) 进行控制)。
如果进程在此状态下死亡,条目将丢失。
一旦一批条目被排序器 处理,新数据将从易失状态转换为被持久分配索引的状态。
如果进程在此状态下死亡,条目将是安全的,尽管在叶子 被[集成](#integration)之前,它将无法通过日志的读取 API 使用。
一旦索引号被分配给叶子,就没有其他数据会被分配相同的索引号。
所有索引号都是连续的,并且从 0 开始。
### 集成
集成是一个后台过程,当 Tessera 生命周期对象被创建时发生。
此过程获取已排序 的条目并将它们合并到日志中。
一旦此过程完成,一个新条目将:
- 可通过读取 API 在排序返回的索引处访问
- 拥有 Merkle 树哈希,这些哈希承诺将此数据包含在树中
### 发布
发布是一个后台过程,它为最新的树创建一个新的 Checkpoint。
此后台进程定期运行(可通过
[WithCheckpointInterval](https://pkg.go.dev/github.com/transparency-dev/tessera#AppendOptions.WithCheckpointInterval)
和
[WithCheckpointRepublishInterval](https://pkg.go.dev/github.com/transparency-dev/tessera#AppendOptions.WithCheckpointRepublishInterval) 配置)
并执行以下步骤:
1. 创建一个新的 Checkpoint 并使用 [WithCheckpointSigner](https://pkg.go.dev/github.com/transparency-dev/tessera#AppendOptions.WithCheckpointSigner) 提供的签名器对其进行签名
2. 联系见证者 并收集足够的副签名 以满足由 [WithWitnesses](https://pkg.go.dev/github.com/transparency-dev/tessera#AppendOptions.WithWitnesses) 配置的任何见证策略
3. 如果满足见证策略,则公开此新 Checkpoint
一旦条目被发布的 Checkpoint 承诺(即发布的 Checkpoint 的大小大于条目的分配索引),该条目即被视为已发布。
由于仅追加日志的性质,此之后发出的所有 Checkpoint 也将承诺包含此条目。
## 用法
### 快速入门
最佳起点是 [codelab](./cmd/conformance#codelab)。
它将引导您完成设置第一个日志、通过 HTTP 向其写入一些条目以及检查内容的步骤。
查看 `/cmd/` 目录中的示例个性化:
- [posix](./cmd/conformance/posix/):操作由本地文件系统支持的日志的示例
- 此示例运行一个 HTTP Web 服务器,该服务器接收任意数据并将其添加到基于文件的日志中。
- [mysql](./cmd/conformance/mysql/):操作使用 MySQL 的日志的示例
- 此示例最容易通过 `docker compose` 部署,它允许轻松设置和拆除。
- [gcp](./cmd/conformance/gcp/):在 GCP 中运行的日志的操作示例。
- 此示例可以通过 terraform 部署,请参阅 [部署说明](./deployment/live/gcp/conformance#manual-deployment)。
- [aws](./cmd/conformance/aws/):在 AWS 上运行的日志的操作示例。
- 此示例可以通过 terraform 部署,请参阅 [部署说明](./deployment/live/aws/codelab#aws-codelab-deployment)。
- [posix-oneshot](./cmd/examples/posix-oneshot/):用于将条目添加到存储在本地文件系统上的日志的命令行工具示例
- 此示例不是长期运行的进程;运行该命令会将条目集成到仅作为文件存在的日志中。
这些示例个性化中的每一个的 `main.go` 文件都试图在展示 Tessera 的功能时,在简单性与展示最佳实践之间取得平衡。
如果您有使示例更易于访问的想法,请针对仓库提出 issue,或在 [Slack](#contact) 中与我们聊天!
### 编写个性化
#### 简介
Tessera 是一个用 Go 编写的库。
它旨在高效地服务于允许通过 [tlog-tiles API][] 进行读取访问的日志。
您编写的调用 Tessera 的代码被称为个性化,因为它使通用库适应您的生态系统。
在开始编写您自己的个性化之前,强烈建议您熟悉 [快速入门](#getting-started) 中引用的提供的个性化。
在编写 Tessera 个性化时,您需要做出的第一个决定是使用哪个原生驱动程序:
* [GCP](./storage/gcp/)
* [AWS](./storage/aws/)
* [MySQL](./storage/mysql/)
* [POSIX](./storage/posix/)
最容易操作和扩展的驱动程序是云实现:GCP 和 AWS。
这些是大多数在生产中运行的用户推荐的选择。
如果您不使用云提供商,那么您的选择是 MySQL 和 POSIX:
- POSIX 是最简单的入门选择,因为它几乎不需要额外的基础设施,而且如果您已经作为业务/项目的一部分提供静态文件服务,这可能是一个很好的选择。
- 或者,如果您习惯于操作由 RDBMS 支持的面向用户的应用程序,那么 MySQL 可能是一个自然的选择。
要了解不同后端预期的大致性能,请查看
[docs/performance.md](/docs/performance.md)。
#### 设置
一旦您选择了存储驱动程序,您就可以开始编写您的个性化了!
您需要导入 Tessera 库:
```
# 这会在 main 处导入库。
# 这应该设置为最新 release 版本以获得稳定 release。
go get github.com/transparency-dev/tessera@main
```
#### 构建 Appender
导入主 `tessera` 包和您要使用的存储后端的驱动程序:
```
"github.com/transparency-dev/tessera"
// Choose one!
"github.com/transparency-dev/tessera/storage/posix"
// "github.com/transparency-dev/tessera/storage/aws"
// "github.com/transparency-dev/tessera/storage/gcp"
// "github.com/transparency-dev/tessera/storage/mysql"
```
现在您需要为您正在使用的原生驱动程序实例化生命周期对象。
到目前为止,操作日志最常见的方式是以仅追加 的方式进行,本指南的其余部分将讨论
这种模式。
对于 Appender 模式以外的生命周期状态,请查看下面的 [生命周期](#lifecycles)。
以下是为 POSIX 驱动程序创建 `Appender` 的示例:
```
driver, _ := posix.New(ctx, "/tmp/mylog")
signer := createSigner()
appender, shutdown, reader, err := tessera.NewAppender(
ctx, driver, tessera.NewAppendOptions().WithCheckpointSigner(signer))
```
请参阅每个驱动程序实现的文档,以了解每个驱动程序接受的参数。
配置 Tessera 的最后一部分是设置您想要使用的附加功能。
这些可选库可用于提供常见的日志行为。
阅读完本节的其余部分后,请参阅 [功能](#features) 以了解更多详细信息。
#### 写入日志
现在您应该已经为您的环境配置了具有正确功能设置的 Tessera 实例。
现在是有趣的部分 - 写入日志!
```
appender, shutdown, reader, err := tessera.NewAppender(
ctx, driver, tessera.NewAppendOptions().WithCheckpointSigner(signer))
if err != nil {
panic(err)
}
index, err := appender.Add(ctx, tessera.NewEntry(data))()
```
`AppendOptions` 允许调整 Tessera 行为。
查看根包中 `AppendOptions` 结构体上名为 `With*` 的方法,例如 [`WithBatching`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#AppendOptions.WithBatching),以了解可用的选项以及如何使用它们。
写入日志遵循以下流程:
1. 使用作为日志中叶子创建的新条目调用 `Add`。
- 此方法返回一个 _future_,形式为 `func() (Index, error)`。
2. 调用此 future 函数,该函数将阻塞,直到传递给 `Add` 的数据已排序
- 成功时,_持久地_ 分配并返回一个索引号
- 失败时,返回错误
一旦返回索引,新数据即已排序,但不一定已集成到日志中。
正如上面 [集成](#integration) 中讨论的那样,已排序 的条目将被 _异步_ 集成到日志中,并通过读取 API 提供。
某些个性化可能需要阻塞直到执行此操作,例如,因为它们将向请求者提供包含证明,这需要集成。
建议此类个性化使用 [同步发布](#synchronous-publication) 来执行此阻塞。
#### 从日志读取
写入日志的数据需要供客户端和验证器使用。
Tessera 通过 [tlog-tiles API][] 使日志可读。
对于 AWS 和 GCP,要服务的数据被写入对象存储并由云提供商直接服务。
日志操作员只需要确保这些对象存储实例是公开可读的,并设置一个指向它们的 URL。
对于 MySQL 和 POSIX,日志操作员需要采取更多步骤来使数据可用。
POSIX 完全按照 API 规范写出文件,因此日志操作员可以通过 HTTP 文服务器提供服务。
MySQL 是一个特殊的实现,因为它需要个性化代码来处理读取流量。
请参阅为 MySQL 编写的示例个性化,以了解如何配置此 Go Web 服务器。
## 功能
### 反垃圾
在某些情况下,特别是在日志可公开写入的情况下(如证书透明度),日志可能会被要求
无论是恶意还是意外地添加它们已经包含的条目。通常,这是不可取的,因此 Tessera 提供了一个
可选机制,试图在尽力而为的基础上检测并忽略重复条目。
不允许直接向日志公开提交的日志可能希望在没有此可选反垃圾措施的情况下运行,而是依赖
个性化永远不会生成重复项。这可以显著降低运营成本并提高写入吞吐量。
反垃圾机制由两层组成,它们位于存储的底层 `Add` 实现之前:
1. 第一层是一个 `InMemory` 缓存,它跟踪可配置数量的最近添加的条目。
如果最近看到的条目被同一个应用程序实例发现,这一层将短路
重复项的添加,而是返回先前分配给此条目的索引。否则,请求的条目将
传递到第二层。
2. 第二层是一个 `Persistent` 索引,将条目的哈希映射到其在日志中的分配位置。
与第一层类似,这一层将在其存储数据中查找与传入
条目匹配的记录,如果存在这样的记录,它将短路重复条目的添加并返回先前
版本的分配位置。
这些层由以下对象的 `WithAntispam` 方法配置
[AppendOptions](https://pkg.go.dev/github.com/transparency-dev/tessera@main#AppendOptions.WithAntispam) 和
[MigrateOptions](https://pkg.go.dev/github.com/transparency-dev/tessera@main#AppendOptions.WithAntispam)。
### 见证
日志必须是仅追加数据结构。
此属性可以由见证者验证,并且来自见证者的签名可以在发布的检查点中提供,以增加日志用户的信心。
个性化可以使用指定与 [C2SP Witness Protocol](https://github.com/C2SP/C2SP/blob/main/tlog-witness.md) 兼容的见证者的选项来配置 Tessera。
配置见证者是通过使用 [`NewWitnessGroupFromPolicy`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#NewWitnessGroupFromPolicy)
助手,或者通过程序化创建一个顶级 [`WitnessGroup`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#WitnessGroup) 来完成的,该组包含
子 `WitnessGroup`s 或 [`Witness`es](https://pkg.go.dev/github.com/transparency-dev/tessera@main#Witness)。
每个 `Witness` 都配置有一个可以访问见证者的 URL,以及一个用于它必须签名的密钥的 `Verifier`。
`WitnessGroup` 配置有其子组件,以及为了满足该组而必须满足的这些组件的数量。
这些原语允许指定任意复杂的见证策略。
一旦配置了顶级 `WitnessGroup`,它将使用
[AppendOptions#WithWitnesses](https://pkg.go.dev/github.com/transparency-dev/tessera@main#AppendOptions.WithWitnesses) 传递到 `Appender` 生命周期选项中。
如果未设置此选项,将不会配置见证。
### 同步发布
同步发布由 [`tessera.PublicationAwaiter`](https://pkg.go.dev/github.com/transparency-dev/tessera#PublicationAwaiter) 提供。
这允许使用 Tessera 构建的应用程序阻塞,直到通过调用 `Add()` 传递的叶子通过公共检查点提交。
## 生命周期
### Appender
这是最常见的生命周期模式。Appender 允许应用程序添加叶子,这些叶子将被分配日志中的位置
与日志已经承诺的任何条目相邻。
此模式通过 [`tessera.NewAppender`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#NewAppender) 实例化,并
使用 [`tessera.NewAppendOptions`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#NewAppendOptions) 结构体进行配置。
这在上面 [构建 Appender](#constructing-the-appender) 中进行了描述。
请注意,[tlog-tiles][] 规范将条目限制为 64KB 大小,但当 Tessera 配置为通过 `WithCTLayout` 选项与 Static CT 一起使用时,条目将被限制为 256KB。
有关更多详细信息,请参阅 [生命周期设计:Appender](https://github.com/transparency-dev/tessera/blob/main/docs/design/lifecycle.md#appender)。
### 迁移目标
此模式用于将日志从一个位置迁移到另一个位置。
这是通过 [`tessera.NewMigrationTarget`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#NewMigrationTarget) 实例化的,
并使用 [`tessera.NewMigratonOptions`](https://pkg.go.dev/github.com/transparency-dev/tessera@main#NewMigrationOptions) 结构体进行配置。
用于迁移_到_每个存储实现的二进制文件可以在 [./cmd/experimental/migrate/](./cmd/experimental/migrate/) 找到。
这些二进制文件接收远程平铺日志的 URL,并将其复制到目标位置。
这些二进制文件应该足以满足大多数用例。
需要编写自己的迁移二进制文件的用户应使用提供的二进制文件作为参考 codelab。
有关更多详细信息,请参阅 [生命周期设计:Migration](https://github.com/transparency-dev/tessera/blob/main/docs/design/lifecycle.md#migration)。
### 冻结日志
冻结日志会阻止对日志的新写入,但仍允许读取访问。
我们建议操作员允许所有待处理的 [已排序](#sequencing) 条目被 [集成](#integration),并且所有集成的条目通过 Checkpoint [发布](#publishing),然后再继续。
一旦所有待处理的条目发布,日志现在就是 _静止的_,如 [生命周期设计:Quiescent](https://github.com/transparency-dev/tessera/blob/main/docs/design/lifecycle.md#quiescent) 中所述。
为了确保所有待处理的条目都已发布,请在正在运行的进程中保留当前生命周期状态的实例对象,但在个性化级别禁用对此的写入。
例如,一个从 Internet 接收 HTTP 请求并调用 `Appender.Add` 的个性化应该保持运行带有 `Appender` 的进程,但禁用任何导致调用 `Add` 的代码路径(例如,通过翻转更改此行为的标志)。
实例化的 `Appender` 允许其后台进程继续运行,确保所有条目都已排序、集成和发布。
确定何时完成可以通过检查数据库或通过插桩此代码的 OpenTelemetry 指标来完成;
当下一个可用的序列号和发布的检查点大小趋同并保持稳定时,日志处于静止状态。
使用 GCP、AWS 或 POSIX 的静止日志现在永久只读,可以使运营成本更低。实现不再需要任何运行 Tessera 代码的正在运行的二进制文件。可以删除为此日志创建的任何数据库(即排序表或反垃圾)。读取路径可以直接从存储桶(对于 GCP、AWS)或通过标准 HTTP 文件服务器(对于 POSIX)提供服务。
使用 MySQL 的日志必须继续运行个性化以便服务于读取路径,因此在冻结时无法受益于相同程度的成本节省。
### 删除日志
删除日志通常在 [冻结日志](#freezing-a-log) 之后执行。
删除已冻结的 GCP、AWS 或 POSIX 日志只需要从磁盘删除存储桶或文件。
删除 MySQL 日志可以通过关闭个性化二进制文件,然后删除数据库来完成。
### 分片日志
部署日志的一种常见方式是并行运行多个日志,每个日志接受不同的条目子集。
例如,CT 根据证书的到期日期对日志进行临时分片。
Tessera 目前没有对日志分片的特殊支持。
实例化日志新分片的推荐方法只是简单地创建一个如上所述的新日志。
这需要实例化完整的堆栈,包括:
- 任何数据库实例
- 每个日志的个性化二进制文件
#589 跟踪为分片日志添加更优雅的资源共享支持。
如果您希望我们优先考虑它,请投票支持该 issue。
## 贡献
有关详细信息,请参阅 [CONTRIBUTING.md](/CONTRIBUTING.md)。
## 许可证
本仓库根据 Apache 2.0 许可证授权,有关详细信息,请参阅 [LICENSE](/LICENSE)。
## 联系
- Slack: https://transparency-dev.slack.com/ ([邀请](https://transparency.dev/slack/))
- 邮件列表: https://groups.google.com/forum/#!forum/trillian-transparency
## 致谢
Tessera 建立在过去多年来参与透明生态系统的许多 _许多_ 人的辛勤工作、经验和教训之上。
标签:AWS, Bing搜索, CT日志, DPI, EVTX分析, EVTX分析, GCP, Go语言, Merkle Tree, POSIX, Tile-based Logs, Tlog, Trillian替代, Zenmap, 分布式系统, 去重, 可验证日志, 响应大小分析, 存储抽象, 密码学, 库, 应急响应, 手动系统调用, 数据完整性, 日志审计, 漏洞利用检测, 用户代理, 程序破解, 软件供应链, 透明度日志