V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
newmiao
V2EX  ›  Go 编程语言

OPA-重新定义规则引擎-入门篇

  •  1
     
  •   newmiao · 2020-03-15 20:52:55 +08:00 · 3372 次点击
    这是一个创建于 1717 天前的主题,其中的信息可能已经有所发展或是发生改变。

    OPA,全称OpenPolicyAgent, 底层用Go实现,它灵活而强大的声明式语言全面支持通用策略定义。

    目前国内资料还比较少。

    个人因为工作接触比较多,打算陆续分享些教程介绍下。

    私以为规则引擎的技术选型完全可以多这个选择~~

    什么是 OPA

    具体看官方文档 OPA philosophy docs

    主要关键词是:

    • 轻量级的通用策略引擎
    • 可与服务共存
    • 集成方式可以是 sidecar、主机级守护进程或库引入

    opa

    文字图片还是不够生动,看看 OPA 作者怎么说:

    OPA: The Cloud Native Policy Engine - Torin Sandall, Styra

    备用国内 B 站地址

    优点

    • 强大的声明式策略

      • 上下文感知
      • 表达性强
      • 快速
      • 可移植
    • 输入和输出支持任意格式

    配合强大的声明式策略语言Rego,描述任意规则都不是问题

    • 全面支持规则和系统解耦

    如图

    除了集成做Auth外,还可以应用到k8s,terraform,docker,kafka,sql,linux等上做规则决策

    • 工具齐全
      • 有命令行,有交互式运行环境
      • 支持测试,性能分析(底层Go实现)
      • 有强大的交互式编辑器扩展vscode-opa
      • playground分享代码

    下面从一个 RBAC 鉴权例子来了解下OPA

    一个 RBAC 例子

    以下 json 配置了 role 能操作的资源和 user 的绑定关系

    // data.json
    {
        "roles": [
            {
                "operation": "read",
                "resource": "widgets",
                "name": "widget-reader"
            },
            {
                "operation": "write",
                "resource": "widgets",
                "name": "widget-writer"
            }
        ],
        "bindings": [
            {
                "user": "inspector-alice",
                "role": "widget-reader"
            },
            {
                "user": "maker-bob",
                "role": "widget-writer"
            }
        ]
    }
    

    当一个请求读取 widgets 的 user (如下 json )过来操作资源,怎么判定他是否可以呢?

    // input.json
    {
        "action":{
            "operation":"read",
            "resource":"widgets"
        },
        "subject":{
            "user":"inspector-alice"
        }
    }
    

    可能你习惯性在想用自己趁手的语言和框架,一顿遍历循环搞定。

    且慢,OPA告诉我们:

    几行代码就可以!(当然代码少不是重点。。。)

    这里是可以在线运行的代码示例

    example_rbac

    我们先抛开语法,代码其实就是描述了一条规则:

    用户是否有角色,角色是否有权限操作的资源

    下面我们开始学习OPA如何定义这条规则

    基本语法

    OPA基于一种数据查询语言Datalog实现了描述语言Rego

    OPARego基本语法如下表:

    | 语法 | 例子 | | ---- | ---- | | 上下文| data| | 输入| input| | 索引取值 | data.bindings[0] | | 比较 | "alice" == input.subject.user | | 赋值 |user := input.subject.user| | 规则| < Header > { < Body > }| | 规则头| < Name > = < Value > { ... } 或者 < Name > { ... }| | 规则体| And 运算的一个个描述| | 多条同名规则| Or 运算的一个规则| | 规则默认值| default allow = false| | 函数| fun(x) { ... }| | 虚拟文档|doc[x] { ... }|

    一点也不多。函数和虚拟文档我们后边再开文章展开,今天主要看明白他的规则定义。

    首先输入会挂在input对象下,用到的上下文(就是规则决策基于的源数据)会挂在data对象下

    rule

    当定义规则时:

    • 每条规则都会有返回值

      • 格式 1:< Name > { ... }

      不声明返回值,则只返回 true 或 false

      • 格式 2 < Name > = < Value > { ... }

      声明返回值 < Value > 则返回其值

    • 规则体内每条描述会逐条And运算,全部成立才会返回值

    • 多条同名规则相互之间是Or运算,满足其一即可

    具体到代码中规则allow, 默认值是 false

    要求user_has_rolerole_has_permission同时满足

    两者的role_name也是一样。

    你可能发现,局部变量role_name 没声明啊!

    Rego里可以省略声明局部变量, 直接使用。

    Tips: 但要这样的变量可以被同名的全局变量修改。 局部变量必要时还是应该使用some声明 如 some role_name

    default allow = false
    
    # allow will be true when user has role and role has permission
    allow {
      user_has_role[role_name]
      role_has_permission[role_name]
    }
    

    然后其中user_has_role[role_name]这种带参数的结构不是规则,叫虚拟文档(文档:可被查询的集合)

    # check user role binding exist
    user_has_role[role_name] {
      role_binding = data.bindings[_]
      role_binding.role = role_name
      role_binding.user = input.subject.user
    }
    

    Tips: 仔细同学会发现,线上运行版有withrole_binding = data.bindings[_] with data.bindings as data_context.bindings with 是用来替换输入 input 或者上下文 data 里的数据。 因为线上版没法指定上边的 data.json, 所以通过变量data_context替换传入的。

    集合里边role_binding = data.bindings[_]是遍历data.bindings

    Rego的遍历语法类似 python,这里遍历流程是

    data.bindings一个值赋值给role_binding

    进行后续处理,处理完后再赋下一个值

    Tips: _是特殊变量名,当需要变量占位又不需要后边引用时使用(类似 Go 的_

    至于role_binding.role = role_name这条你应该能猜到是判断请求过来的 role 名是否和配置一致

    可是为什么是=操作符,不应该是==?

    这里是一个有趣的点!

    unification

    Rego中实际只有=,而且作用是为变量赋值使等式成立,叫Unification

    :=局部变量赋值,==比较,是=的语法糖,为了实现局部变量赋值和比较,和编译错误更容易区分

    所以=更像是数据查询。(毕竟Rego是一个数据查询语言嘛)

    这里举个例子就好理解了:

    [x, "world"] = ["hello", y]
    # 之后,x 值为 hello,y 为 world
    

    总结一下,本文介绍什么是OPA,并借一个简单的 RBAC 例子初探了Rego强大的声明规则语法。

    下一篇,将会介绍如何本地优雅的开发OPA,感兴趣同学可以先在OPA的 playground 玩玩。

    了解更多:OPA 的 Rego 文档 本文代码详见:NewbMiao/opa-koans


    文章首发公众号:newbmiao

    推荐阅读:OPA 系列

    3 条回复    2020-03-21 11:03:03 +08:00
    simon8410
        1
    simon8410  
       2020-03-18 15:35:32 +08:00
    写得不错,赞一个
    newmiao
        2
    newmiao  
    OP
       2020-03-20 21:39:38 +08:00
    @simon8410 那是 OPA 真香,用起来很方便
    newmiao
        3
    newmiao  
    OP
       2020-03-21 11:03:03 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1055 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 20:07 · PVG 04:07 · LAX 12:07 · JFK 15:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.