alitrack

DuckDB 做 Server 还差什么?RBAC,社区已经补上了

2026 年 5 月,DuckDB Labs 发布了 Quack —— 第一个官方 client-server 协议。DuckDB 从此不再是只能嵌入应用的单机引擎。

但 MotherDuck 的博客写得很直白:

"所有客户端共享同一个 token,没有角色权限。Quack 没法区分不同用户。"

一句话说透了 Quack 最大的短板:没有访问控制。Server 模式下所有人要么全权读写、要么只读。这在任何生产环境都不可接受。

最直接的解法是上 MotherDuck。它内置了数据库级权限隔离、Read-Scaling Token、SSO 对接,SOC 2 + GDPR 认证,是 DuckDB 生态里唯一的"开箱即用"方案。代价是付费。

如果你不想为托管服务买单——或者你更想控制自己的基础设施——DuckDB 的扩展机制已经提供了完整的钩子,社区也在快速补齐这块拼图。本文拆解四种开源方案,从零代码到 C++ 扩展,覆盖不同的场景和投入。

四种方案架构总览

四种方案架构总览


● ● ●

先搞清楚:MotherDuck 的"RBAC"长什么样

MotherDuck 其实没有传统 RBAC(CREATE ROLE / GRANT SELECT ON table TO role)。他们用了更简单的模型:

机制
作用
Database 级隔离
用户能访问某个 database = 能访问里面的所有表。不能做表级/列级权限
Read-Scaling Token
只能 SELECT,不能写、不能改 schema
Zero-Copy SHARE
把生产库只读共享给 BI 工具
Filtered ViewCREATE VIEW team_a AS SELECT * WHERE team='A'
 — 应用层隔离

MotherDuck 的官方立场:数据库级隔离 + Read-Only Token 对大多数场景已经够了。 传统行级权限的运维成本太高。

这意味着"对标 MotherDuck 的 RBAC"比想象中简单。不需要实现完整的 SQL 标准 RBAC(CREATE ROLE / GRANT / REVOKE),只需要做三件事:

  1. 01多用户认证
    (不同 token = 不同身份)
  2. 02权限分级
    (谁可以读写、谁只能读)
  3. 03数据隔离
    (谁可以看到哪些表/库)

● ● ●

方案一:SQL MACRO — 零代码,5 分钟上线

Quack 内置了两个可替换的钩子函数:

-- 认证钩子:客户端连接时调用 quack_authentication_function(session_id, client_token, server_token) → BOOLEAN  -- 授权钩子:客户端发 SQL 前调用 quack_authorization_function(connection_id, query_string) → BOOLEAN

你可以用 SQL MACRO 覆盖它们,不需要写任何 C++ 代码:

-- 1. 建用户表 CREATE TABLE quack_users (     token_hash VARCHAR PRIMARY KEY,     user_name  VARCHAR,     role       VARCHAR  -- 'admin' | 'writer' | 'reader' ); INSERT INTO quack_users VALUES     ('alice-key-123', 'alice', 'admin'),     ('bob-key-456',   'bob',   'reader');  -- 2. 覆盖认证钩子:查 token 表 CREATE MACRO check_token(sid, client_token, server_token) AS (     EXISTS (SELECT 1 FROM quack_users WHERE token_hash = client_token) ); SET GLOBAL quack_authentication_function = 'check_token';  -- 3. 覆盖授权钩子:按角色做权限检查 CREATE MACRO check_perm(sid, query) AS (     CASE         -- admin 全放行         WHEN (SELECT role FROM quack_users WHERE token_hash = sid) = 'admin'             THEN true         -- reader 只放 SELECT 开头的语句         ELSE regexp_matches(upper(trim(query)), '^(SELECT|FROM|WITH|EXPLAIN|DESCRIBE|SHOW)\b')     END ); SET GLOBAL quack_authorization_function = 'check_perm';

优点:5 分钟上线、热加载(改表即生效)、不依赖任何外部工具。

局限:授权钩子只拿到原始 SQL 字符串,没有解析后的查询树。做权限控制只能靠正则匹配 SQL 文本——CTE、子查询、多语句场景下可能漏掉。

适用场景:开发环境、小团队内部使用、只需要 admin/reader 两级权限。


● ● ●

方案二:quack_oauth — 社区扩展,支持 OAuth + 策略表

德国的 DataZooDE 团队写了一个更完整的方案:quack_oauth。已发布为 DuckDB 社区扩展。

INSTALL 'quack_oauth' FROM 'http://get.erpl.io'; LOAD 'quack_oauth';

它的思路是用 JWT + 策略表 替代简单的 token 查表:

quack_oauth 认证授权流程

quack_oauth 认证授权流程

策略表存在 DuckDB 里,热加载:

CREATE TABLE main.policies (     priority  INTEGER,     subject   VARCHAR,      -- 用户 ID,NULL = 所有用户     any_scope VARCHAR[],    -- OAuth scope,如 ['quack:read']     actions   VARCHAR[],    -- Attach, Scan, CopyTo, CopyFrom, ServeAdmin     allow     BOOLEAN );  INSERT INTO main.policies VALUES     (10, NULL, ['quack:read'],  ['Attach', 'Scan'], true),     (20, NULL, ['quack:write'], ['Attach', 'Scan', 'CopyTo', 'CopyFrom'], true);

