
本文字数:6128;估计阅读时间:16 分钟
作者:Mark Needham
本文在公众号【ClickHouseInc】首发

ClickHouse 25.12 我最喜欢的功能之一是用于使用共享密钥(shared key)进行消息认证(message authentication)的 HMAC 函数。
ClickHouse 一直能够充当网络钩子端点(webhook endpoint),但有了 HMAC 函数,我们现在可以验证网络钩子请求,过滤掉那些并非来自预期来源的请求。我们将首先通过一个本地示例了解其工作原理,然后再探讨一个使用 ClickHouse Cloud 作为 GitHub 网络钩子端点的真实案例。
网络钩子模式(Webhook Pattern)
ClickHouse 的一个出色之处在于能够使用物化视图(materialized views)将表链接起来。数据被摄入(ingested)到一张表中,物化视图充当 SQL 触发器(SQL trigger),然后结果被写入另一张表。
当与 Null 表引擎(Null table engine)结合使用时,这变得尤其强大。Null 表引擎不存储数据,而是将其接收到的所有内容转发给任何已连接的物化视图——使其成为完美的暂存层(staging layer)。
结果是一个清晰的三步模式:
1. 暂存表接收所有传入数据
2. 物化视图验证并转换数据
3. 最终表仅存储已验证数据

