首页 > 解决方案 > 根据 OPA 策略过滤数据集

问题描述

我有一个由两层组成的访问控制模型:Access LevelsShared 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 中强制执行此授权,那会是什么样子?

我读过这篇文章,但我很难看出它是如何适合这里的。

标签: open-policy-agent

解决方案


我假设你的两层模型和'访问级别'和'共享权限'。例如,“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.policiesdata.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[_]
}

推荐阅读