Open Policy Agent(OPA) 入門實踐

張晉濤 發表於 2021-12-07

大家好,我是張晉濤。

本篇我來為你介紹一個我個人很喜歡的,通用策略引擎,名叫 OPA,全稱是 Open Policy Agent。

在具體聊 OPA 之前,我們先來聊一下為什麼需要一個通用策略引擎,以及 OPA 解決了什麼問題。

OPA 解決了什麼問題

在實際的生產環境中很多場景中都需要策略控制,比如:

  • 需要策略控制使用者是否可登陸伺服器或者做一些操作;
  • 需要策略控制哪些專案/哪些元件可進行部署;
  • 需要策略控制如何訪問資料庫;
  • 需要策略控制哪些資源可部署到 Kubernetes 中;

img

但是對於這些場景或者軟體來說,配置它們的策略是需要與該軟體進行耦合的,彼此是不統一,不通用的。管理起來也會比較混亂,帶來了不小的維護成本。

OPA 的出現可以將各處配置的策略進行統一,極大的降低了維護成本。以及將策略與對應的軟體/服務進行解耦,方便進行移植/複用。

img

OPA 的發展過程

OPA 最初是由 Styra 公司在 2016 年建立並開源的專案,目前該公司的主要產品就是提供視覺化策略控制及策略執行的視覺化 Dashboard 服務的。

OPA 首次進入 CNCF 併成為 sandbox 級別的專案是在 2018 年, 在 2021 年的 2 月份便已經從 CNCF 畢業,這個過程相對來說還是比較快的,由此也可以看出 OPA 是一個比較活躍且應用廣泛的專案。

OPA 是什麼

前面我們已經介紹過 Open Policy Agent (OPA) 是一種開源的通用策略引擎,可在整個堆疊中實現統一、上下文感知的策略控制。

OPA 可將策略決策與應用程式的業務邏輯分離(解耦),透過現象看本質,策略就是一組規則,請求傳送到引擎,引擎根據規則來進行決策。

img

圖 3 ,OPA 的策略解耦示例

OPA 並不負責具體任務的執行,它僅負責決策,需要決策的請求通過 JSON 的方式傳遞給 OPA ,在 OPA 決策後,也會將結果以 JSON 的形式返回。

Rego

OPA 中的策略是以 Rego 這種 DSL(Domain Specific Language) 來表示的。

