Cerbos 开源用户授权解决方案
前言⌗
最近因为业务中需要用到权限管理和用户授权,我调研了多个开源解决方案,以及自建权限管理的方案,最终找到了这款开源的用户授权解决方案 Cerbos。
Cerbos 基于 Go 语言开发,支持 RESTful 和 gRPC 方式调用,并且提供了常见语言的 SDK,但这不是选择它的主要原因,因为在其他的开源项目中也都有一定的支持。最终让我在一众开源解决方案中选择它的原因是:
- 无状态的授权即服务
- 支持多种后端存储(用于存储授权策略)
- 简单方便的部署和用户友好的文档
对比⌗
调研的过程中我翻阅了很多开源项目的文档及其源码,其中包括:
- Oso: https://www.osohq.com
- OPA: https://www.openpolicyagent.org
- OPAL: https://www.opal.ac
- Ladon: https://github.com/ory/ladon
- Permify: https://permify.co
- OpenFGA: https://openfga.dev
- Authzed: https://authzed.com
- Pomerium: https://www.pomerium.com
- Shield: https://raystack.github.io/shield
- Speedle: https://speedle.io
- Warrant: https://warrant.dev
- Cerbos: https://cerbos.dev
Project | Language | Stateless | Library | Service | Dynamic Policy |
---|---|---|---|---|---|
Oso | Rust | Yes | Yes | No | No |
OPA | Go | Yes | Yes | Yes | No |
OPAL | Python | Yes | No | Yes | Yes |
Ladon | Go | Yes | Yes | No | Yes |
Permify | Go | No | No | Yes | Yes |
OpenFGA | Go | No | No | Yes | Yes |
Authzed | Go | No | No | Yes | Yes |
Pomerium | Go | No | No | Yes | Yes |
Shield | Go | No | No | Yes | Yes |
Speedle | Go | No | No | Yes | Yes |
Warrant | Go | NO | No | Yes | Yes |
Cerbos | Go | Yes | No | Yes | Yes |
为什么我比较看中无状态呢?因为这种方式对于开发者而言最为简单,无需在其他数据库中维护用户和资源的关系或者用户和角色或权限的关系。而支持无状态的也就只有 Oso、OPA、OPAL、Ladon 和 Cerbos,而 Oso 则是只能作为库来使用,并且不支持 PHP 等语言;
OPA 则可以作为 Go 项目中的库来使用,也可以作为单独的服务来使用,但是问题就是不支持动态更新策略,但是 OPA 的策略编写语法最为强大和灵活!
OPAL 则是在 OPA 之上又实用 Python 进行了一层封装,通过 Python 开发的服务,来实现动态更新 Policy 文件。
最后就是 Cerbos 了,它支持本地存储、Git、SQLite 和 MySQL 等常见数据库,只是用于存储策略和角色定义!并且策略的定义支持 YAML 格式或 JSON 格式,这对于想通过 Admin API 来管理策略和 Role 就方便很多了!
配置⌗
---
server:
cors:
allowedOrigins:
- example.com
allowedHeaders:
- X-RequestID
adminAPI:
enabled: true
adminCredentials:
username: {ADMIN_USER}
passwordHash: {BASE64_ENCODE_HASH_PASS}
httpListenAddr: ":3592"
grpcListenAddr: ":3593"
metricsEnabled: true
logRequestPayloads: true
storage:
driver: disk
git:
protocol: ssh
url: [email protected]:services/policies.git
subDir: policies
branch: master
checkoutDir: /etc/cerbos/policies
updatePollInterval: 60s
ssh:
user: git
privateKeyFile: ${HOME}/.ssh/id_rsa
sqlite3:
dsn: ":memory:"
disk:
directory: /policies
watchForChanges: true
telemetry:
disabled: true
engine:
defaultPolicyVersion: "default"
auxData:
jwt:
keySets:
- id: zitadel
remote:
url: https://passport.example.com/.well-known/openid-configuration
schema:
enforcement: reject
部署⌗
docker-compose.yaml
配置如下:
services:
cerbos:
image: ghcr.io/cerbos/cerbos:latest
restart: always
container_name: cerbos
ports:
- 0.0.0.0:3592:3592
- 0.0.0.0:3593:3593
command: ["server", "--config=/config/config.yaml"]
volumes:
- ./cerbos/config:/config
- ./cerbos/policies:/policies
networks:
services:
name: services
external: true
编写策略⌗
policies
目录的结构如下:
.
├── _schemas
│ └── customer.json
└── crm
├── principals
└── resources
└── customer
├── customer.yaml
└── derivedRoles.yaml
policies/_schemas/customer.json
内容如下:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"trackerID": {
"type": "string"
}
},
"required": [
"trackerID"
]
}
注意:_schemas 必须在
policies
的根目录下!!!
policies/crm/resources/customer/customer.yaml
配置文件内容如下:
---
apiVersion: 'api.cerbos.dev/v1'
resourcePolicy:
version: 'default'
resource: 'crm:customer'
importDerivedRoles:
- customer_owner_role
rules:
- actions:
- '*'
roles:
- admin
effect: EFFECT_ALLOW
- actions:
- view
- create
roles:
- user
- sales
effect: EFFECT_ALLOW
- actions:
- update
- delete
- transfer
- tracking
effect: EFFECT_ALLOW
roles:
- owner
condition:
match:
any:
of:
- expr: request.resource.attr.trackerID == ""
- expr: request.resource.attr.trackerID == request.principal.id
schemas:
resourceSchema:
ref: "cerbos:///customer.json"
policies/crm/resources/customer/derivedRoles.yaml
内容如下:
---
apiVersion: "api.cerbos.dev/v1"
description: "Common dynamic roles used within the CRM"
variables:
flagged_resource: request.resource.attr.flagged
derivedRoles:
name: customer_owner_role
definitions:
- name: owner
parentRoles: ["user"]
condition:
match:
expr: request.resource.attr.trackerID == request.principal.id
上面的策略是对于 CRM 中的客户资源进行限制:
ADMIN
可以执行所有 Action,- 普通的
user
和sales
则可以查看和创建客户信息 owner
则可以对客户信息进行update
、delete
、transfer
和tracking
,但是必须满足用户没有 trackerID 或 trackerID 是自己derivedRoles.yaml
中的配置则允许衍生角色,当客户的 trackerID 等于自己的 ID 时,则对于当前客户资源来说,该用户则是owner
权限
到这里,用户授权模型就搭建完成了。后面会尝试在其他项目中实现接入用户授权逻辑!
I hope this is helpful, Happy hacking…