分析 iam 使用 opa 做权限检查
以 ai-arts 为例,其它模块应该是类似的。
1. ai-arts 注册 endpoints
func (p *IamService) RegisterEndpoints(req *dto.RegisterEndpointsReq, header http.Header)
注意,请求体中,有:
type IamPoliciesType struct {
Module string `json:"module"`
//SystemAdmin []IamEndPointAction `json:"systemAdmin"`
OrgAdmin []IamEndPointAction `json:"orgAdmin"`
Developer []IamEndPointAction `json:"developer"`
Public []IamEndPointAction `json:"public"`
}
note: ai-arts 并不是每一个路由都注册一下,而是收集起来,最后才调用一次。
2. iam 维护策略
endpoints := v1.Group("/endpoints")
endpoints.POST("", EndpointsAndPoliciesCreate)
会写入表中: iam.policies 。可能可以多注意下 account 和 statements 字段。
触发 opa 刷新:
Refresh(*dtoData.TenantId, false, ctx)
(同步或者异步地)走到:
func updateOpa(ctx context.Context, request *policyRefresherMessageRefresh) {
...
pm, err := getPolicyMap(ctx, request.tenantId)
if err != nil {
return
}
...
opa.UpdateStore(ctx, request.tenantId, pm)
...
}
getPolicyMap 准备了数据给到 opa, 所有也值得注意(可以看出准备了啥数据)。这个数据会给到 UpdateStore 里面的 pm:
err = opaData.store.Write(ctx, opaData.txn, storage.AddOp, storage.MustParsePath("/policies"), pm)
rego.go 的 opa 规则中,会 import 这个 policies.
3. iam 实施权限检查
func Authz(tenantId int64, action, subject string)
...
ctx := context.Background()
r := opaData.partialResult.Rego(
rego.Input(input),
)
rs, err := r.Eval(ctx)
if err != nil {
logging.Error(err).Int64("tenant", tenantId).Str("action", action).
Str("subject", subject).Msgf("opa authz failed")
return
}
result, err = getResultProjects(rs)
...
3.1 输入端
用户这边,提供了哪些数据:
input["action"] = action
input["subject"] = constants.UserGroupSeparator + subject + constants.UserGroupSeparator
input["resource"] = "*"
input["projects"] = []string{"~~ALL-PROJECTS~~"}
这里的 subject ,可以看出,是 user group id.
3.2 规则端
一方面,前面提到, pm 提供了 policies 数据,这其实是一个字典, key 为 policyId, value 是一个处理后的值。如果我们去看func getPolicyMap(ctx context.Context, tenantId int64)
,就会发现, value 是一个多层字典,大概是这样的:
{
"members": ";;1;;2;;",
"statements": [
{
"id": 1,
"role": "",
"actions": ["a:b:c"],
"resources": "*",
"effect": "allow",
"projects": "*",
}
]
}
另一方面, rego.go 中其实有许多规则,这里的 data.authz.hasProject ,似乎可以让我们知道,应该从哪个规则看起。
opaData.r = rego.New(
rego.Query("data.authz.hasProject"),
rego.Module("example.rego", OPA_MODULE),
rego.Store(opaData.store),
rego.Transaction(opaData.txn),
)
以 hasProject 为起点去看:
hasProject = true {
count(authorized_project) > 0
}
authorized_project[statement_id] {
allowed_project[statement_id]
# not denied_project[project]
}
allowed_project[statement_id] {
match[["allow", pol_id, statement_id]]
}
match[[effect, pol_id, statement_id]] {
effect := policies[pol_id].statements[statement_id].effect
has_member[pol_id]
# has_resource[[pol_id, statement_id]]
has_action[[pol_id, statement_id]]
}
has_member[pol_id] {
pol_sub := policies[pol_id].members
contains(pol_sub, input.subject)
}
has_action[[pol_id, statement_id]] {
statement_action := policies[pol_id].statements[statement_id].actions[_]
action_matches(input.action, statement_action)
}
action_matches(in, stored) {
# no_wildcard(stored)
in == stored
}
结合 输入 和 规则,就可以作权限检查了。