可动态重配置的CORS中间件与jub0bs/cors
TL;DR
在这篇对之前文章的简短跟进中,我描述了为什么以及如何在jub0bs/cors中添加了对CORS中间件动态重配置的支持。
重新思考配置不可变性
直到最近,我一直强烈主张CORS中间件不应在运行时重新配置,任何配置更改都需要服务器重启:
由于CORS放宽了同源策略(SOP)强制执行的一些限制,它是一个安全关键机制。因此,服务器的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进行比较,在撰写本文时,它仍然是最流行的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更改的建设性反馈。