我们是 Fly.io。这篇文章与 Fly.io 无关,但无论如何您都必须了解我们,因为我的博客,我的规则。我们的用户向我们运送 Docker 容器,我们将它们转换为 Firecracker 微虚拟机,我们将其托管在我们自己的全球硬件上。使用工作中的 Dockerfile,启动和运行将花费您不到 10 分钟的时间。
这实际上并不是一篇关于 Fly.io 的帖子,尽管我会提前谈谈我们以设置场景。
我生命中的最后几周一直在关注 API 安全。我正在为 Fly.io 开发一个新的权限系统,并对我的选择进行了大量研究。我们甚至录制了一个关于它的播客。我不会让您悬而未决,而是直接告诉您:我们正在努力推出基于 Macaroon 的计划,稍后您将了解更多信息。
这篇文章很长。您可能只对一种令牌感兴趣。我会为您提供方便:这是目录:
Fly.io 是一个应用托管平台。把我们想象成有一个运行在 Fly.io 上的应用程序与之交互的控制平面,以及一个我们的用户与之交互的 API——主要通过我们的 CLI flyctl
,. flyctl
这就是我们在这里谈论的那一块。
今天,Fly.io API 访问是孤注一掷。每个人都有根凭据。我们想要的是细粒度的权限。这是我们要解决的两个大问题:
这就是人们通常所说的 IAM 的 API 工作。有很多不同的方法可以完成 IAM 的工作,而且它们都很有趣。
我在这里感兴趣的是 API 安全性,面向最终用户;“零售”安全。
我不是在谈论一个密切相关的 API 安全问题:服务间身份验证。现代应用程序由小型服务的集合组成。理想情况下,它们之间有一个安全层。但是没有人使用 Kerberos 或 mTLS 零售 API IAM。如果您想了解更多关于这些方法的信息,我在其他地方写了一篇关于它们的长文。
另一个相关问题是联合身份验证和单点登录。Google、Apple 和 Okta 将为您提供将请求映射到其平台上的身份的令牌。这些令牌格式在这里是相关的,但我想明确一点,联合身份不是我所追求的。
大多数 API 安全方案归结为伴随 API 请求的令牌。令牌以某种方式与访问规则相关联。API 看起来接受请求,提取令牌,找到访问规则,并决定如何继续。
当您阅读本文时,一些问题要留在您的脑海中:
现在是 2021 年,所以我不需要告诉你让你的 API 通过 HTTP 基本身份验证传递用户名和密码是个坏主意。无论它们是什么,您的令牌都应该看起来很大且随机。
这是一个令牌生成器,从安全角度来看,它很难被击败:
>>> binascii.hexlify(os.urandom(16))
b'46d684a052c29cdce14c7e03e19da0f9'
保留一张随机令牌表,将它们与用户表相关联,并将这些用户与允许的操作相关联。你不需要我告诉你怎么做;这就是 CRUD 应用程序的工作方式。
您可能需要我告诉您的是,即使从长远来看,这是处理 IAM 问题的好方法。随机令牌在密码学上并不可怕。它们很容易被撤销和过期。随附的权限逻辑清晰且富有表现力;这只是您的 API 代码。
坦率地说,对简单随机标记的最大打击是它们很无聊。如果你能侥幸使用它们——大多数应用程序都可以——你可能应该这样做。允许自己说你是出于安全原因这样做。从现在开始,对于我要讨论的所有花哨的代币来说,安全性都是一个问题。
假设我们试图最小化必须访问数据库的请求的比例。主流 Web 应用程序框架往往已经具有帮助实现此目的的功能。
例如,Rails 具有MessageVerifier
和MessageEncryptor
. 给他们一袋属性并取回一个防篡改(可选加密)字符串,使用 HMAC-SHA2 和encrypt-then-MAC AES-CBC。将字符串放入 cookie 中。服务器只记住一个根密码,并且可以将用户数据从 cookie 中提取出来,而不是从数据库中提取出来。这就是 Rails 会话的工作方式。
Python 框架具有类似的功能,但也有出色的 Python pyca/密码库,其中包括Fernet
,它提供了为令牌优化的相同功能。
您通常不能将通用用户会话用于 API 令牌(API 令牌的定义特征是它不会注销)。但是您可以使用相同的功能来构建 API 令牌。在多个服务之间共享根秘密——也许这很好——并且微服务不必依赖中央服务。
平台代币相对简单,可以是无状态的。有什么问题?好吧,您有效地将令牌用作数据库缓存,而缓存一致性令人沮丧。
马上,您就失去了最简单的令牌撤销形式。整个前提是您没有针对数据库验证令牌,所以现在您必须想出另一种方法来判断它们是否已被撤销。如果没有更新它们的标准协议,短期到期令牌也不起作用。
我在这里看到了很多模式,而且我有点喜欢,就是对用户进行“版本化”。在用户表中粘贴一个令牌版本,让令牌承载当前版本。要撤销,请在数据库中修改版本;未完成的令牌现在无效。当然,你需要保持状态才能做到这一点,但状态非常便宜;用户版本的 Redis 缓存,回退到数据库,就可以了。
之前附加到“平台Tokens”部分的所有展品和附录特此并入本部分并成为其中的一部分。
按照设计,OAuth 是一种联合协议。典型地,OAuth 允许第 3 方使用您的帐户发布推文。这不是我们要解决的问题。
但是 OAuth 2.0 很受欢迎,并且遇到了您在使用令牌时可能遇到的每一个乏味问题,并且他们提出了各种粗略程度的解决方案。您可以并且很多人都可以在您自己的 API IAM 情况下草拟该工作。
例如,OAuth 2.0 有一个内置的短期令牌解决方案;OAuth 2.0 有一个“刷新令牌”,用于交换“访问令牌”。访问令牌是您在 API 中实际使用的令牌,它们很快就会过期。OAuth 2.0 库知道如何使用刷新令牌。而且它们很容易撤销,因为它们的使用频率较低并且不会惩罚数据库。
OAuth 2.0 访问令牌是不透明的字符串,因此您可以对它们执行与使用平台令牌相同的操作(或者只是在其中填充一个平台令牌)。
OAuth 2.0 中的“密码学”很简单。它在独立的单页应用程序中变得很棘手,但其他一切也是如此。我曾经对人们在简单的客户端-服务器应用程序中使用 OAuth 进行攻击。不再。
简短的历史课。我们得到了 OAuth,应用程序可以代表用户发推文,上帝看到了他所做的一切,这很好。然后有人意识到,如果您可以代表用户发布推文,您可以使用该功能作为身份证明并“使用 Twitter 登录用户”。推文本身变得无关紧要,人们只是使用 OAuth 令牌,可以读取您的用户个人资料作为身份证明。
这是一个问题,因为读取您的用户配置文件的能力并不是一个好的身份证明。您可能出于与是否可以“使用 Twitter 登录”到约会应用程序无关的原因授予应用程序该功能。人们发现了一堆漏洞。
输入 OpenID 连接 (OIDC)。OIDC 是 OAuth 2.0 和称为 JWT 的加密令牌标准的恶魔联姻。OIDC 是明确的:它给你一个“身份令牌”,JWT 编码,告诉你谁在登录。
我们在这里对 OIDC 不太感兴趣,但是让 OIDC 诞生的可怕仪式将大量 JWT 释放到世界上,这是我们现在必须考虑的事情。
从纯粹的功能角度来看,JWT 所做的不仅仅是嵌入在 OAuth 2.0 中的平台令牌。但是 JWT 是标准化的,而“使用 Fernet 加密并嵌入 OAuth 访问令牌中的 JSON”不是,因此围绕 JWT 涌现了大量的开发用户体验。所以,不幸的是,JWT的人机工程学非常好。
是什么让这很不幸?JWT 不好。
这不是一篇关于为什么 JWT 不好的帖子,尽管我确实希望你能不同意我的观点。所以我会简短。
首先,JWT 是由委员会设计的密码厨房水槽。JWT 可以使用 MAC 进行保护,例如 HMAC-SHA2。或者使用 RSA 数字签名。或使用静态临时 P 曲线椭圆曲线 Diffie Hellman加密。这与其说是脚枪,不如说是整个岩岛兵工厂都部署在你的脚上。如果您是加密漏洞的狂热爱好者,那么您几乎必须爱上它。除了 TLS 之外,您还能在哪里找到无效的曲线点攻击?
其次,JWT 的 JSON 语义没有经过深思熟虑的设计。JWT 不会将用途甚至域参数绑定到密钥,并且 JWT 库的编写假设 RSA 和 HMAC-SHA2 只是同一问题的可互换解决方案。因此,您会遇到人们采用 RSA 签名的 JWT 并将JWT 标头从 RS256 切换到 HS256 的错误(甚至不要让我开始使用这些名称),并且库会不经意地将公共签名密钥视为私有 MAC 密钥。此外,还有alg=none
.
JWT 如此受欢迎,以至于它已成为无状态身份验证令牌概念的同义词,尽管事实上无状态令牌在没有 JWT 的情况下(并且在此之前被广泛使用)很简单。
从某种意义上说,抱怨 JWT 简直就是在月球上嚎叫,因为它在 OIDC 中是非可选的,而 OIDC 是 Google 和 Apple 实现单点登录的方式。播客之友 Jonathan Rudenberg对此有很好的观察:如果您的应用程序保持与(例如)Apple 的直接连接,您可以稍微安全地使用 OIDC JWT,只需信任您与 Apple 服务器的 TLS 连接; 你甚至不需要关心令牌本身的密码学错误。
有连恶魔都无法忍受的仪式。OIDC 的竞争对手是基于XML DSIG的 SAML,这是一种将 XML 文档转换为签名令牌的方法。您不应将 XML 文档转换为签名令牌。您不应该签署 XML。XML DSIG 是 Internet 上最常用的加密格式。考虑 JWT 的所有缺陷,包括对不受信任数据的广泛解析,只是为了弄清楚如何验证内容。混入一个 DOM 模型,其中单个文档可能有几十个不同的签名子树,然后添加一个可插入的规范化层,在文档签名之前对其进行转换. 让它足够复杂,以至于每个 SAML 库都包含一个规范的 C 语言实现。你显然不会用它来验证你的 API,但是,如果你不知道,我在这里从我的系统中取出一些东西。
PASETO(与“土豆”押韵)是时髦的 JWT。我的意思是最好的方式。它具有与 JWT 基本相同的开发人员 UX,但试图将令牌锁定到现代密码学中。
JWT 是一个加密厨房水槽。PASETO 是较小的浴室洗手盆。我在这里很挑剔,因为 PASETO 出于某些充分的理由,在象征书呆子中做得很好,不需要我的帮助。
今天有四个版本,每个版本都定义了两种令牌,一种对称的“本地”和一种非对称的“公共”。版本 1使用“符合 NIST 的”AES-CTR、HMAC-SHA2 和 RSA。版本 2具有 XChaPoly 和 Ed25519。版本 3将 RSA 替换为 P-384 ECDSA。版本 4将 XChaPoly 替换为 XChaCha 和 Blake2 KMAC。您可以交换v4
使用v4c
CBOR 而不是 JSON。很多。
我对 PASETO 的问题是它本质上与 JWT 相同。通过添加一些算法并禁止其他一些算法,您几乎可以从 JWT 构建它。
PASETO 提倡现在接受的对整个协议进行版本控制的做法,而不是动态协商参数。这应该是一个强大的优势。但是 PASETO 有 8 个版本,其中 4 个是“当前的”,我认为 PASETO 遗漏的协议版本控制的部分想法是你不应该让多个版本到处乱跑。版本 3 和 4 部分是由于Thai Duong 发现的一个漏洞(不是超级严重的漏洞)。PASETO 库支持多个版本,在某些情况下是动态的。杀死旧版本!
IRTF CFG 是 IETF 的密码学审查委员会。由于我永远无法理解的原因,PASETO 作者将其提交给 CFRG 以供考虑。永远不要这样做。在帖子中,Neil Madden 指出它已经设法继承了 JWT 的 RSA/HMAC 问题;所有 PASETO 版本现在都有一个“算法清晰”警告,告诉人们确保他们用力键入他们的键。
我不认为这是 PASETO 的错,因为我认为基本思想是不可能的三位一体:密码灵活性、密码误用性和 Javascript-y 开发人员用户体验。
此外:“符合 NIST 标准”的 PASETO 版本是非强制错误。
JSON 令牌对随机用户属性包以及令牌元数据(如发行日期和受众)进行身份验证,这让我很生气。密码学工程师听到我对此的抱怨并挠头,但我认为他们主要考虑的是携带最少数据的 OIDC JWT,而不是所有奇怪的 JWT 开发人员在其中用户数据与元数据混合在一起。这对我来说似乎也是一个非受迫性错误。很多元数据都是可选的这一事实也是如此。为什么?这一点很重要!
尽管如此,使用 PASETO 比使用 JWT 要好得多。我对 PASETO 的看法是,如果你使用它,你应该清楚地知道你想要对称还是非对称令牌;它们是具有不同用例的两种不同的东西。只支持一个版本。
只需定义自己的强类型协议格式,您就可以获得 PASETO 试图做的事情,而没有任何缺点。David Adrian 称这些为“Protobuf 代币”。
您要做的就是定义一个如下所示的 Protocol Buffer 模式:
message SignedToken {
bytes signature = 0;
bytes token = 1;
}
message Token {
string userId = 0;
uint64 not_before = 1;
uint64 not_after = 2;
// and other stuff
}
将所有令牌语义推送到Token
消息中,并使用 Protobuf 编码的第一遍将其编组为字符串。使用 Ed25519 对其进行签名(将“Protobuf-Token-v1”之类的版本字符串连接到签名块中),将令牌字节字符串粘贴到token
a 的字段中SignedToken
,然后填充签名。再次元帅,你就完成了。
这种两遍编码为您提供了两件事。首先,只有一种方法可以解码和验证令牌。其次,令牌中的所有内容都已签名,因此对元数据进行签名没有歧义。令牌紧凑,易于使用,并且可以扩展(协议缓冲区擅长于此)以携带任意可选声明。
你根本不需要令牌。相反,您可以只拥有钥匙。使用它们来验证请求。这就是 AWS API 的工作方式。
我们倾向于向我们的 API 发送正常的 HTTP 请求,并传递一个带有“不记名令牌”的附加标头。不记名代币就像不记名债券,如果你把熊爪放在上面,游戏就结束了。经过身份验证的请求没有这个问题。
为此,您需要 HTTP 请求的规范化方案。同一个 HTTP 请求有多种表示;我们只需要决定一个来计算 MAC 标签。这似乎很容易,但却是AWS 方案早期实施中的漏洞来源。只需使用AWS 的版本 4。
在规范化的请求上计算 HMAC(使用 AWS,您只需使用您的AWS_SECRET_ACCESS_KEY
)并将生成的标签作为参数附加。
上帝帮助你,你可以在这里使用 X509 并颁发人们可以用来签署请求的证书和密钥,这显然是 Facebook 内部所做的事情。
关于经过身份验证的请求有很多好处。没有不记名令牌,没有熊。最大的问题是后勤问题:构建请求身份验证代码很麻烦,因此,除非您的应用程序变得庞大,否则与之对话的唯一方法是使用您的官方 SDK 来完成所有请求签名工作。
所以,这是一个很酷的技巧。你有一堆服务,比如Messages
andPhotos
和Presence
and Ivermectin Advocacy
。而且你有一个中央Authentication
服务,你的服务和你的用户都可以与之交谈。
Authentication
持有根密钥。Messages
上线,并建立(例如)与Authentication
. 它发布了一个服务密钥,即HMAC(k=root, v=“Messages”)
.
现在一个用户“Alice”来了。Authentication
给她一把钥匙。是HMAC(k=HMAC(k=root, v=“Messages”), v=“Alice”)
。
看看我们在那里做了什么?Messages
没有爱丽丝的钥匙。但她的密钥只是密钥下她用户名的 HMAC Messages
,因此服务可以重建它并验证消息。
您可以使用类似 CATS 的构造来签署请求,或签署 Protobuf 令牌(使用 HMAC 或 AEAD,而不是 Ed25519)。您将获得公钥密码学的一些解耦优势。Messages
只需要偶尔联系Authentication
, 来注册自己并定期轮换密钥。这足以验证来自所有来者的请求,相信 Alice 获得她的密钥的唯一方法是如果Authentication
它成功了。
我们不需要 5,000 字就能告诉您如何让应用程序在世界各地(从悉尼到阿姆斯特丹)的用户附近运行。只需要一个工作的 Dockerfile
我们可以去安静干燥的地方散步,谈论Macaroons。
想象一下您的服务的金票,一个允许任何操作的经过身份验证的令牌。作为不记名令牌传递太危险了。
现在想象一下为那个金色令牌添加警告。你只能读,不能写。仅适用于单个文档。仅针对来自特定 IP 的请求,或针对特定用户 ID 独立验证的会话。这种衰减的令牌危险性要小得多。实际上,您可能会将其锁定得如此之深,以至于它甚至都不敏感。
我们利用 CAT 用来派生用户密钥的相同技巧。从您的金票开始,并在根密钥下对其进行 HMAC。现在您想将其设为只读,因此您向令牌添加另一个消息层,并使用前一层的 MAC 标签作为密钥对新层进行 MAC 化。新代币的持有者无法计算出金票的原始MAC标签;令牌仅携带新的链式 MAC 标签。但是服务具有根键并且可以重新派生所有中间值。
Macaroon 是围绕这个想法构建的令牌格式。他们做了三件大事。
衰减:用户可以在不与发行服务对话的情况下限制令牌。所有的警告都必须评估true
。你不能用新的警告来撤销以前的警告。该服务只知道基本的警告类型,不需要用户可能想要的所有愚蠢组合的特殊代码。
限制:如果你有正确的警告类型,你可以设置它,以便有有用的 Macaroon 可以安全传递,因为它们仅(比如说)在特定 mTLS 客户端证书下的会话中有效,或在特定一天中的时间。
委托:Macaroons 有“第三方警告”,将逻辑委托给其他系统。第三方警告被加密;用户只能看到他们可以与之交谈以解决问题的第三方服务的 URL。第三方系统发出“出院马卡龙”,与原马卡龙一起提交以解决警告。
这些想法相辅相成。您可以将身份验证委托给 IAM 服务,然后添加其他特定于服务的访问规则作为第一方警告。撤销服务验证用户的令牌;系统的其余部分不需要知道撤销是如何实现的。审计日志和反滥用也是如此。
有时候世界上有这么多美丽,我觉得我无法接受,就像我的心要塌陷一样。
但Macaroons不受欢迎是有充分理由的。
第一:Macaroons 有一个图书馆生态系统,但不是很好。没有库可以支持开发人员想要的所有甚至大部分警告。因此,“标准”Macaroons 使用无类型字符串 DSL 作为警告格式,并要求依赖服务解析它们。
它们也很笨重。对于大多数以前的格式,您可以想象将它们放入 OAuth 2.0。但第三方警告打破了这一点。你的 Macaroon API 会很麻烦。用户可能必须生成并存储一堆查询的结果才能发出真正的请求。
Macaroon 依赖于对称密码学。这有好有坏。它从根本上简化了系统,但意味着您必须使用共享密钥来表达服务之间的关系。当然,您也必须使用 HS256 JWT 来做到这一点,但除非您完全脱离Macaroon 论文,否则您无法获得公钥胜利,除非想出类似 CAT-caroons 之类的东西。
在实践中,警告可能很难推理。很容易在一组警告上编写一个循环,一旦评估就会爆炸false
。但是您可能会不小心引入产生警告的语义,而不是合同授权。您的代码想要回答“我可以这样做吗?” 通过询问数据库有关用户 ID 的问题,您可以编写做类似事情的警告结构,这在连贯的 Macaroon 设计中绝不是您想要的。
这些问题我还有话要说!不过,就目前而言,我可以说我花了很多年为 Macaroon 敲鼓,然后我去实现它们,我可能不会再敲鼓了。但在他们运作良好的地方,我认为他们可能运作得非常好。我的看法是:如果衰减、限制和授权这三者都与您的设计产生共鸣,那么 Macaroon 可能会工作得很好。如果您跳过这三个中的任何一个,请考虑其他内容。
最后,还有Geoffroy Couprie 的Biscuits。如果你坐下来写一篇像这样的超长博客文章,做了所有的研究,然后决定写一个加密令牌来解决其他所有令牌的缺点,你就会得到Biscuits。
Biscuits深受马卡龙的影响(Couprie 声称它们也受到 JWT 的影响,但我没有看到)。像Macaroons一样,用户可以减薄Biscuits。但不像Macaroons:
Biscuits是令人难以置信的雄心勃勃。
首先,将 Macaroon 中的简单密码学换成公钥签名并不是一件容易的事。向 Macaroon 添加警告的加密过程很简单:您只需将前一个警告中的 MAC 标签作为新警告的 HMAC 密钥提供。但是对于签名没有相对简单的操作。
为Biscuits提出的密码学始于配对曲线月亮数学。Keller Fuchs 用曲线 VRF 将它们拉回近地轨道。然后他们绕道进入了带有聚合伽马签名的区块链。但最终,Biscuit 的核心密码学以非常简单的 Ed25519 签名链回到了地球。
Biscuit 代币的警告结构是灵活的,可能是错误的,但形式上是严格的,这是一个有趣的组合。它通过评估一系列签名程序(使用协议缓冲区编译和编组)来工作。服务从请求中派生事实模式,例如“您请求cats2.jpg
”或“您请求的操作是WRITE
”。令牌本身包括派生新事实模式的规则,以及针对谓词测试这些模式的检查器。
老实说,当我第一次读到Biscuits时,我觉得它很疯狂。如果该提案没有让我在“配对曲线”上迷失方向,那么在它开始描述 Datalog 时它已经迷失了方向。但后来我为自己实现了 Macaroon,现在,我有点明白了。Biscuits 让您了解的一件事是其他代币无法做到的,那就是明确代币授权的操作。以文本呈现,Biscuit 警告读起来就像政策文件。
这是我认为我对他们唯一的大担忧。我想知道要真正利用 Biscuits 是否需要您将所有授权逻辑基本上都转移到您的令牌中。即使是以前拥有“最具表现力的代币”称号的 Macaroon,主机服务仍然在首先决定可以表达哪些警告方面做出强有力的选择。Biscuits 将服务对授权策略的贡献剥离到看起来像它们的组成原子,并在 Prolog 中派生所有安全策略。我知道它有多强大,但也知道你如何不得不批发购买它才能使用它。
这是一个记分卡:
信不信由你,除了密码和 SAML,我认为在所有这些方案中都有一些值得喜欢的地方。
我仍然认为,无聊的、值得信赖的随机令牌被低估了,人们为了追求他们无法实现也不需要的无状态而消耗了大量的复杂性,因为 Facebook 以外的大多数系统的令牌数据库并不难扩展。
几个月前,我会说蛋白Macaroons干以不同的方式被低估,就像大明星的“#1 Record”一样。现在我认为这只是像第一部性手枪节目那样被低估了;每个阅读过它们的人都创建了自己的令牌格式。我们正在推进 Macaroon,我对此很兴奋,但我会犹豫是否将它们推荐给典型的 CRUD 应用程序。
但是,不要使用 JWT。
近期阅读文章
,质量尚可的,大部分较新,但也可能有老文章。开卷有益,不求甚解
,不需面面俱到,能学到一个小技巧就赚了。译文仅供参考
,具体内容表达以及含义, 以原文为准
(译文来自自动翻译)尽量阅读原文
。(点击原文跳转)每日早读
基本自动化发布(不定期删除),这是一项测试
最新动态: Follow Me
微信/微博:
red4blue
公众号/知乎:
blueteams