基于 OPA 策略过滤数据集

Filtering Datasets Based on OPA Policies

我有一个由两层组成的访问控制模型:访问级别共享权限

用户的访问级别决定了您在系统中可以拥有的最大权限集,它还授予您一些在系统中创建顶级对象的基本权限(投资组合、计划、项目等)。系统中的对象也可以由系统中的其他人与您共享(从而授予您一个或多个专门针对某个对象的权限)。如果某个对象不是您创建的或尚未分配给您,那么您应该能够看到它,除非它已明确与您共享。示例数据集如下所示:

"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, "/"), "/")

假设我创建了一个项目 API 来管理项目资源。 API 有一个列出项目的方法,该方法应该只 return 用户有权查看的项目。因此,在上面的示例中,用户 'joe.humphreys@example.com' 不应能够查看项目 2,即使他的访问级别授予他 'projects.view'。这是因为没有分享给他。

如果我想使用 OPA,我如何提供一个通用模式来跨多个 API 实现此目的?查询对于 OPA 来说会是什么样子来完成这样的事情?换句话说,如果我想在 SQL 中执行此授权,那会是什么样子?

我读过 this artcile,但我很难理解它是否适​​合这里..

我假设您的两层模型是 'Access Level' 与 'Shared Permission' 的 AND。例如,"joe" 可以看到 "project1" 因为 "joe" 是一个 Worker 因此他有 "projects.view" 权限 AND "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.projectsdata.policiesdata.resource_hierarchy 如何填充的问题(我假设访问级别数据集要小得多,但也可能是与那些相同的问题。)博客 post(您链接的)讨论了该问题的答案。请注意,通过 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[_]
}