把 MeetingBaaS 指向你自己的对象存储,之后每一份录像、转写文本、音频分块和 log 都会写到那里。两套凭据、一次真实的访问校验,以及关于你要付出什么代价的坦诚说明。

「录像存在哪里?」是让企业客户的合作卡住的那个问题。对银行、医院,或者任何有数据驻留要求的机构来说,「在我们的 bucket 里,已加密」不是一个能拿去交给合规团队的答案。
所以现在你可以给我们一个 bucket。从那一刻起,你的 bot 产出的每一份产物,包括录像、音频分块、转写文本、截图和 log,都会写入你自己拥有的存储,用的是你签发、也能随时吊销的凭据。
GET /v2/storage-config your current configuration, 404 if unset
PUT /v2/storage-config set or replace it
POST /v2/storage-config/test re-run the access check
DELETE /v2/storage-config back to MeetingBaaS storage也可以在 dashboard 里走 设置(Settings)→ 存储(Storage),背后是同样这四个处理程序,只是换成 session 认证。
两套凭据,按它们最终实际落在哪里划分
这是我最愿意为之辩护的设计决定,所以先从它讲起。
你需要提供两对访问密钥,而不是一对:
| 权限 | 存放位置 | |
|---|---|---|
| ingest | PutObject、PutObjectTagging、AbortMultipartUpload | 交给录制 bot。会离开我们的网络。 |
| service | GetObject、ListBucket、PutObject、DeleteObject | 只在我们的 API 服务器上。绝不外传。 |
ingest key 是唯一会靠近你会议的凭据。它会被带到一个在通话中运行无头浏览器的 pod 上,那是这套系统里任何凭据所能处的最暴露的位置。它能写入对象。它读不回来,列不出内容,也删不掉任何东西。一个被攻陷的 pod 顶多往某个前缀里塞点垃圾,仅此而已。
其余的事都由 service key 完成:通过签名 URL 把录像送回给你、转写文本到达时写入,以及在你的保留期到期时删除产物。

