RBAC 测试多个策略

bp rbac-testing-multiple-policies

问题描述

Patrole 当前通过检查策略操作是否允许来测试 API 端点,依据 oslo.policy,然后执行使用 CONF.rbac.rbac_test_role 下指定的角色进行策略执行的 API 端点。然而,这种方法没有考虑到直接(在 API 端点实现内部)或间接(在不同的辅助函数和 API 端点之间)强制执行多个策略操作的 API 端点。因此,Patrole 中当前的 RBAC 测试方法并不总是提供完整的策略覆盖。就像对 oslo.policy 进行多次调用是由各种端点执行的,Patrole 也应该这样做。

例如,考虑一个强制执行 2 个策略操作 A 和 B 的 API,其中 A 是 admin_api,B 是 admin_or_owner。使用 rbac_test_role 作为 admin 角色的调用必然会通过,因为 admin 角色具有执行策略操作 A 和 B 的权限,并且也能够执行 API 端点。但是,对于非 admin 角色,测试将失败,rbac_rule_validation 装饰器评估策略操作 B。这是因为非 admin 角色(例如 Member 角色)具有执行策略操作 B(即 admin_or_owner)的权限,但具有执行 API 端点的权限,因为端点强制执行 admin_api 策略:这会导致引发 Forbidden 异常,并且测试失败。

提议的变更

建议的更改是修改 rbac_rule_validation 装饰器,使其能够接收策略操作列表,而不仅仅是一个策略操作。对于每个策略操作,将向 oslo.policy 进行调用,以确认测试角色是否允许执行该操作。从 oslo.policy 返回的每个结果都将进行逻辑与运算。例如,如果策略操作 A 评估为 True,而策略操作 B 评估为 False,则最终结果为 False:因此,用户不应能够成功执行 API 调用。因此,Patrole 可以推断角色是否允许调用强制执行多个策略的 API。

为了提供一个具体的例子,以下测试

@rbac_rule_validation.action(
    service="nova",
    rule="os_compute_api:os-lock-server:unlock:unlock_override")
def test_unlock_server_override(self):
    server = self.create_test_server(wait_until='ACTIVE')
    # In order to trigger the unlock:unlock_override policy instead
    # of the unlock policy, the server must be locked by a different
    # user than the one who is attempting to unlock it.
    self.os_admin.servers_client.lock_server(server['id'])
    self.addCleanup(self.servers_client.unlock_server, server['id'])

    self.rbac_utils.switch_role(self, toggle_rbac_role=True)
    self.servers_client.unlock_server(server['id'])

可以更改为

@rbac_rule_validation.action(
    service="nova",
    rules=["os_compute_api:os-lock-server:unlock",
           "os_compute_api:os-lock-server:unlock:unlock_override"])
def test_unlock_server_override(self):
    server = self.create_test_server(wait_until='ACTIVE')
    self.os_admin.servers_client.lock_server(server['id'])
    self.addCleanup(self.servers_client.unlock_server, server['id'])

    self.rbac_utils.switch_role(self, toggle_rbac_role=True)
    self.servers_client.unlock_server(server['id'])

根据 Nova 关于锁定服务器的文档,“unlock_override”策略“仅在检查 os_compute_api:os-lock-server:unlock 通过后执行”。通过此更改,Patrole 将基于测试角色是否能够执行传递给 rules所有策略来生成其“预期”结果;否则,如果测试角色无法执行至少一个策略,则预期结果将为 False。之后,将使用测试角色调用 API 操作,并将结果与预期结果进行比较。

如果预期结果和实际结果匹配,则测试将通过。否则,Patrole 可以生成详细的错误消息,说明传递给 rules 的哪些策略导致测试失败。例如,在上面的示例中,如果测试角色具有执行“os_compute_api:os-lock-server:unlock”的权限,但没有“os_compute_api:os-lock-server:unlock:unlock_override”的权限,那么 Patrole 将发出错误消息,说明“os_compute_api:os-lock-server:unlock:unlock_override”导致测试失败。这将帮助云部署者和开发人员确定测试失败的来源,并精确定位不一致的自定义策略配置。

替代方案

目前,没有其他可行的替代方案。由于各种显而易见的原因,反复调用每个 API 端点针对其强制执行的每个策略操作是不切实际的,也不可取的。

  1. 应尽量减少代码冗余,以使代码更易于阅读和维护。

  2. 这会在 Patrole 的网关中引入严重运行时问题。

安全影响

无。

通知影响

LOG 语句需要更新,以便在测试失败后向用户传达多个策略操作,尤其是在测试失败后。

如果 Patrole 测试测试多个策略,那么在测试失败后,对于用户来说,Patrole 记录哪些策略导致测试失败会很有用。可以通过迭代调用 oslo.policy 来针对传递给 rbac_rule_validation 装饰器的每个策略,并存储与角色不兼容的策略列表以及预期的测试结果来确定这一点。

其他最终用户影响

无。

性能影响

性能影响可以忽略不计。此更改只会导致测试运行时间略微变慢,因为将对 oslo.policy 进行多次调用,而不是每个 Patrole 测试仅调用一次。

其他部署者影响

无。

开发人员影响

建议的更改要求开发人员谨慎地选择包含在建议的 actions 参数中的策略操作。包含过多的策略操作不可维护,并且从开发角度来看很繁琐。例如,Cinder 强制执行 volume_extension:volume_host_attributevolume_extension:volume_mig_status_attribute,以及许多不同的策略操作,适用于许多 API 端点。将这些策略操作重复用于每个 Cinder RBAC 测试将是冗余且设计不良的。(如果可以证明这些策略操作适用于每个 Cinder API 端点,那么 Patrole 框架可以自动注入这些策略操作,并将其与显式指定在 actions 中的策略操作进行逻辑与运算。但是,这种方法超出了本规范的范围)。

建议开发人员明智地使用此增强功能。只有强制执行多个相对唯一策略操作的端点才应包含在 actions 列表中。例如,可以从 KeystoneNova 的自文档化代码策略定义中推断出唯一性。

实现

负责人

主要负责人
其他贡献者

工作项

  • 使用 actions 参数增强 rbac_rule_validation 装饰器,并弃用 rule 参数。

  • rbac_rule_validation 中编写一个辅助函数,以迭代调用 rbac_policy_parser.RbacPolicyParser.allowed,针对在 actions 中指定的每个策略操作,进行逻辑与运算,并将结果返回到 rbac_rule_validation 装饰器。

  • 重构测试以使用 actions 代替 rule

  • 编写新的单元测试来测试建议的增强功能。

  • 选择性地将多个策略操作添加到一些测试中。

  • 确认所有 API 测试都适用于建议的增强功能。

  • 更新文档。

依赖项

无。

文档影响

应更新 Patrole 文档,以传达新的参数以及本规范中描述的预期用途。

参考资料