我的服务器
群晖 Container Manager 部署 Docker 版 MinIO,没有公网 IP 所以使用 cloudflared docker(Tunnel)做内网穿透。
问题描述
思源笔记更新到 3.1.17 或更高版本之后,通过上述环境访问 MinIO 上的 S3 资源会提示认证失败:服务器计算得到的签名和客户端提供的不一致。
尝试解决问题时了解到的信息
能力有限,如果有错误的情报还请帮忙指出。
- 思源笔记从 3.1.17 版本开始使用 aws-sdk-go-v2 和后台 s3 服务进行认证。
这个文件 的 blame 记录可以了解到思源使用的认证渠道的差异,但具体的认证方法还是要看亚马逊的源码。 - 不管是 3.1.16 或之前的版本,还是 3.1.17 或之后的版本,使用的认证规格都是 AWS Signature Version 4,前后的差异主要在于 aws-sdk-go-v2 的认证信息里,增加了一些 Header 要校验的内容,具体差异如下(粗体为新增的):
- 旧版:
- host
- x-amz-content-sha256
- x-amz-date
- Authorization
- go-v2:
- accept-encoding
- amz-sdk-invocation-id
- amz-sdk-request
- host
- x-amz-content-sha256
- x-amz-date
- Authorization
- 旧版:
- v4 的认证流程,据我所知是这样的(以 go-v2 版本的获取 BucketList API 为例):
- 客户端组装 request 的内容,包括上面提到的所有必要的 header,每一项的内容大致是:
-
accept-encoding:固定为
gzip
-
amz-sdk-invocation-id:具体的意思我不太清楚,但好像不太重要,原因之后解释
-
amz-sdk-request:我这里是
attempt=1; max=3
-
host:客户端认为的 s3 endpoint 地址,其实就是思源云端设置里填的网址(不包含
https://
和/后面的内容
) -
x-amz-content-sha256:固定为
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
-
x-amz-date:当前日期和时间精确到秒,比如
20241231T133058Z
-
Authorization:最关键的信息,包含认证的基础信息,和上面 header 的顺序和组织方式,以及客户端计算得到的 Signature。以空格和逗号划分为四个部分:
AWS4-HMAC-SHA256
:固定,表示签名认证版本为 v4。Credential=5FF2U4ONuunfHGXxxxxx/20241231/us-east-1/s3/aws4_request
:基础认证,提供了 Access Key、当前日期精确到天、目标桶的地区编号、固定 s3、固定 aws4_request。SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date
:签名所需的 headers。Signature=3050177a7243c268a3a3a21b982241898e1433f9a6d4abd4b2cde6e934xxxxxx
:客户端计算得到的签名值。(计算方法见下文)
-
- 请求使用 https,通过 CloudFlare Tunnel 到达我在内网搭建的 MinIO 服务器。
- 在我这里,更新到 3.1.17 或更高版本之后,服务器会提示服务器计算的签名和客户端计算的不匹配,客户端显示报错信息。
- 客户端组装 request 的内容,包括上面提到的所有必要的 header,每一项的内容大致是:
- 签名不一致的原因,应该是上面提到的 Headers 或者 Host 在通过 CF Tunnel 的转发时发生了篡改(论坛上有人说 CF 的 Proxy 小黄云关闭之后就正常了,侧面印证了中间人篡改了某些数据),为了定位具体哪个数据被改,我简单看了下源码,也用 CF Workers 修改 Header 做了些试验,分 cai 析 ce 如下:
- 根据报错提示信息,我遇到的问题就是单纯的签名计算问题,与地区的填写没有关系(至少还没到检验地区不匹配的步骤),我主要关注签名的计算方式和中间人的转发过程。
- 签名的计算方式: 上面提到的所有 Header 字符串值(包括 Authorization 中 SignedHeaders 这一段),拼接成一个长字符串(可能是用\n),之后用 Secret Key 进行加密,加密方式为 AWS4-HMAC-SHA256,就得到了 Signature 值。
- CloudFlare Tunnel 的转发流程:根据我的试验,CF 可能传改的信息只可能是 host 和 accept-encoding,因为其他条目我用 Worker 试过是可以任意修改且生效的(当然我自己修改也会导致签名失败,但从 CloudFlare Tunnel 的访问记录看确实可以修改成功),唯独这两条我是改不了的,严格说 Hader 里只有 accept-encoding 没法修改,host 我可以在 Tunnel 的设置里填写固定值所以问题不是它(经过我的试验这个设置真实有效,我在思源 3.1.16 中填写 worker 的 url 可以成功让 Tunnel 以为自己的 host 是 worker 的 url 而不是 Worker 转发时提供的,让签名通过验证)。另外,在 CloudFlare Tunnel 的访问记录中,accept-encoding 的值固定为
gzip, br
,这与我在源码中观察到的情况(应该是只有gzip
)不符。 - 综上,我认为问题的源头是 CloudFlare Tunnel 的服务篡改了 accept-encoding Header 的值,从而导致服务器提取 Header 信息计算得到的 Signature 和客户端发出的不符。
我的一些的失败尝试,以及感想
- 亚马逊的这个认证方式够严谨的,很大程度上规避了中间人篡改信息的风险。
- 我曾经尝试在 Worker 里创建了一个新的 Header
Accept-Encoding2 = gzip
,然后篡改 Authorization 中的accept-encoding
->accept-encoding2
,但是结果还是签名匹配失败。 - 除了 host 因为 worker 的原因不匹配的问题(我后来在 Tunnel 设置里强制修改 host 成功解决了此问题)之外,我才发现 Authorization 的一部分竟然也被用来计算签名了,这下彻底捣毁了我想用 Worker 篡改数据曲线修改 accept-encoding 的诡计。
- 我曾经尝试在 Worker 里创建了一个新的 Header
- 我尝试寻找 CloudFlare 关于 accept-encoding 内容的设置,目前没有收获。
- CF 官方宣布提供 br 压缩的支持 - 2023-06
- CF 将在数月内取消 DashBoard 中是否开启 Brotli 的设置,企业用户依然可以控制 - 2023-06
- CF 默认会向 orign server 发送 "br, gzip" 来告知自身支持的压缩格式
- CF 官方文档表示可以通过 Compression Rules 来设置端到端压缩格式,然而无论是 DashBoard 还是 API 我都无法找到正确设置的方法(Free Plan)
- CF 表示 2024-08-15 开始取消了 Brotli 的设置 API
- 既然 CloudFlare 不让修改压缩格式,我认为使用 Tunnel 的方式基本无解了,如果说还有什么解法的话那就要修改思源的源码了。
这篇 Issue 提到的方法也许可以解决此问题,原理是使用反射之类的方法让签名 headers 名称列表里 ignore 掉 accept-encoding。
我不太了解 go 和思源的代码怎么改,有缘人看到可以了解一下,看看是不是有必要尝试一下。 - 我最初希望内网搭建 S3 服务的初衷是想着在内网同步会非常快,但在外网无法访问这点我是无法接受的,因此目前的解决办法是将数据迁移到了 CloudFlare R2,因为是免费的,未来也许对比下访问速度,可能会再迁移到阿里云或者七牛云吧。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于