可重配置的CORS中间件与jub0bs/cors
TL;DR 在这篇对之前文章的简短后续中,我描述了为什么以及如何为jub0bs/cors添加CORS中间件动态重配置支持。
重新思考配置不可变性
直到最近,我一直强烈主张CORS中间件不应支持动态重配置,任何配置变更都需要服务器重启:
由于CORS放宽了同源策略强制执行的一些限制,它是一个安全关键机制。因此,服务器的CORS配置应该受到变更控制:更具体地说,我认为任何CORS配置更新都需要仔细审查,并且应该在服务器重启后进行。[…]如果开发人员认为服务器启动速度过慢,我认为他们应该专注于减少启动时间,而不是避免重启服务器的需求。 使用jub0bs/fcors构建的中间件实际上是不可变的,任何对相应CORS策略的修改都需要服务器重启。虽然这个约束不能保证CORS策略变更会被审查,但我相信它至少会推动开发人员采用这种做法。
不过,我小心地留有余地:
尽管我努力避免他人过去的设计错误,但我自己可能也犯了全新的错误。
我很高兴这样做了。最近关于jub0bs/cors(我的最新参考实现)的一些反馈迫使我重新思考这个原则,并在这个话题上稍微软化立场。
我仍然非常重视不可变基础设施,但这个约束对于我声明的目标来说既过于限制又无效:
- 它过于限制,因为它阻碍了快速的无停机更新,至少在某些情况下如此;例如,想象一个多租户SaaS公司的CORS感知API,其(重新)部署时间过长。
- 它无效,因为正如我在上面引用的段落中暗示的那样,仅凭它不能保证变更控制和可审计性。
回顾起来,我必须承认,试图在库级别解决此类治理问题可能是不明智的。
许多情况需要更务实的方法,只要保持并发安全,配置不可变性可以有利地取消。
jub0bs/cors中间件现在可重配置
在收集了一段时间的想法并征求了Go社区的一些反馈后,我能够为jub0bs/cors添加动态中间件重配置功能,这种方式不会破坏我的整个设计理念、引入破坏性变更或影响性能。
jub0bs/cors仍然提供以下函数来创建CORS中间件:
|
|
但v0.2.0增加了两个方法:
|
|
Reconfigure
方法,顾名思义,允许你重新配置中间件m
。如果cfg
参数非nil但*cfg
无效,Reconfigure
返回一些非nil错误并保持m
不变。如果cfg
为nil,Reconfigure
禁用CORS;它基本上将m
变成一个"直通"中间件,即一个不做任何有趣事情、仅仅委托给它包装的处理程序的中间件。
请注意,在Reconfigure
返回后修改*cfg
的字段不会改变m
的行为。
Config
方法返回一个指向深度复制的Config
值的指针,该值用于构建或最后重新配置CORS中间件m
。借助这个方法,你不需要为了修改而保持中间件的配置在作用域内(例如,为了增加允许的Web来源集);你可以在需要时简单地查询配置。
m.Reconfigure(m.Config())
保证是无操作的(尽管相对昂贵)。再次提醒,修改Config
方法结果的字段不会改变m
的行为。
由于这两种方法都是并发安全的,你可以在中间件并发处理请求时自信地重新配置中间件和/或查询其当前配置。因此,你可以自由地以某种方式暴露这些方法,以便你可以在不重启服务器的情况下执行它们;但是,如果你确实暴露这些方法,出于安全原因,你应该只在一些内部或授权的端点上这样做。
此外,重构的一个愉快副产品是,Middleware
的零值现在可以使用,尽管它仅对应于一个"直通"中间件。
不过,有一个警告:请注意,频繁的CORS中间件重配置可能会加剧缓存问题;我参考Jake Archibald关于CORS的博客文章的相关部分。
与rs/cors基于钩子的方法比较
再次,jub0bs/cors可以与rs/cors进行比较,在撰写本文时,rs/cors仍然是最受欢迎的Go CORS库。
rs/cors的灵活性源于其多个"钩子":
|
|
通过这些钩子,你可以指定区分允许和不允许的Web来源的策略。然而,我相信,出于之前文章中解释的原因,这样的钩子容易出错且过于强大,对你的好处不大;因此,我坚定地拒绝在我的CORS库中引入类似的东西,我仍然这样做。
相反,为了在jub0bs/cors中支持动态中间件可重配置性,我基本上将rs/cors基于钩子的方法内外翻转,就像翻转袜子一样:不是指定代表你策略的回调,你必须以*Config
值的形式声明性地描述你想要的配置,并将该值传递给你的中间件的Reconfigure
方法。
这种方法带来了几个不可低估的好处:
- 灵活性:你可以更新不仅仅是允许的来源集,而是你的CORS中间件的整个配置:允许的来源、允许的方法、允许的请求头等。
- 正确性和安全性:你不能(除了错误)得到一个功能失调或不安全的中间件:jub0bs/cors确实拒绝任何它认为无效或不安全的配置,不仅在中间件初始化时,而且在中间件重配置时。你的中间件要么收到一个干净的更新,要么根本没有更新。
- 性能:在所有时间,中间件的配置都存在于内存中,在优化用于处理CORS请求的数据结构中;中间件重配置仍然相对昂贵,特别是在堆分配方面,但在重配置频率低于中间件调用(至少一个数量级)的合理假设下,你可以期待良好的整体性能。
jub0bs/cors <= 0.1.2中的关键漏洞
最后,请允许我做一个简短但重要的关于安全的题外话。jub0bs/cors依赖的数据结构之一是一个专门的基数树。在实现中间件可重配置性时,我痛苦地意识到,由于我的基数树实现中的一个错误(无疑是由于我变量命名不当引入的),jub0bs/cors包含一个关键漏洞:在v0.1.2及更早版本中,一些CORS中间件允许一系列不受信任的Web来源。
例如,指定来源模式https://foo.com
和https://bar.com
(按此顺序)会产生一个中间件,该中间件会错误地允许不受信任的来源https://barfoo.com
。v0.8.0的jub0bs/fcors同样易受攻击。
这个关键漏洞是另一个警示故事,即100%的代码覆盖率不能保证没有错误。在自己的代码中发现关键漏洞必须是开源开发者的最坏噩梦,但这一事件对我来说特别尴尬:不仅我自称安全研究员,而且仅仅几天前,我还在抱怨rs/cors的维护者修补一个完全次要漏洞的悠闲步伐,并吹捧jub0bs/cors可能是Go最好的CORS中间件库… 😬
Web安全的研究是一堂永无止境的谦逊课。尽管有最好的意图,一个人编写的每一段软件都必然在某个阶段包含漏洞;重要的是一个人如何响应它们的发现。
在这种情况下,我在4月30日识别并修复了这个错误;但为了更好的时机,我推迟发布安全公告、通知Go漏洞数据库和发布修补版本(v0.1.3),直到5月2日(CEST)。我向任何受影响的人道歉,如果你还没有尝试jub0bs/cors,我希望你不会被劝阻。
致谢
感谢Laurent Demailly、Scott Plunkett和Mike Stephen,他们分别在Gophers Slack、Reddit和通过私人通信与我分享了关于我预期API变更的建设性反馈。