open-policy-agent - 根据 OPA 策略过滤数据集
问题描述
我有一个由两层组成的访问控制模型:Access Levels和Shared Permissions。
用户的访问级别控制您在系统中可以拥有的最大权限集,它还授予您在系统中创建顶级对象(组合、程序、项目等)的一些基本权限。系统中的其他人也可以与您共享系统中的对象(从而授予您一个或多个专门针对某个对象的权限)。如果一个对象不是由您创建或没有分配给您,那么您应该能够看到它,除非它明确与您共享。示例数据集如下所示:
"access_levels": {
"Worker": ["projects.view"],
"Planner": ["projects.create", "projects.edit", "projects.view"]
},
"users_access_level": {
"bob.jones@example.com": "Planner",
"joe.humphreys@example.com": "Worker"
},
"resource_hierarchy": {
"customer1": ["customer1"],
"project1": ["project1", "customer1"],
"project2": ["project2", "customer1"]
},
"resource_policyIDs": {
"customer1": "1",
"project1": "2",
"project2": "3",
},
"policies": {
"1": {
"permissions": ["projects.create"],
"subjects": ["users:joe.humphreys@example.com"]
},
"2": {
"permissions": ["projects.view"],
"subjects": ["users:joe.humphreys@example.com"]
},
"3": {}
}
政策看起来像这样:
package authz
default authorized = false
authorized {
input.method == "POST"
http_path = ["programs", "create"]
input.customerId == token.payload.customerId
permitted_actions(token.payload.sub, input.customerId)[_] == "programs.create"
}
subjects_resource_permissions(sub, resource) = { perm |
resource_ancestors := data.resource_hierarchy[resource]
ancestor := resource_ancestors[_]
id := data.resource_policyIDs[ancestor]
data.policies[id]["subjects"][_] == sprintf("users:%v", [sub])
perm := data.policies[id]["permissions"][_]
}
permitted_actions(sub, resource) = x {
resource_permissions := subjects_resource_permissions(sub, resource)
access_permissions := data.access_levels[data.users_access_level[sub]]
perms := {p | p := access_permissions}
x = perms & resource_permissions
}
http_path := split(trim_left(input.path, "/"), "/")
假设我创建了一个 Projects API 来管理项目资源。API 有一个列出项目的方法,该方法应该只返回用户有权查看的项目。因此,在上面的示例中,用户 'joe.humphreys@example.com' 不应该能够查看项目 2,即使他的访问级别为他提供了 'projects.view'。这是因为它没有与他共享。
如果我想使用 OPA,我如何提供一个通用模式来跨多个 API 完成此任务?对于 OPA 来说,要完成这样的事情,查询会是什么样子?换句话说,如果我想在 SQL 中强制执行此授权,那会是什么样子?
我读过这篇文章,但我很难看出它是如何适合这里的。
解决方案
我假设你的两层模型和'访问级别'和'共享权限'。例如,“joe”可以看到“project1”,因为“joe”是一名 Worker,因此他具有“projects.view”权限,并且“joe”被分配给“project1”(通过策略“2”),权限为“projects.view” . 由于“joe”没有通过任何具有“projects.view”权限的策略分配给“project2”,“joe”无法看到“project2”。即,即使通过某些策略将“joe”分配给“project2”,该策略也必须指定“projects.view”权限,否则“joe”无法看到它。
您可以编写类似这样的内容来生成允许主题查看的项目资源集:
authorized_project[r] {
# for some projects resource 'r', if...
r := data.projects[_]
# subject has 'projects.view' access, and...
level := data.user_access_levels[input.sub]
"projects.view" == data.access_level_permissions[level][_]
# subject assigned to project resource (or any parents)
x := data.resource_hierarchy[r.id][_]
p := data.resource_policies[x]
"projects.view" == data.policies[p].permissions[_]
input.sub == data.policies[p].subjects[_]
}
这引出了如何填充data.projects
、填充data.policies
和data.resource_hierarchy
填充的问题(我假设访问级别数据集要小得多,但这些数据集也可能存在相同的问题。)博客文章(您链接的)讨论了该问题的答案。请注意,通过input
而不是传递数据data
并没有真正改变任何东西——它仍然需要在每个查询中都可用。
您可以重构上面的示例并使其更具可读性:
authorized_project[r] {
r := data.projects[_]
subject_access_level[[input.sub, "projects.view"]]
subject_shared_permission[[input.sub, "projects.view", r.id]]
}
subject_access_level[[sub, perm]] {
some sub
level := data.user_access_levels[sub]
perm := data.access_level_permissions[level][_]
}
subject_shared_permission[[sub, perm, resource]] {
some resource
x := data.resource_hierarchy[resource][_]
p := data.resource_policies[x]
perm := data.policies[p].permissions[_]
sub := data.policies[p].subjects[_]
}
您可以将上述内容概括如下:
authorized_resource[[r, kind]] {
r := data.resources[kind][_]
perm := sprintf("%v.view", [kind])
subject_access_level[[input.sub, perm]]
subject_shared_permission[[input.sub, perm, r.id]]
}
subject_access_level[[sub, perm]] {
some sub
level := data.user_access_levels[sub]
perm := data.access_level_permissions[level][_]
}
subject_shared_permission[[sub, perm, resource]] {
some resource
x := data.resource_hierarchy[resource][_]
p := data.resource_policies[x]
perm := data.policies[p].permissions[_]
sub := data.policies[p].subjects[_]
}
推荐阅读
- c# - 实体框架 - MySQL - 未知异常
- android - 我正在使用 bundle 获取变量,但它没有更新
- openwhisk - Openwhisk 作曲家:如何为 Redis 实例设置参数以使用并行合成
- javascript - 当我点击复选框价格时,将显示这些价格的产品
- c# - C# - 如何将变量重置为其第一个状态?
- python - 如何在 Python 中保存 JSON 实例的压缩文件
- ios - Can I make a UIMenu in Xamarin.iOS?
- java - 如何为每个方法扩展类对象的行为?
- lektor - 如何在 lektor 中实现搜索和过滤
- reactjs - 当使用无效参数调用函数时,Jest 不会失败测试