【SaaS架构】多租户SaaS授权和API访问控制--实施PDP
视频号
微信公众号
知识星球
策略决策点(PDP)可以被表征为策略或规则引擎。此组件负责应用策略或规则,并返回是否允许特定访问的决定。PDP可以使用基于角色的访问控制(RBAC)和基于属性的访问控制模型(ABAC);然而,PDP是ABAC的要求。PDP允许将应用程序代码中的授权逻辑卸载到单独的系统。这可以简化应用程序代码。它还提供了一个易于使用的幂等接口,用于为API、微服务、前端后端(BFF)层或任何其他应用程序组件进行授权决策。
以下部分讨论了实现PDP的两种方法:开放策略代理(OPA)和自定义解决方案。然而,这并不是一个详尽的列表。
PDP实施方法:
- 使用OPA
- 使用自定义策略引擎
使用OPA
实现PDP的首选方法是使用开放策略代理(OPA)。OPA是一个开源的通用策略引擎。OPA有很多用例,但与PDP实现相关的用例是它将授权逻辑与应用程序分离的能力。这被称为政策脱钩。
由于几个原因,OPA在实现PDP时很有用。它使用名为Rego的高级声明性语言来起草策略和规则。这些策略和规则与应用程序分开存在,可以在没有任何特定于应用程序的逻辑的情况下呈现授权决策。OPA还公开了一个RESTful API,使检索授权决策简单明了。为了做出授权决策,应用程序使用JSON输入查询OPA,OPA根据指定的策略评估输入,以返回JSON形式的访问决策。OPA还能够导入可能与授权决策相关的外部数据。
OPA功能
OPA与定制策略引擎相比有以下几个优势:
- OPA及其与Rego的策略评估提供了一个灵活的预先构建的策略引擎,只需要插入策略和做出授权决策所需的任何数据。必须在自定义策略引擎解决方案中重新创建此策略评估逻辑。
- OPA通过使用声明性语言编写策略来简化授权逻辑。您可以独立于任何应用程序代码修改和管理这些策略和规则,而无需应用程序开发技能。
- OPA公开了一个RESTful API,它简化了与策略执行点(PEP)的集成。
- OPA为验证和解码JSON Web令牌(JWT)提供内置支持。
- OPA是公认的授权标准,这意味着如果您需要帮助或研究来解决特定问题,那么文档和示例将非常丰富。
- 采用OPA等授权标准,无论团队应用程序使用何种编程语言,都可以在团队之间共享用Rego编写的策略。
OPA不会自动提供两件事:
- OPA没有用于更新和管理策略的强大控制平面。OPA通过公开一个管理API,为实现策略更新、监控和日志聚合提供了一些基本模式,但必须由OPA用户处理与此API的集成。作为最佳实践,您应该使用连续集成和连续部署(CI/CD)管道来管理、修改和跟踪OPA中的策略版本和管理策略。
- 默认情况下,OPA无法从外部源检索数据。授权决策的外部数据源可以是保存用户属性的数据库。在如何向OPA提供外部数据方面有一定的灵活性,当请求授权决策时,可以预先在本地缓存外部数据或从API动态检索外部数据,但OPA无法代表您获取这些信息。
在本节中:
- Rego概述
- 示例1:带OPA和Rego的基本ABAC
- 示例2:具有OPA和Rego的多租户访问控制和用户定义RBAC
- 示例3:使用OPA和Rego对RBAC和ABAC进行多租户访问控制
- 示例4:使用OPA和Rego进行UI过滤
Rego概述
Rego是一种通用策略语言,这意味着它适用于堆栈的任何层和任何域。Rego的主要目的是接受JSON/YAML输入和数据,这些输入和数据经过评估,以做出有关基础设施资源、身份和操作的策略性决策。Rego使您能够编写关于堆栈或域的任何层的策略,而无需更改或扩展语言。以下是Rego可以做出的一些决策示例:
- 是否允许或拒绝此API请求?
- 此应用程序的备份服务器的主机名是什么?
- 此拟议基础设施变更的风险评分是多少?
- 为了实现高可用性,应将此容器部署到哪些集群?
- 该微服务应使用哪些路由信息?
为了回答这些问题,Rego采用了关于如何做出这些决定的基本哲学。在雷戈起草政策时的两个关键原则是:
- 每个资源、标识或操作都可以表示为JSON或YAML数据。
- 策略是应用于数据的逻辑。
Rego通过定义如何评估JSON/YAML数据输入的逻辑,帮助软件系统做出授权决策。C、Java、Go和Python等编程语言是解决这个问题的常用方法,但Rego的设计重点是表示系统的数据和输入,以及使用这些信息进行策略决策的逻辑。
示例1:带OPA和Rego的基本ABAC
本节描述了一个场景,其中OPA用于决定哪些用户可以访问虚构的Payroll微服务中的信息。提供了Rego代码片段,以演示如何使用Rego来呈现访问控制决策。这些示例既不是详尽的,也不是对Rego和OPA功能的全面探索。为了更全面地了解Rego,我们建议您查阅OPA网站上的Rego文档。
基本OPA规则示例
在上图中,OPA为Payroll微服务实施的访问控制规则之一是:
员工可以读取自己的工资。
如果Bob试图访问Payroll微服务以查看自己的工资,那么Payroll微型服务可以将API调用重定向到OPA RESTful API以做出访问决定。Payroll服务使用以下JSON输入向OPA查询决策:
{
"user": "bob",
"method": "GET",
"path": ["getSalary", "bob"]
}
OPA根据查询选择一个或多个策略。在本例中,以下策略(用Rego编写)评估JSON输入。
default allow = false
allow = true {
input.method == "GET"
input.path = ["getSalary", user]
input.user == user
}
默认情况下,此策略拒绝访问。然后,它通过将查询中的输入绑定到全局变量输入来计算该输入。点运算符与此变量一起使用以访问变量的值。如果规则中的表达式也是true,则Rego规则allow返回true。Rego规则验证输入中的方法是否等于GET。然后,在将列表中的第二个元素分配给变量user之前,它验证列表路径中的第一个元素是getSalary。最后,它通过检查发出请求的用户input.user是否与用户变量匹配来检查访问的路径是否为/getSalary/bob。如果逻辑返回布尔值,则应用规则allow,如输出所示:
{ "allow": true }
使用外部数据的部分规则
要演示其他OPA功能,您可以向正在实施的访问规则添加要求。让我们假设您希望在前面的示例中强制执行此访问控制要求:
员工可以阅读任何向他们汇报的人的工资。
在此示例中,OPA可以访问外部数据,这些数据可以被导入以帮助做出访问决策:
"managers": {
"bob": ["dave", "john"],
"carol": ["alice"]
}
您可以通过在OPA中创建部分规则来生成任意的JSON响应,该规则返回一组值而不是固定的响应。这是部分规则的示例:
direct_report[user_ids] {
user_ids = data.managers[input.user][_]
}
该规则返回一组报告input.user值的所有用户,在本例中,该值为bob。规则中的[_]构造用于迭代集合的值。这是规则的输出:
{ "direct_report": [ "dave", "john" ] }
检索此信息可以帮助确定用户是否是经理的直接下属。对于某些应用程序,返回动态JSON比返回简单的布尔响应更好。
把这一切放在一起
最后一个访问要求比前两个更复杂,因为它结合了两个要求中指定的条件:
员工可以阅读自己的工资和任何向他们汇报的人的工资。
要满足此要求,您可以使用此Rego策略:
default allow = false
allow = true {
input.method == "GET"
input.path = ["getSalary", user]
input.user == user
}
allow = true {
input.method == "GET"
input.path = ["getSalary", user]
managers := data.managers[input.user][_]
contains(managers, user)
}
策略中的第一条规则允许任何试图查看自己工资信息的用户访问,如前所述。在Rego中有两个同名的规则allow作为逻辑或运算符。第二条规则检索与input.user关联的所有直接报告的列表(从上一个图表中的数据),并将此列表分配给manager变量。最后,该规则通过验证经理变量中是否包含用户的姓名来检查试图查看其工资的用户是否是input.user的直接报告。
本节中的示例是非常基本的,并没有对Rego和OPA的功能进行完整或彻底的探索。有关更多信息,请查看OPA文档,查看OPA GitHub README文件,并在Rego游乐场进行实验。
示例2:具有OPA和Rego的多租户访问控制和用户定义RBAC
本示例使用OPA和Rego演示如何在具有租户用户定义的自定义角色的多租户应用程序的API上实现访问控制。它还演示了如何根据租户限制访问。该模型显示了OPA如何根据高级角色提供的信息做出精细的权限决策。
具有自定义角色的多租户访问的OPA用例
租户的角色存储在外部数据(RBAC数据)中,用于为OPA做出访问决策:
{
"roles": {
"tenant_a": {
"all_access_role": ["viewData", "updateData"]
},
"tenant_b": {
"update_data_role": ["updateData"],
"view_data_role": ["viewData"]
}
}
}
当由租户用户定义这些角色时,这些角色应存储在外部数据源或身份提供程序(IdP)中,当将租户定义的角色映射到权限和租户自身时,该身份提供程序可以充当真实的来源。
本示例使用OPA中的两个策略来做出授权决策,并检查这些策略如何强制租户隔离。这些策略使用前面定义的RBAC数据。
default allowViewData = false
allowViewData = true {
input.method == "GET"
input.path = ["viewData", tenant_id]
input.tenant_id == tenant_id
role_permissions := data.roles[input.tenant_id][input.role][_]
contains(role_permissions, "viewData")
}
要显示此规则的功能,请考虑具有以下输入的OPA查询:
{
"tenant_id": "tenant_a",
"role": "all_access_role",
"path": ["viewData", "tenant_a"],
"method": "GET"
}
通过组合RBAC数据、OPA策略和OPA查询输入,该API调用的授权决策如下:
- 租户A的用户对/viewData/Tenant_A进行API调用。
- Data微服务接收调用并查询allowViewData规则,传递OPA查询输入示例中显示的输入。
- OPA使用OPA策略中的查询规则来评估提供的输入。OPA还使用RBAC数据中的数据来评估输入。OPA执行以下操作:
- 验证用于进行API调用的方法是否为GET。
- 验证请求的路径是否为viewData。
- 检查路径中的tenant_id是否等于与用户关联的input.tenant_id。这确保了租户隔离。另一个租户,即使具有相同的角色,也无法被授权进行此API调用。
- 从角色的外部数据中提取角色权限列表,并将其分配给变量role_permissions。通过使用与input.role中的用户关联的租户定义的角色来检索此列表。
- 检查role_permissions以查看它是否包含权限viewData。
- OPA向Data微服务返回以下决定:
{ "allowViewData": true }
该过程显示了RBAC和租户意识如何帮助OPA做出授权决策。为了进一步说明这一点,请考虑使用以下查询输入对/viewData/tenant_b的API调用:
{
"tenant_id": "tenant_b",
"role": "view_data_role",
"path": ["viewData", "tenant_b"],
"method": "GET"
}
该规则将返回与OPA查询输入相同的输出,尽管它是针对具有不同角色的不同租户的。这是因为此调用针对/tenant_b,并且RBAC数据中的view_data_role仍然具有与其关联的viewData权限。要对/updateData实施相同类型的访问控制,可以使用类似的OPA规则:
default allowUpdateData = false
allowUpdateData = true {
input.method == "POST"
input.path = ["updateData", tenant_id]
input.tenant_id == tenant_id
role_permissions := data.roles[input.tenant_id][input.role][_]
contains(role_permissions, "updateData")
}
此规则在功能上与allowViewData规则相同,但它验证不同的路径和输入方法。该规则仍然确保租户隔离,并检查租户定义的角色是否授予API调用方权限。要查看如何执行此操作,请检查/updateData/tenant_b的API调用的以下查询输入:
{
"tenant_id": "tenant_b",
"role": "view_data_role",
"path": ["updateData", "tenant_b"],
"method": "POST"
}
当使用allowUpdateData规则求值时,此查询输入将返回以下授权决策:
{ "allowUpdateData": false }
此呼叫将不被授权。尽管API调用方与正确的tenant_id关联,并使用经批准的方法调用API,但input.role是租户定义的view_data_role。view_data_role没有updateData权限;因此,对/updateData的调用是未授权的。对于具有update_data_role的tenant_b用户,此调用将成功。
示例3:使用OPA和Rego对RBAC和ABAC进行多租户访问控制
为了增强上一节中的RBAC示例,可以向用户添加属性。
具有用户属性的多租户访问的OPA用例
此示例包含与上一示例相同的角色,但添加了用户属性account_lockout_flag。这是一个特定于用户的属性,不与任何特定角色关联。您可以使用先前用于此示例的相同RBAC外部数据:
{
"roles": {
"tenant_a": {
"all_access_role": ["viewData", "updateData"]
},
"tenant_b": {
"update_data_role": ["updateData"],
"view_data_role": ["viewData"]
}
}
}
account_lockout_flag用户属性可以作为用户Bob的/viewData/tenant_a的OPA查询输入的一部分传递给数据服务:
{
"tenant_id": "tenant_a",
"role": "all_access_role",
"path": ["viewData", "tenant_a"],
"method": "GET",
"account_lockout_flag": "true"
}
为访问决策查询的规则与前面的示例类似,但包含一行用于检查account_lockout_flag属性:
default allowViewData = false
allowViewData = true {
input.method == "GET"
input.path = ["viewData", tenant_id]
input.tenant_id == tenant_id
role_permissions := data.roles[input.tenant_id][input.role][_]
contains(role_permissions, "viewData")
input.account_lockout_flag == "false"
}
此查询返回false的授权决定。这是因为account_lockout_flag属性对于Bob为true,并且Rego规则allowViewData拒绝访问,尽管Bob具有正确的角色和租户。
示例4:使用OPA和Rego进行UI过滤
OPA和Rego的灵活性支持过滤UI元素的能力。下面的示例演示了OPA部分规则如何做出授权决策,决定哪些元素应该显示在带有RBAC的UI中。此方法是使用OPA过滤UI元素的多种不同方法之一。
基于RBAC的OPA UI过滤
在本例中,单页web应用程序有四个按钮。假设您希望过滤Bob、Shirley和Alice的UI,以便他们只能看到与其角色对应的按钮。当UI收到来自用户的请求时,它查询OPA部分规则以确定哪些按钮应该显示在UI中。当Bob(使用角色查看器)向UI发出请求时,查询将以下内容作为输入传递给OPA:
{
"role": "viewer"
}
OPA使用为RBAC构建的外部数据来做出访问决策:
{
"roles": {
"viewer": ["viewUsersButton", "viewDataButton"],
"dataViewOnly": ["viewDataButton"],
"admin": ["viewUsersButton", "viewDataButton", "updateUsersButton", "updateDataButton"]
}
}
OPA部分规则使用外部数据和输入生成一组用户可以在UI上查看的按钮:
ui_buttons[buttons] {
buttons := data.roles[input.role][_]
}
在部分规则中,OPA使用作为查询一部分指定的input.role来确定应该显示哪些按钮。Bob具有角色查看器,外部数据指定查看器可以看到两个按钮:viewUsersButton和viewDataButton。因此,Bob(以及具有查看器角色的任何其他用户)的此规则输出如下:
{ "ui_buttons": [ "viewDataButton", "viewUsersButton" ] }
雪莉具有dataViewOnly角色,她的输出将包含一个按钮:viewDataButton。具有管理员角色的Alice的输出将包含所有按钮。当查询OPA的UI_buttons时,这些响应将返回到UI。UI可以使用此响应来相应地隐藏或显示按钮。
使用自定义策略引擎
实现PDP的另一种方法是创建自定义策略引擎。此策略引擎的目标是将授权逻辑与应用程序分离。自定义策略引擎负责做出授权决策,类似于OPA,以实现策略解耦。此解决方案与OPA的主要区别在于,编写和评估策略的逻辑是定制的。与引擎的任何交互都必须通过API或其他方法公开,以使授权决策能够到达应用程序。您可以用任何编程语言编写自定义策略引擎,也可以使用其他机制进行策略评估,例如公共表达式语言(CEL)。
- 52 次浏览