创建表
让我们来看看如何在 ClickHouse 中进行所有这些设置。如果您想跟着操作,需要运行一个 ClickHouse 服务器或 ClickHouse Cloud 服务。
首先,让我们创建暂存表:
CREATE TABLE webhook_staging (
received_at DateTime DEFAULT now(),
raw_payload String,
signature String DEFAULT getClientHTTPHeader('X-Hub-Signature-256')
) ENGINE = MergeTree()
ORDER BY received_at
SETTINGS allow_get_client_http_header=1;
allow_get_client_http_header 设置是必需的,因为默认情况下禁用了读取客户端请求头(client headers)。
我们正在使用
MergeTree引擎创建webhook_staging表,以便调试传入的请求。在生产系统中,我们将使用Null引擎,并且可能还会有一个第二个物化视图来捕获无效请求。
接下来,我们有一个表,它将只存储已验证的行:
CREATE TABLE webhook_logs (
received_at DateTime,
payload JSON
) ENGINE = MergeTree()
ORDER BY received_at;
最后,我们有一个物化视图来验证数据:
CREATE MATERIALIZED VIEW webhook_validator TO webhook_logs AS
SELECT
received_at,
raw_payload::JSON as payload
FROM webhook_staging
WHERE signature = 'sha256=' || lower(hex(HMAC('SHA256', raw_payload, 'my_secret_key')));
我们正在将请求头(request header)中的签名与使用共享密钥计算出的预期签名进行比较。我们的共享密钥是 my_secret_key。
如果传入的行不匹配预期签名,它将不会被写入 webhook_logs 表。
创建受限用户
在我们自己的机器上进行实验时,我们可以使用默认的管理员用户,但对于生产系统而言,创建一个具有最小权限的专用用户更安全。我们创建的用户将用于稍后构建的 webhook URL。
首先,让我们创建用户:
CREATE USER webhook_receiver
IDENTIFIED WITH sha256_hash
BY 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
该哈希值 (hash) 是一个空字符串的 SHA256,因此当我们构建 webhook URL 时无需密码。
接下来,让我们授予用户所需的权限:
GRANT INSERT ON default.webhook_staging TO webhook_receiver;
GRANT SELECT ON default.webhook_staging TO webhook_receiver;
GRANT SHOW TABLES ON default.webhook_staging TO webhook_receiver;
GRANT SHOW DATABASES ON default.* TO webhook_receiver;
如果我们想格外谨慎,我们还可以对此用户应用速率限制 (rate limits),以防止滥用。
构建 webhook URL
ClickHouse 接受通过 HTTP 进行插入,这意味着整个 webhook 终结点 (endpoint) 是一个嵌入了 INSERT 查询的 URL。
http://localhost:8123/?user=webhook_receiver&query=INSERT+INTO+webhook_staging+(raw_payload)+FORMAT+RawBLOB
关键部分:
• user=webhook_receiver - 我们创建的受限用户
• query=INSERT INTO webhook_staging (raw_payload) FORMAT RawBLOB - 插入原始请求正文 (raw request body) 的 URL 编码查询。
FORMAT RawBLOB 告诉 ClickHouse 将整个请求正文视为一个单一的字符串值,而不是尝试解析它。
然后我们可以通过发送一个有效请求来测试它,即签名是使用正确密钥计算的请求:
PAYLOAD='{"event":"user_login","user_id":456}'
SIGNATURE=$(echo -n ",[object Object]," | openssl dgst -sha256 -hmac "my_secret_key" | cut -d' ' -f2)
curl -X POST "http://localhost:8123/?user=webhook_receiver&allow_get_client_http_header=1&query=INSERT%20INTO%20webhook_staging%20(raw_payload)%20FORMAT%20RawBLOB" \
-H "X-Hub-Signature-256: sha256=,[object Object]," \
-d ",[object Object],"
现在让我们使用 ClickHouse 客户端 (Client) 连接到我们的 ClickHouse 服务器 (Server):
clienthouse client
我们可以返回 webhook_staging 和 webhook_logs 的内容:
SELECT * FROM webhook_staging;
Row 1:
──────
received_at: 2026-02-20 15:42:22
raw_payload: {"event":"user_login","user_id":456}
signature: sha256=5a23c796b6248c725a6ec7fc2cf0788117d69d376ee6241f411a8887297d3ca4
1 row in set. Elapsed: 0.001 sec.
SELECT * FROM webhook_logs;
Row 1:
──────
received_at: 2026-02-20 15:42:22
payload: {
"event": "user_login",
"user_id": 456
}
1 row in set. Elapsed: 0.003 sec.
两张表都有数据。现在,如果我们发送一个使用不同密钥计算签名的请求又会怎样:
PAYLOAD='{"event":"user_login","user_id":456}'
SIGNATURE=$(echo -n ",[object Object]," | openssl dgst -sha256 -hmac "rogue_key" | cut -d' ' -f2)
curl -X POST "http://localhost:8123/?user=webhook_receiver&allow_get_client_http_header=1&query=INSERT%20INTO%20webhook_staging%20(raw_payload)%20FORMAT%20RawBLOB" \
-H "X-Hub-Signature-256: sha256=,[object Object]," \
-d ",[object Object],"
让我们对 webhook_staging 运行查询:
Row 1:
──────
received_at: 2026-02-20 15:43:21
raw_payload: {"event":"user_login","user_id":456}
signature: sha256=01f65041f2505f2b245f3caff410913a22a0ff8c7e8414c9fe1e861359973a7a
Row 2:
──────
received_at: 2026-02-20 15:42:22
raw_payload: {"event":"user_login","user_id":456}
signature: sha256=5a23c796b6248c725a6ec7fc2cf0788117d69d376ee6241f411a8887297d3ca4
2 rows in set. Elapsed: 0.002 sec.
那么现在来看看 webhook_logs:
Row 1:
──────
received_at: 2026-02-20 15:42:22
payload: {
"event": "user_login",
"user_id": 456
}
1 row in set. Elapsed: 0.004 sec.
webhook_staging 得到一个新行,而 webhook_logs 没有——签名不匹配,因此物化视图 (materialized view) 丢弃了它。
GitHub webhook
在本地建立模式后,让我们使用 ClickHouse Cloud 将其连接到一个真实的 GitHub 仓库 (repository)。结构几乎相同,只有一个区别——暂存表 (staging table) 会额外增加一个 event_type 列(来自 GitHub 发送的 X-GitHub-Event 头部 (header)),并且 webhook_logs 也增加一个 event_type 列。
下面的查询创建了我们的表和物化视图。
CREATE TABLE webhook_staging (
received_at DateTime DEFAULT now(),
raw_payload String,
event_type String DEFAULT getClientHTTPHeader('X-GitHub-Event'),
signature String DEFAULT getClientHTTPHeader('X-Hub-Signature-256')
) ENGINE = MergeTree()
ORDER BY received_at
SETTINGS allow_get_client_http_header=1;
CREATE TABLE webhook_logs (
received_at DateTime,
event_type String,
payload JSON
) ENGINE = MergeTree()
ORDER BY received_at;
CREATE MATERIALIZED VIEW webhook_validator TO webhook_logs AS
SELECT
received_at,
event_type,
raw_payload::JSON as payload
FROM webhook_staging
WHERE signature = 'sha256=' || lower(hex(HMAC('SHA256', raw_payload, 'my_secret_key')));
在 ClickHouse Cloud 中,点击 Connect 获取你的集群 URL,然后以相同的方式构建 webhook URL——只需将 localhost:8123 替换为你的 Cloud 主机,并添加 allow_get_client_http_header=1:
https://<host>:8443/?user=webhook_receiver&allow_get_client_http_header=1&query=INSERT+INTO+webhook_staging+(raw_payload)+FORMAT+RawBLOB
配置 GitHub webhook
在你的GitHub仓库中,前往 设置 → Webhooks → Add webhook:
1. 将你的ClickHouse URL粘贴到 Payload URL 中
2. 将 Content type 设置为 application/json
3. 在 Secret 字段中输入你的密钥 - 这必须与物化视图(materialized view)中的 my_secret_key 匹配
4. 选择要发送的事件(初步选择推送事件(push events)即可)
5. 点击 Add webhook

GitHub将立即发送一个 ping 事件。刷新webhook页面,你应该会看到“上次交付成功”。

验证功能是否正常
随后,我们可以通过点击表的名称来查看其内容。
你应该在这两个表中都看到 ping 事件,这证实了 HMAC 验证已通过。现在对你的仓库进行更改——例如,提交到一个分支,打开一个拉取请求(PR),然后合并它——之后,推送事件将同时出现在这两个表中。

测试签名验证
为了确认验证功能正常,请回到GitHub,将webhook密钥更改为不同的值,然后进行另一次提交。
webhook_staging 将会有新的行,但 webhook_logs 不会——物化视图计算出了不同的 HMAC 并将其过滤掉了。

征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

331

被折叠的 条评论
为什么被折叠?