它做了什么:

  • 真正的 JWT 验证(支持 Keycloak、Azure Entra、Google)
  • 动作级权限(允许 SELECT、禁止 DDL)
  • 审计日志(每次授权决定都记录到 main.audit 表)
  • 客户端自动获取 token(quack_oauth_acquire())

它没做什么:表级/列级权限。因为和 MACRO 一样,它也只能拿到原始 SQL 字符串,没法知道查询涉及哪些表。

适用场景:需要对接企业 IdP(Keycloak/Azure AD)、需要审计日志的团队。

GitHub: github.com/DataZooDE/quack-oauth


● ● ●

方案三:独立 DuckDB RBAC 扩展 — 最完整的答案

如果你需要真正的 SQL 级 RBAC(表级、列级、甚至行级权限),需要在 DuckDB 引擎层面动手。社区已经有人写了详细的 RFC。

2025 年 12 月,dufferzafar 在 GitHub Gist 上发布了一份完整的实现方案。核心理念是用 DuckDB 的三个扩展钩子:

RBAC 扩展的三个钩子协作流程

RBAC 扩展的三个钩子协作流程

目标语法:

CREATE ROLE analyst; GRANT analyst TO alice; GRANT SELECT ON orders TO analyst; GRANT SELECT (id, customer, amount) ON orders TO analyst;  -- 列级权限 CREATE ROW POLICY filter ON orders     FOR SELECT USING (region = current_user_region()) TO analyst;

技术细节:

在 DuckDB 的查询优化器运行之前,扩展遍历 LogicalOperator 树,找到所有 LogicalGet(表扫描)节点,检查当前用户是否有权限访问这些表/列。如果有行策略,注入一个 LogicalFilter 来限制可见行。

已知局限:SELECT  问题。DuckDB 在 binding 阶段就把 SELECT  展开为显式列名,这发生在优化器扩展运行之前。所以列级权限无法"隐藏"列,只能拒绝整个查询。用户需要使用显式列名来绕过这个限制。

实现难度:需要 C++ 扩展开发能力,深度理解 DuckDB 的 binder/optimizer 内部机制。但社区 RFC 已经把架构讲清楚了,不存在未知的技术障碍。

为什么值得做成独立扩展而不是嵌进 Quack?

因为 RBAC 是 SQL 引擎层的语义问题,不是协议层问题。一个独立扩展可以在所有 DuckDB 使用模式下工作——嵌入 Python、CLI、Quack server、Arrow Flight——而不仅仅是在 client-server 模式下。

Gist: gist.github.com/dufferzafar/f12081d4f32e640966d984b33e7077e6


● ● ●

方案四:nginx 反向代理层 — 最务实的"够用"方案

如果你只是想让外部 BI 工具(Tableau、Metabase)安全地连上 Quack,最简单的方法是在反向代理层做访问控制:

# 只允许特定 IP 的 SELECT 查询 location /quack {     allow 10.0.1.0/24;     # 内网 BI 工具     deny all;      # 只放行 GET 请求(Quack 的 SELECT 用 GET)     limit_except GET {         deny all;     }      proxy_pass http://localhost:9494; }

优点:不改一行 DuckDB 代码、运维团队已有的 nginx 技能直接用。

局限:只能做 IP 级 + HTTP 方法级的粗粒度控制。没法区分"哪个用户在查"。

适用场景:BI 工具固定 IP、内网只读访问。


● ● ●

对比矩阵

方案
认证方式
权限粒度
需要写代码?
热加载
适用规模
SQL MACRO
Token 表
动作级(regex)
❌ 零代码
✅
2-5 人团队
quack_oauth
JWT/OIDC
动作级(策略表)
❌ 只写 SQL
✅
10-50 人团队
独立 RBAC 扩展
可定制
表级/列级/行级
✅ C++ 扩展
✅
企业级
nginx 反向代理
IP/HTTP Method
IP 级
❌ 配置
✅ 需 reload
BI 工具内网访问

● ● ●

我的选择:先 MACRO,后扩展

对于大多数 Quack 用户,方案一(SQL MACRO)应该先上线。5 分钟部署,覆盖 80% 的场景(admin/reader 两级 + token 认证)。

当你需要更细粒度的控制——比如财务团队只能看 finance schema,工程团队只能看 engineering schema——再考虑方案三(独立 RBAC 扩展)。

好消息是这些方案是递进关系。你可以今天用 MACRO 上线,下个月换成 quack_oauth 接企业 IdP,半年后自己写 RBAC 扩展做表级权限。Quack 的钩子设计让每一步迁移都很平滑。


DuckDB 从嵌入引擎变成 server 数据库,访问控制不是可选项,是必选项。MotherDuck 用托管的办法解决了这个问题。但 Quack 的钩子设计证明了一件事:你完全可以用开源组件自己搭一套 RBAC,不需要托管服务。

你现在在用 Quack 吗?你的权限是怎么做的?评论区聊聊。


相关项目:

  • Quack 协议文档: duckdb.org/docs/current/quack/overview
  • Quack 安全机制: duckdb.org/docs/current/quack/security
  • quack_oauth 扩展: github.com/DataZooDE/quack-oauth
  • DuckDB RBAC RFC: gist.github.com/dufferzafar/f12081d4f32e640966d984b33e7077e6
  • MotherDuck vs Quack 对比: motherduck.com/blog/duckdb-client-server