OPA 重新定義規則引擎-入門篇 (持續更新中)

newbmiao發表於2020-03-17

檢視更多:歷史集錄


OPA,全稱OpenPolicyAgent, 底層用Go實現,它靈活而強大的宣告式語言全面支援通用策略定義。

目前國內資料還比較少。

個人因為工作接觸比較多,打算陸續分享些教程介紹下。

私以為規則引擎的技術選型完全可以多這個選擇~~

什麼是 OPA

具體看官方文件 OPA philosophy docs

主要關鍵詞是:

  • 輕量級的通用策略引擎
  • 可與服務共存
  • 整合方式可以是 sidecar、主機級守護程式或庫引入

opa

文字圖片還是不夠生動,看看 OPA 作者怎麼說:

優點

  • 強大的宣告式策略

    • 上下文感知
    • 表達性強
    • 快速
    • 可移植
  • 輸入和輸出支援任意格式

配合強大的宣告式策略語言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值為helloy為world

總結一下,本文介紹什麼是OPA,並借一個簡單的 RBAC 例子初探了Rego強大的宣告規則語法。

下一篇,將會介紹如何本地優雅的開發OPA,感興趣同學可以先在OPA的 playground 玩玩。

瞭解更多:OPA 的 Rego 文件

本文程式碼詳見:NewbMiao/opa-koans


文章首發公眾號:newbmiao

推薦閱讀:OPA 系列

歡迎關注,獲取及時更新內容

更多原創文章乾貨分享,請關注公眾號
  • OPA 重新定義規則引擎-入門篇 (持續更新中)
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章