Rego 受 Datalog(https://en.wikipedia.org/wiki...) 的啟發,並且擴充套件了 Datalog 對於結構化文件模型的支援,方便以 JSON 的方式對資料進行處理。

Rego 允許策略制定者可以專注於返回內容的查詢而不是如何執行查詢。同時 OPA 中也內建了執行規則時的優化,使用者可以預設使用。

Rego 允許我們使用規則(if-then)封裝和重用邏輯,規則可以是完整的或者是部分的。

每個規則都是由頭部和主體組成。在 Rego 中,如果規則主體對於某些變數賦值為真,那麼我們說規則頭為真。可以通過絕對路徑引用任何載入到 OPA 中的規則來查詢它的值。規則的路徑總是: data.<package-path>.<rule-name> (規則生成的所有值都可以通過全域性 data 變數進行查詢。 例如,下方示例中的 data.example.rules.any_public_networks

  • 完整規則是將單個值分配給變數的 if-then 語句。

img

圖 4 ,Rego 完整規則示例

  • 部分規則是生成一組值並將該組分配給變數的 if-then 語句。

img

圖 5 ,Rego 部分規則示例

  • 邏輯或是要在 Rego 中定義多個具有相同名稱的規則。(查詢中將多個表示式連線在一起時,表示的是邏輯 AND)

img

圖 6 ,Rego 規則或的完全規則和部分規則示例圖

OPA 的使用

OPA 的使用還是比較簡單的,我們來看一下。

安裝 OPA

二進位制方式

我們可以直接從 OPA 的 release 頁面下載其二進位制進行使用

➜  ~ wget -q -O ~/bin/opa https://github.com/open-policy-agent/opa/releases/download/v0.35.0/opa_linux_amd64_static 
➜  ~ chmod +x ~/bin/opa
➜  ~ opa version
Version: 0.35.0
Build Commit: a54537a
Build Timestamp: 2021-12-01T02:11:47Z
Build Hostname: 9e4cf671a460
Go Version: go1.17.3
WebAssembly: unavailable

容器

我們可以使用其官方映象

➜  ~ docker run --rm  openpolicyagent/opa:0.35.0 version    
Version: 0.35.0
Build Commit: a54537a
Build Timestamp: 2021-12-01T02:10:31Z
Build Hostname: 4ee9b086e5de
Go Version: go1.17.3
WebAssembly: available

OPA 互動

opa eval

最簡單的命令是 opa eval ,當然我們除了能使用它進行策略的執行外,還可以用來做表示式計算。

img

圖 7 , opa eval 的使用幫助

➜  ~ opa eval "6+6"
{
  "result": [
    {
      "expressions": [
        {
          "value": 12,
          "text": "6+6",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}

opa run

opa run 會啟動一個互動式 shell ( REPL) 。我們可以使用 REPL 來試驗策略並構建新的原型。

➜  ~ opa run
OPA 0.35.0 (commit a54537a, built at 2021-12-01T02:11:47Z)

Run 'help' to see a list of commands and check for updates.

> true
true
> ["Hello", "OPA"]
[
  "Hello",
  "OPA"
]
> pi := 3.14
Rule 'pi' defined in package repl. Type 'show' to see rules.
> show
package repl

pi := 3.14
> pi > 1
true

我們也可以將策略直接載入進去,或者 將 OPA 作為一個服務執行並通過 HTTP 執行查詢。預設情況下,OPA 監會監聽在 8181 埠。

➜  ~ opa run --server
{"addrs":[":8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2021-12-07T01:12:47+08:00"}

開啟瀏覽器也可以看到一個簡單的查詢視窗

img

opa 作為 go 的庫使用

OPA 可以作為庫嵌入到 Go 程式中。將 OPA 嵌入為庫的最簡單方法是匯入 github.com/open-policy-agent/opa/rego 包。通過 rego.New 函式用來建立一個可以準備或評估的物件, PrepareForEval() 以獲取可執行查詢。

以下是一個簡單的示例:

  • 目錄結構
➜  opa tree 
.
├── data
├── go.mod
├── go.sum
├── input.json
├── k8s-label.rego
└── main.go

1 directory, 5 files
  • 策略檔案

這裡的策略檔案是來校驗 INPUT 中是否包含名為 domain 的 label , 以及該 label 是否以 moelove-開頭。

package kubernetes.validating.existence

deny[msg] {
    not input.request.object.metadata.labels.domain
    msg := "Every resource must have a domain label"
}


deny[msg] {
    value := input.request.object.metadata.labels.domain
    not startswith(value, "moelove-")
    msg := sprintf("domain label must start with `moelove-`; found `%v`", [value])
}
  • INPUT 檔案, 以 Kubernetes 的 AdmissionReview 為例
{
    "kind": "AdmissionReview",
    "request": {
        "kind": {
            "kind": "Pod",
            "version": "v1"
        },
        "object": {
            "metadata": {
                "name": "myapp",
                "labels": {
                    "domain": "opa"
                }
            },
            "spec": {
                "containers": [
                    {
                        "image": "alpine",
                        "name": "alpine"
                    }
                ]
            }
        }
    }
}
  • main.go 檔案
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "os"

    "github.com/open-policy-agent/opa/rego"
)

func main() {

    ctx := context.Background()

    // Construct a Rego object that can be prepared or evaluated.
    r := rego.New(
        rego.Query(os.Args[2]),
        rego.Load([]string{os.Args[1]}, nil))

    // Create a prepared query that can be evaluated.
    query, err := r.PrepareForEval(ctx)
    if err != nil {
        log.Fatal(err)
    }

    // Load the input document from stdin.
    var input interface{}
    dec := json.NewDecoder(os.Stdin)
    dec.UseNumber()
    if err := dec.Decode(&input); err != nil {
        log.Fatal(err)
    }

    rs, err := query.Eval(ctx, rego.EvalInput(input))
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(rs)
}

執行結果如下:

➜  opa go run main.go k8s-label.rego "data" < input.json
[{[map[kubernetes:map[validating:map[existence:map[deny:[domain label must start with `moelove-`; found `opa`]]]]]] map[]}]

總結

以上便是對於 OPA 的一個大致介紹,OPA 的應用場景有很多,後續文章中將為大家分享 OPA 在 Kubernetes 中的應用,和將 OPA 應用與 CI/CD pipeline 的場景。敬請期待。


歡迎訂閱我的文章公眾號【MoeLove】