Lilly
(Lillian )
1
我最近配置了 Cloudflare R2 上传存储桶,结果聊天缩略图显示异常。于是我深入排查并快速修复了配置,随后发现了这个主题:https://meta.discourse.org/t/cloudflare-r2-image-url-display-issue-detailed-explanation-and-fix/358204。无论如何,我查看了其他 S3 上传存储桶的配置,发现这个问题并非 Cloudflare 特有。
描述
当为上传配置了外部 S3 或兼容的对象存储时,聊天缩略图会绕过 CDN,直接从存储桶 URL 加载。
对于像 Cloudflare R2 这样需要安全配置的外部 S3 兼容存储桶,聊天缩略图会损坏且无法显示。
根本问题在于聊天序列化器未能将 s3_cdn_url 设置应用于缩略图。图像未通过配置的 CDN 进行路由,而是将原始的 S3 存储桶内部 URL 直接泄露到了浏览器负载中。
复现步骤
在 Meta 及其他使用 S3 上传存储桶的站点上均可复现此问题:
- 在聊天或频道中发布一张图片
- 在控制台检查缩略图图片的 URL
- 点击图片以获取更大的原图,并检查其 URL
- 将其与缩略图 URL 进行对比
以下是来自 Meta 聊天的示例:
缩略图 URL:来自存储桶
https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/meta/optimized/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532_2_375x500.jpeg
原图 URL:通过 CDN
https://global.discourse-cdn.com/meta/original/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532.jpeg
在控制台中,缩略图的 HTML 代码 <img... 包含 data-large-src(CloudFront CDN URL)和 src(AWS 存储桶 URL)。
截图
影响:
- 对于像 Cloudflare R2 这样默认安全且阻止未授权访问原始存储桶端点的 S3 兼容存储,聊天缩略图(优化后的)会损坏。
- 对于允许访问原始存储桶端点的 AWS 及其他 S3 兼容对象存储桶,会导致带宽泄露,因为聊天完全绕过了 CDN;这将导致为所有聊天缩略图流量支付直接的 S3 出站费用。
- 基础设施泄露:原始后端存储 URL(包括内部存储桶名称,有时甚至包括账户 ID)正被暴露在客户端 JSON 负载中。
拉取请求(PR):
我提交了一个修复该问题的 PR:
看起来 Sam 添加了 getURLWithCDN 到聊天编辑器预览中——不过,我认为它并没有应用到聊天流中?
我想知道编辑器的修复是否在某些 S3 配置下也失败了,因为 getURLWithCDN 在协议不匹配(// 与 https://)时会崩溃?无论如何,上述 PR 通过为聊天流添加包装器并使其与协议无关,扩展了 Sam 的工作。
临时解决方案:
在我意识到这不仅仅是 Cloudflare 的问题之前,我制作了一个轻量级的主题组件。它在浏览器尝试下载之前,拦截聊天 DOM 中的原始 S3 域名,并将其替换为正确的 CDN 域名。这正确地路由了流量并堵住了带宽泄露的漏洞。我对其进行了适配,使其适用于任何 S3 兼容的对象存储。只需两个设置——「原始 S3 存储桶 URL」和「S3 CDN URL」。
(不知道为什么 GitHub 的自动链接在这里失效了) 现已修复
5 个赞
Lilly
(Lillian )
4
5 个赞
Lilly
(Lillian )
8
对于添加了自定义表情的站点,修复合并后,聊天中的表情现在会失效。
与标准帖子图片不同(可以通过运行 rake posts:rebake 修复),聊天自定义表情是通过 /site.json 动态传递给前端的。
如果你的数据库中包含缺少协议的 S3 URL(例如 //bucket.endpoint...),或者使用了虚拟主机风格域名且该域名与你的 app.yml 环境变量不完全匹配,Discourse 的内部 CDN 替换器会静默失败。原始的存储桶 URL 会被传递给浏览器,从而导致聊天中的自定义表情失效。
如何修复:
要永久修复此问题,你需要在数据库中强制将原始存储桶 URL 映射到你的 CDN URL,然后清除站点缓存,以便 /site.json 重新生成。
1. 进入容器:
SSH 登录到你的服务器并进入 Discourse 容器(通常是 app,如果你使用双容器设置,则是 web_only)。
cd /var/discourse
./launcher enter app
2. 重新映射 URL:
运行内置的 Discourse remap 工具。你应该运行两次,以捕获 https:// 变体以及迁移脚本有时会遗留的无协议 // 变体。
将占位符替换为你的实际原始存储桶 URL 和实际 CDN URL:
# 修复标准的 https:// URL
discourse remap "https://<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"
# 修复无协议的 // URL(这通常是导致自定义表情失效的原因)
discourse remap "//<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"
3. 清除缓存
由于 /site.json 被高度缓存,你必须清除 Rails 缓存以强制论坛提供新的 URL:
打开 Rails 控制台:
rails c
运行以下命令:
Rails.cache.clear
Site.clear_cache
exit
4. 刷新
硬刷新你的浏览器(如果仍在使用,请禁用主题组件的临时解决方案)。聊天中的自定义表情现在应该已修复,并通过 CDN 正常加载。
3 个赞
嗨,Lilly,非常感谢你出色的工作。经过两次重新映射后,我之前的上传文件和缩略图现在都能正常显示了。在此之前,就连我的 “/admin/config/customize/themes” 图片都无法加载。现在一切都恢复正常了,真不错。
谢谢!
1 个赞