权限,也称为授权,是授予系统中资源的访问的过程。对于任何团队,对获得权限至关重要。在Carta,我们整天都在使用财务数据,这是最重要的事情。
但我们有一个问题。我们不是维持一个遗留系统,我们保持五个。权限可能会发生冲突 - 他们无法扩展。我们的业务需求正在增长,我们在漏斗中有几种新产品。
很明显,我们必须建立一个新的授权系统,以创造工程和产品的杠杆。我们知道我们需要它是三件事:
听起来很简单,但实际上它并不容易。在我的职业生涯中,我已经看到了太简单的权限系统。它们缺乏支持单一资源的细粒度访问的功能。我也看到他们太复杂了。一个小型更改可能会解开基于属性的权限的整个策略。
在本文中,我们将研究我的团队 - 身份和访问管理 - 采用创造性的方法来避免通过基于Google Zanzibar的基于Carta的权限系统来避免那些陷阱。
我们的工作开始于2019年中期,我们开始分解我们的单片申请。工程师正在开发腐败的服务,并需要一种授权传入请求的方法。许可数据仍然耦合到整料中的传统许可系统。
我们的第一个实验是分布式的基于令牌的访问控制系统。我们通过编码的JWT令牌在服务之间传递了用户权限数据。分布式令牌使得进入快速检查,但建立巨大的成本。令牌建设时间具有很长的尾部,并对具有大型权限集的电力用户造成性能问题。
一些令牌的大小达到了近1MB(!!)。几乎每个请求有效载荷都有一个嵌入式令牌和响应时间将在服务之间传递的大令牌受到影响。
此外,授权令牌不可扩展。服务所有者必须为新的权限集构建自己的系统。我们决定废除授权令牌并调查其他选项。
在我们发现过程中,我们评估了开源和供应商产品。我们评估的许多项目都不足够粒度,可以处理我们的用例,所以我们统治了它们。然后我们重点关注避免供应商锁定。
我们确定了一个名为Open Policy Agent(OPA)的有前途的项目。 OPA是一个提供用于授权检查的单个接口的框架。
opa用作授权策略的记录系统。域服务将代码定义的权限策略推送给OPA。网络流量通过OPA,策略用于授权或拒绝传入请求。 OPA是为分布式系统而构建的,因此它会跨群集缩放。
opa有一些缺点。它需要一大集的自我管理基础设施。许可检查也很慢。使用OPA,消费者控制底层数据源。您在OPA中存储策略代码,但策略查询域级数据进行访问检查。这适用于简单的权限,但复杂的是对我们引起的重大问题。
复杂的权限通常会查询几个数据模型,并添加了数百毫秒的响应时间。这不会为我们工作。
其他项目未能满足数据的要求,因为它们在基于角色的访问控制(RBAC)上运行。 RBAC通过允许角色授予对用户的访问权限。由于一个名为“角色爆炸”的现象,角色与大数据集打破。
当用户对几个不同的系统实体有许多角色时,会导致角色爆炸。 (例如,如果用户有10个不同实体的角色,则用户具有40个托管角色。)如果系统中存在数千个实体,则此数字没有上限,并且可以成为数百万个托管角色。
在CartA中,用户权限很复杂,因为它们是否遵守投资组合资产。用户可以访问他们拥有的一组共享的个人组合。他们还可以通过投资公司的基金来获得证券。或该基金甚至可能投资于本公司拥有股份的另一个基金。最重要的是,公司可能可以访问您的投资组合,以便他们可以管理证券。
用户在每个投资组合中都有权限。拥有数百万的平台上的投资组合和证券,我们的权限系统必须管理大量的个性角色。
2019年中期,谷歌发布了一篇名为“Zanzibar:谷歌的一贯,全球授权制度”的白纸。
谷歌使用桑给巴尔为每秒提供数百万授权请求。几种高流量产品,包括YouTube和Drive,使用桑给巴尔授权。桑给巴尔是强大的,因为它是可扩展,灵活,快速的。
桑给巴尔的核心功能之一是一种统一的语言,用于定义权限。桑给巴尔消费者使用统一的语言来构建访问控制列表(ACL)。 ACL就像Unix文件权限。他们为用户提供系统中的各个资源。
使用Zanzibar,服务为用户权限组撰写抽象。用户权限组可以互相组成。他们还授予对低级资源ACL的访问权限。
与OPA不同,桑给巴尔是数据的真理来源。消费者只是在不考虑如何实现快速查找的情况下添加权限。
但我们有一个问题:没有任何开源实现。我们决定建立自己的系统,添加了几个自己的修改,使其更适合Carta。
此时,我将从Authz提到我们的下一代授权系统。 Authz代表授权。
authz通过添加和删除相关性来配置权限。关系是一个由演员,关系和对象组成的元组。
演员:执行动作的实体(例如用户:Bob)。演员也可以是实体的分组(例如,组:7名成员)。它甚至可以是非用户(例如服务:通知服务)。
关系的每个部分都是自由形式,因此我们不会限制您可以使用Authz设置的权限类型。例如,“读取”和“问题_Certificates”都是有效权限。
消费者使用特定于域的语言来组合关系。关系策略在演员和对象之间创建直接访问。相关性自动暗示Actors和对象之间的间接访问。我稍后会解释一下这使Authz如此强大。
直接关系:数据库中的一行已被客户端明确设置
间接关系:数据库中不存在的行,但图表通过间接关系提供对actor的访问。
关系构建权限图。消费者查询权限图以确定Actor是否可以访问对象。如果actor和对象之间存在直接或间接关系,则演员可以访问对象。 (这个过程的一个很好的助学版:“演员在物体上行动。”)
遍历图形是昂贵的,因此Authz维护了自定义二级索引。辅助索引响应在子10ms响应时间中对权限图的访问检查。
该系统的体系结构超出了本文的范围。我们稍后会发布关于它的信息,因此请按照构建CartA进行更新。
在Carta,我们通过建立最低可行的产品(MVP)启动每个新项目。内部服务也不例外。 MVPS帮助您的团队收集有价值的早期反馈。早期反馈可以防止您的团队在花时间建立错误的功能。
我们的团队开始从消费者中汇总了几个要求。此时,我们的身份验证令牌仍然被大多数域服务用于查询遗留权限。大多数团队都希望成为建立新权限的方法。
我们开始从Zanzibar实现索引算法的基本版本。我们的第一个目标是在新权限上测试这个系统。
速度是Authz的主要要求之一。我们构建了Zanzibar指数的原型,以验证我们可以实现类似的性能水平。
我们建造了原型后,我们开始进行测试。我们与我们的Zanzibar指数版本进行了多次测试。我们将Authz的自定义指数与基线度量的替代品进行比较。
使用Zanzibar自定义索引的查找时间非常快。关于密集图的检查并没有比对稀疏图的检查的速度慢得多,并且索引比替代优化的缓存策略快。
通过较高的平均时间才能添加边缘,构建自定义索引的初始成本。
最终,这成为一个非问题,因为Authz使用异步管道构建索引。消费者不必等待索引更新以通过系统传播。为了减少负载,索引更新也分布在几个工人之间。随着消费者人数的增加,我们可以扩大工人以更快地处理索引事件。
Authz的检查端点表现给我们的消费者印象深刻,但他们觉得API缺乏功能。大多数用例都添加了多个关系。在更新期间消费者称为Authz。每个呼叫都会添加个人关系。
围绕可扩展性也有一些担忧。此时,索引不是异步构建的。因此,更新很慢。由于单独添加权限,因此完整更新可能需要几秒钟。
最后,最大的担忧之一缺乏对系统的可见性。一旦他们推入authz,就无法检查权限。在没有此功能的情况下迁移现有许可模型是不可能的。
这是听到的很难反馈。但这就是为什么Carta迭代地工作。他们没有说“不。”他们正在说“现在不对”。
我们知道如果我们添加了几个功能(对于已经部署的MVP),我们可以获得我们想要的采用。
此反馈是我们初始要求的正指标。 MVP解决了我们旨在解决的大多数问题。缩放问题并不大笔交易。我们可以通过实施来自桑给巴尔文档的更多功能来解决这些问题。
MVP确定了我们没有预测的东西:我们的消费者想要的工具。它们优先的软件,帮助它们使用系统,而不是系统本身的功能。由于来自消费者的需求,我们快速优先考虑了工具。
我们的消费者也有问题添加相关性。他们不想为引导权限进行50个单独的请求。相反,他们希望在单个呼叫中添加多个权限。此客户请求成为模板功能。
在我们的初始测试期间,我们构建了一些工具来调试索引的问题。其中一个工具是一个简单的图形可视化器。给定一组关系,它能够生成图形的JPEG图像。
我们考虑了创建一个端点,为消费者提供这张照片,但很快就会决定这是一个坏主意。我们决定构建查询API,让消费者查看图形的部分。这些API充当了我们的后端。然后,我们创建了一个Concord,一个用作呈现图层的Web应用程序,以可视化图表中的节点。
此功能仅解决了可发现性的大部分问题,具有识别和检查先前添加的权限的能力。
在我们的测试期间,我们注意到一些服务拙劣的其他服务权限。当两个服务应用于相同的权限类型时,会发生这种情况。
例如,一个服务可能会增加银行帐户的权限。另一个可能会增加用户帐户的权限。两者都可以尝试用演员类型写入:“帐户”。一个服务可以覆盖另一个服务的变化。
我们引入了名称空间以分隔域级权限。命名空间阻止权限覆盖,但促进跨域查询。服务写入他们具有写入访问的一个或多个名称空间。如果服务尝试写入名称空间,则无权访问,Authz拒绝更新并抛出错误。
CartA使用共享名称空间,以实现一些用例。例如,用户权限在一个名称空间中;文档权限在另一个。 Authz授予身份公共命名空间的文档服务访问权限。文档服务可以为用户提供对文档权限的访问权限。
通过引入相关性的模板,我们解决了批量更新问题(添加多个权限)。模板允许消费者预定定义在Authz中应用的一组更改。模板使更新明确和可重复。
消费者使用Mustache模板语言构造模板,并在发生更改时将模板传递给Authz。 Authz将数据注入模板并执行批量更新事件。
#这名命名空间设置管理员{{#admin}} authz_service:{{service_id}},admin,authz_namespace:{{namespace_id}} {{/ adminace_id}} {{/ admin}}#如果需要它,则提供其他服务写访问{{#write} } authz_service:{{service_id}},create_any_relation,authz_namespace:{{namespace_id}} {{/ write}}
Authz使用此模板来为新命名空间添加权限。 Authz注入更改事件的数据。在这种情况下,命名空间ID和可以访问命名空间的任何服务。该模板将批量更新应用于权限图。未来的Authz调用将包括模板应用的关系。
在一些用例中,服务显示用户可以访问的资源列表(即搜索页面)。
使用我们的查询API遍历图表返回正确的数据,但它很昂贵。深图可能需要几秒钟来检索设置的结果,而使用自定义索引时的数十毫秒。
我们确定我们可以重新定制自定义索引来检索演员的平面资源列表。由于消费者有效地平衡了图形,我们称之为这个API“扁平”。
该索引使用特殊过滤器来减少返回消费者的结果。消费者使用扁平的输出来查询其数据库以获取资源以向用户展示。
列表查找随着索引的速度要快得多,但是当消费者使用过滤器时减慢了。
我们在自定义索引之上构建了一个TrIgram指数,以支持更快的过滤器查找。所有这些优化将查找时间降低到95百分位的50ms。
持续的“构建,衡量,学习”反馈循环使我们的团队能够提供立即值。新功能推动了整个公司的采用。竞争验证的团队是因为它是表演且易于使用的。
但这不是很多。人们希望使用新系统查询旧权限。为了进一步采用,我们实施了遗留权限代理。
代理使团队能够调用Authz for Leavency权限,而不是使用JWT令牌。遗留许可检查比Authz呼叫慢。但代理鼓励一个接口。在单个界面上迁移客户比在两个不同的系统上更容易。
目前,Authz服务我们的生产环境中的七种不同应用程序约为130个新的,独特的许可类型。我们的平均负载率为每秒约15个请求。我们的指标正在快速增长。
我们正在努力用本机Authz调用替换遗留权限代理。本机Authz调用将比代理程序平均更快地检查数量级。第95百分位数速度超过两个数量级。
Authz使团队更容易管理权限。开发人员可以建立新产品,而无需构建潜在的授权基础架构。
在建立新的东西时 - 成为内部服务或外部产品 - 不要害怕实验。在我们建立了我们的消费者想要使用的东西之前,我们的团队有几个故障。最终,早期发布是我们的关键。
我挑战你在自己的工作中使用这个过程。贵公司是否有破碎的系统?你能推出一个简单的实验,帮助建立有用的东西吗?
我们将在未来文章中涵盖Authz的架构。如果您对Authz或我们的开发过程有疑问,请留下评论。如果您想帮助我们建立下一个版本的Carta,我们正在招聘。