有件事这里要说准确。service key 不是只读的,把它说成只读是谎言,你迟早会抓到我们。它既写也删,因为 API 确实两件事都做。这样拆分买到的,是让处在危险位置的那个凭据成为弱的那个。这是实实在在地缩小了影响范围,但它和「处处最小权限」不是一回事,我更愿意明确告诉你你拿到的是哪一种。
如何配置
{
"endpoint": "https://s3.eu-west-3.amazonaws.com",
"region": "eu-west-3",
"force_path_style": false,
"artifacts_bucket": "acme-meeting-artifacts",
"audio_chunks_bucket": "acme-meeting-audio-chunks",
"logs_bucket": "acme-meeting-logs",
"allow_transient_spill": false,
"ingest_access_key_id": "AKIAIOSFODNN7EXAMPLE",
"ingest_secret_access_key": "...",
"service_access_key_id": "AKIAI44QH8DHBEXAMPLE",
"service_secret_access_key": "..."
}这三个 bucket 名字可以全都填同一个 bucket。无论如何对象键都以 bot id 为前缀,所以不会冲突。force_path_style 是 MinIO、Ceph 和大多数自托管网关需要的;AWS 和 Scaleway 不用它也能正常工作。
endpoint 必须是公网 HTTPS,里面不能带凭据或 query string。bucket 应该设为私有,因为产物是通过短期有效的签名 URL 提供的;但它们确实需要能从公网访问:转写服务商是直接从签名 URL 拉取音频的,不会经我们中转。
顺手把 dashboard 的 origin 也在产物 bucket 上放行。dashboard 不是直接给出链接,而是自己去取产物,读转写 JSON 也是同样的方式,所以这些都是对你 endpoint 的跨源读取;没有 CORS 规则,浏览器会在任何东西渲染出来之前就把响应丢掉。整条规则就是:允许来自你 dashboard origin 的 GET 和 HEAD。值得一提的是,PutBucketCors 比 service key 持有的对象权限高一档,需要 owner 级别的凭据——service key 在这里返回 Forbidden,是权限收窄在起作用,不是 bug。
回读配置时,你会拿到两个 access key ID,这样你能分辨正在用哪套凭据;两个 secret 一个都不会返回,永远不会。替换配置意味着两个 secret 都要重新输入,因为我们没法把现有的显示给你看。
访问校验,以及它为什么不是走过场
PUT 会先验证配置,再把它存下来。不是调一次 ListBuckets 就算完事。
ingest key 会往每一个不同的 bucket 里写入一个标记对象。然后 service key 读取这个对象、写入它,再删除它。
每个动作都由生产环境中真正执行它的那套凭据来做一遍,所以探测失败的方式和生产失败的方式完全一致。这四步都是关键:ingest 上传是录像到达的方式,service 上传是转写文本写入的方式,读取是产物被提供出去的方式,删除则是保留期真正生效的方式。
真正有用的地方在于把写和读拆开。标记由一套凭据写入,必须由另一套找到。这能抓出没人会去测的那种故障:两对密钥各自完全有效,却指向了不同的 bucket 或不同的账户。世界上任何单凭据健康检查都会让这种配置通过,然后你就丢了一份录像。
没有这一步的话,bucket 名字打错的第一个信号,会是一场真实会议结束时上传失败,那时 pod 的本地磁盘已经没了。校验失败时,我们会原样呈现服务商自己的错误信息,而不是一个笼统的错误,因为「ingest key 无法写入 artifacts bucket」正是跑这次校验的全部价值。
你随时可以用 POST /v2/storage-config/test 重跑一次,轮换密钥或改动 bucket 策略之后正该这么做。
有一件事它有意抓不到:CORS。探测是从我们的服务器发起的,那里根本不涉及 CORS,所以一个 bucket 可以通过上面每一个动作,却依然拒绝把录像交给浏览器。这个缺口该由我们来补上;在那之前,上面关于放行 dashboard origin 的那段说明,就是「检查全绿」和「dashboard 什么都不显示」之间唯一的分隔。
其他一切都由这条不变式推出
一个 bot 永远解析到它当初写入的那个存储,而不是它所属团队今天配置的存储。
bot 被派发时,那一刻生效的存储配置会被打戳记录到 bot 记录上。这个 bot 的每一次读取、每一个签名 URL、每一次删除,都通过这个戳记来解析。不走团队配置。
这听起来像个实现细节。它其实是这个功能可以放心开启的原因,而且直接推出四条结论:
修改配置会插入一行新记录,并停用旧的那行。 它绝不会原地更新。改动那一行会悄悄把所有现存 bot 的产物重定向到一个根本不存在这些产物的 bucket。
DELETE /v2/storage-config 只是翻转一个标志位。 两边都不会删除任何东西。硬删除会剥掉更老的 bot 仍然需要的凭据,让你账户里的每一份录像变成孤儿。
解析过程刻意忽略配置是否处于启用状态。 停用只会让新产物不再写入你的 bucket。它绝不能把已经在那里的产物变成孤儿。
找不到打戳的配置时会报错,而不是回退到我们的 bucket。 回退会把你的产物报告成缺失;在按保留期删除时,它还会一边悄悄留下你的数据,一边报告成功。大声失败才是正确行为。
落到实际操作上:随时设置、随时修改、随时移除都行。更早的录像在整个过程中照常可用。唯一会让老产物变得不可读的,是你那边吊销我们的访问权限,而这本就该由你决定,它的表现也完全符合你的预期。
你实际做出的取舍
对使用自有存储的团队,allow_transient_spill 默认为 false,与我们平台的默认值相反。下面把它的含义完整讲清楚,因为这是这个功能唯一让你付出代价的地方。
正常情况下,上传失败时我们会把产物镜像到我们自己的网络卷上,再由一个对账任务稍后推送上去。这条路径会复制整场会议:视频、音频、说话人分离的输出、每一个分块。复制到我们的基础设施上,在我们的账户里。
而这恰恰是有数据驻留要求的客户想要避免的。更糟的是,驻留客户走到这条路径上的次数比平台客户更多,因为他们的 bucket 在我们的网络之外,往往还在完全不同的服务商那里。
所以当 allow_transient_spill: false 时,所有 bot 类型和消费端的这条回退路径都会关闭。重试次数耗尽的上传会被报告为失败,产物也就丢了。
这就是取舍:用持久性换驻留。我们做了部分补偿,把 bot 上传客户端的尝试次数从 SDK 默认的 3 次提高到 6 次,这样一次抖动不至于让你丢掉一份录像;但你的 bucket 若持续故障,录像还是会丢。
如果你宁可保住录像也不要那份保证,就把它设为 true。dashboard 在你翻转它时会显示一条警告,明确说明你放弃了什么,因为这不该是一个随手勾过去的复选框。
如果录像丢失,而你的 bucket 当时出过故障,这就是第一个要查的地方。
这个功能做不到什么
四件事,提前说清楚,免得有人事后才发现。
已经录好的内容不会被搬走。 没有迁移。每个 bot 始终解析到它当初写入的位置,这正是开启这个功能安全的原因,同时也意味着你的历史数据仍留在我们的 bucket 上。
不能按 bucket 配置前缀。 对象键就是 <bot_uuid>/…,这一点是固定的。
光靠它并不能让数据驻留做到滴水不漏。 会议进行期间,bot pod 仍然把录像放在本地磁盘上。它关掉的是唯一一条会让你的录像长期存放在 MeetingBaaS 共享基础设施上的路径。
dashboard 资源仍在我们的 bucket 上。 徽标、用户头像和支持工单附件不属于会议数据,不在此列。
回滚
DELETE /v2/storage-config,任何时候执行都安全。新 bot 立刻回到我们的存储。现有 bot 继续解析到你的 bucket,继续正常工作。
相关阅读
- 六家转写服务商,一套 API —— 把 bucket 区域和服务商区域配成一对,实现端到端的数据驻留
- 自托管 Meeting BaaS —— 如果只拥有 bucket 还不够
- Meeting BaaS API 参考文档
如果你正在处理数据驻留要求,想确切知道哪个组件接触了什么,来问我们。我们会给你和这里一样的答案。