在现代可扩展应用中,授权逻辑至关重要。随着授权模型变得越来越复杂,手动检查每一个可能的权限变更会迅速成为瓶颈,从而导致暴露数据或阻止合法访问的错误。想象一下像GitHub这样的例子:随着模型的扩展,为每次权限变更测试所有场景会变得难以管理。
为了在应用程序及其权限演进过程中保持持续的正确性,您需要一个健壮的、自动化的CI/CD测试策略。本文将指导您为OpenFGA模型定义测试,并使用GitHub Actions工作流自动化其执行,确保每次代码变更在到达生产环境之前都是安全的。
理解核心组件
在自动化流程之前,让我们简要回顾测试OpenFGA模型所需的组件。
OpenFGA
OpenFGA集中处理授权决策。它依赖于三个核心组件:
- 授权模型:在OpenFGA DSL中定义,这是您权限的架构(例如,用户与文档具有管理员关系)。
- 关系元组:实际数据(例如,user:alice是document:report的管理员)。
- 授权检查:查询特定关系是否为真的问题(例如,user:alice能否查看document:report?)。
OpenFGA模型使用一个标准化的YAML格式进行测试,该文件与.fga或.fga.yaml模型文件并存。这个文件通过包含以下内容来定义一个场景:
- Model:正在测试的FGA DSL模型。
- Tuples:设置场景的初始关系数据。
- Tests:带有预期结果(true或false)的权限检查列表。
这种格式允许您为模型逻辑编写独立的测试,完全独立于应用程序的代码。
设计授权模型
让我们使用一个众所周知的示例,考虑一个类似Git的仓库管理应用,权限定义如下:
- 用户可以是仓库的管理员、维护者、写入者、分拣者或读取者(每个级别继承低于它的级别的所有访问权限,例如,管理员继承维护者的访问权限,依此类推)。
- 团队可以有成员。
- 组织可以有成员。
- 组织可以拥有仓库。
- 用户可以在组织上拥有仓库管理员访问权限,从而拥有该组织所有仓库的管理员访问权限。
这样的授权模型可以在store.fga.yaml文件中定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
model
schema 1.1
type user
type team
relations
define member: [user, team#member]
type repo
relations
define admin: [user, team#member] or repo_admin from owner
define maintainer: [user, team#member] or admin
define owner: [organization]
define reader: [user, team#member] or triager or repo_reader from owner
define triager: [user, team#member] or writer
define writer: [user, team#member] or maintainer or repo_writer from owner
type organization
relations
define member: [user] or owner
define owner: [user]
define repo_admin: [user, organization#member]
define repo_reader: [user, organization#member]
define repo_writer: [user, organization#member]
|
定义用于测试的元组
接下来,您将定义一些元组,作为测试模型的数据。创建这些元组时,尝试代表模型中呈现的所有关系和边缘情况非常重要。请考虑以下标准:
- 关注边缘情况:定义测试模型逻辑极限的元组。创建使用以下关系类型的元组:
- 直接访问:授予最基本、显式访问权限的元组(例如,user:alice是organization:acme的拥有者)。
- 不存在的访问:用户/对象对不存在任何关系,确保模型正确拒绝权限(例如,检查非成员是否可以查看组织)。
- 隐式/继承访问:触发计算关系的元组,例如作为组织成员的用户,而该组织是文档的管理员(例如,user:bob是team:devs的成员,team:devs#member是repo:x的管理员)。
- 覆盖所有关系类型(角色、权限和用户集):确保模型中DSL定义的每个不同关系至少有一个元组。这包括:
- 角色分配(如果适用):例如,user:carla在object:id上拥有role:name。
- 对象层次结构:例如,document:child是document:parent的父级(建立继承关系)。
- 条件元组:如果使用FGA条件,请创建条件明确为True和明确为False的元组。
- 验证递归和交集:创建专门测试涉及多跳或用户集交集的路径的元组:
- 对于层次结构(from子句):建立一个三级层次结构(例如,祖父、父和子对象),并测试在祖父对象上具有权限的用户是否正确继承了对子对象的访问权限。
- 对于交集(and):创建一个满足两个必需关系的用户和另一个只满足一个关系的用户,以确保and逻辑严格。
- 模拟真实场景:为常见的、高价值的业务用例设计元组集,用作回归测试。例如:
- 正确配置user:admin的元组集,并验证他们可以执行所有管理操作(创建、删除、邀请)。
- 为外部用户user:guest分配对单个特定文档的只读访问权限,而无需使其成为父组织成员的元组。
最好在端到端测试中完成此操作。
考虑到以上几点,让我们更新store.fga.yaml文件,使其具有以下结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
model:
model
schema 1.1
type user
type team
relations
define member: [user, team#member]
type repo
relations
define admin: [user, team#member] or repo_admin from owner
define maintainer: [user, team#member] or admin
define owner: [organization]
define reader: [user, team#member] or triager or repo_reader from owner
define triager: [user, team#member] or writer
define writer: [user, team#member] or maintainer or repo_writer from owner
type organization
relations
define member: [user] or owner
define owner: [user]
define repo_admin: [user, organization#member]
define repo_reader: [user, organization#member]
define repo_writer: [user, organization#member]
tuples:
# OpenFGA organization is the owner of the openfga/openfga repository
- user: organization:openfga
relation: owner
object: repo:openfga/openfga
# Members of the OpenFGA organization have a repository admin base permission on the organization
- user: organization:openfga#member
relation: repo_admin
object: organization:openfga
# Erik is a member of the OpenFGA organization
- user: user:erik
relation: member
object: organization:openfga
# The openfga/core team members are admins on the openfga/openfga repository
- user: team:openfga/core#member
relation: admin
object: repo:openfga/openfga
# Anne is a reader on the openfga/openfga repository
- user: user:anne
relation: reader
object: repo:openfga/openfga
# Beth is a writer on the openfga/openfga repository
- user: user:beth
relation: writer
object: repo:openfga/openfga
# Charles is a member of the openfga/core team
- user: user:charles
relation: member
object: team:openfga/core
# Members of the openfga/backend team are members of the openfga/core team
- user: team:openfga/backend#member
relation: member
object: team:openfga/core
# Diane is a member of the openfga/backend team
- user: user:diane
relation: member
object: team:openfga/backend
|
创建测试
定义测试是最重要的步骤之一,因此在创建测试之前,让我们看看一些需要记住的事项:
- 测试完整性:对于每个定义的权限,创建相应的断言,验证允许和拒绝的场景。
- 使用边界情况验证复杂规则:专门针对涉及用户集重写、使用
from关键字或逻辑运算符的逻辑创建测试用例。例如,测试层次结构时,确保用户跨多个级别继承权限。测试and规则时,创建一个满足所有条件的测试和另一个只满足一个条件的测试。
- 使测试原子化和上下文特定化:对于任何需要复杂或独特关系安排的测试,请使用该单独测试块内的专用元组数组(例如本例)。这种做法确保测试完全自包含,易于阅读,并且其结果独立于测试文件中的其他不相关数据,从而简化维护和调试。
考虑到这一点,让我们定义以下测试并将其添加到store.fga.yaml文件的末尾:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
tests:
- name: Test individual user permissions on the openfga/openfga repo
check:
- user: user:anne
object: repo:openfga/openfga
assertions:
reader: true
triager: false
- user: user:beth
object: repo:openfga/openfga
assertions:
admin: false
- user: user:charles
object: repo:openfga/openfga
assertions:
writer: true
- user: user:diane
object: repo:openfga/openfga
assertions:
admin: true
- user: user:erik
object: repo:openfga/openfga
assertions:
reader: true
- name: Test who are readers of the openfga/openfga repo
list_users:
- object: repo:openfga/openfga
user_filter:
- type: user
assertions:
reader:
users:
- user:diane
- user:charles
- user:beth
- user:anne
- user:erik
- name: Test which repos can Diane read
list_objects:
- user: user:diane
type: repo
assertions:
reader:
- repo:openfga/openfga
- name: Check if the right users have access to the right repositories
list_users:
- object: repo:openfga/openfga
user_filter:
- type: user
assertions:
writer:
users:
- user:charles
- user:beth
- user:diane
- user:erik
- object: repo:openfga/openfga
user_filter:
- type: team
relation: member
assertions:
writer:
users:
- team:openfga/backend#member
- team:openfga/core#member
|
使用FGA CLI本地测试模型
在与GitHub Actions集成之前,开发人员应该能够在本地运行这些测试。您可以在OpenFGA文档页面上找到FGA CLI的安装说明。
安装后,您可以执行以下命令:
1
|
$ fga model test --test store.fga.yaml
|
这将产生以下输出:
1
2
3
4
5
|
# Test Summary #
Tests 4/4 passing
Checks 6/6 passing
ListObjects 1/1 passing
ListUsers 3/3 passing
|
使用GitHub Actions自动化FGA模型测试
目标是在模型或测试文件更新时运行测试,确保我们防止在CI/CD管道内部署损坏的授权模型。
OpenFGA团队为此目的创建了一个GitHub Action:action-openfga-test。
要了解更多关于GitHub Actions工作流的信息,请参阅文档。
在项目的根文件夹中,创建一个新文件.github/workflows/fga-tests.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
name: Test FGA Model
run-name: ${{ github.actor }} is testing the FGA model
on:
push:
paths:
- fga/**
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Test FGA Model
uses: openfga/action-openfga-test@v0.1.1
with:
test_path: fga/store.fga.yaml
|
这个GitHub Action将在每次涉及store.fga.yaml文件的推送时执行。它将在Ubuntu上运行,并执行两个步骤:
- 使用GitHub的Checkout Action检出代码仓库。
- 使用OpenFGA Test Action运行测试。
当您向仓库推送更改时,在Actions选项卡下,您应该看到您的操作正在运行,如果成功,您应该看到与之前FGA CLI提供的完全相同的输出。
FGA与CI/CD:授权的持续验证
通过采用这种自动化测试策略,您将授权测试从繁琐的手动步骤转变为集成到CI/CD管道中的可靠、可重复的过程。FGA为您提供了定义复杂权限的声明性能力,而GitHub Actions使您能够在CI/CD过程中安全、快速、自信地部署它们。您的应用程序的授权逻辑现在可以持续验证,让您能够专注于构建功能,而不是调试访问控制。