天天看點

Open Policy Agent(OPA) 【1】介紹

文章目錄

  • ​​1. OPA 介紹​​
  • ​​2. OPA 解決了哪些問題​​
  • ​​3. rego介紹​​
  • ​​4. OPA 安裝​​
  • ​​5. OPA 運作​​
  • ​​6. OPA run(互動式)​​
  • ​​7. OPA run(伺服器)​​
  • ​​8. Rego 文法​​
  • ​​8.1 參考​​
  • ​​8.2 表達式(邏輯與)​​
  • ​​8.3 邏輯或​​
  • ​​8.4 Variables變量​​
  • ​​8.5 疊代​​
  • ​​8.6 規則​​
  • ​​8.6.1 完整規則​​
  • ​​8.6.2 部分規則​​
  • ​​8.7 文法示例​​
  • ​​9. 将 OPA 用作Go庫​​

1. OPA 介紹

開放政策代理(OPA,發音為“ oh-pa”)是一個開放源代碼的通用政策引擎,它統一了整個堆棧中的政策執行。OPA提供了一種進階的聲明性語言,使您可以将政策指定為代碼和簡單的API,以減輕軟體決策的負擔。您可以使用OPA在微服務,Kubernetes,CI / CD管道,API網關等中實施政策。

​OPA​

​​ 最初是由 ​

​Styra​

​​ 公司在 2016 年建立并開源的項目,目前該公司的主要産品就是提供可視化政策控制及政策執行的可視化 ​

​Dashboard​

​ 服務的。

OPA 首次進入 ​

​CNCF​

​​ 并成為 ​

​sandbox​

​​ 級别的項目是在 2018 年, 在 2021 年的 2 月份便已經從 ​

​CNCF​

​​ 畢業,這個過程相對來說還是比較快的,由此也可以看出 ​

​OPA​

​ 是一個比較活躍且應用廣泛的項目。

透過現象看本質,政策就是一組規則,請求發送到引擎,引擎根據規則來進行決策。OPA 并不負責具體任務的執行,它僅負責決策,需要決策的請求通過 JSON 的方式傳遞給 OPA ,在 OPA 決策後,也會将結果以 JSON 的形式傳回。

Open Policy Agent(OPA) 【1】介紹

2. OPA 解決了哪些問題

OPA通過評估查詢輸入以及針對政策和資料來生成政策決策。OPA和Rego是域無關的,是以您可以描述政策中幾乎所有類型的不變式。例如:

  1. 哪些使用者可以通路哪些資源;
  2. 允許哪些子網出口流量;
  3. 必須将工作負載部署到哪個群集;
  4. 可以從哪些系統資料庫二進制檔案下載下傳;
  5. 容器可以執行哪些OS功能;
  6. 可以在一天的哪個時間通路系統;
  7. 需要政策控制使用者是否可登陸伺服器或者做一些操作;
  8. 需要政策控制哪些項目/哪些元件可進行部署;
  9. 需要政策控制如何通路資料庫;

    10.需要政策控制哪些資源可部署到 Kubernetes 中;

政策決策不僅限于簡單的是/否或允許/拒絕答案。像查詢輸入一樣,您的政策可以生成任意結構化資料作為輸出。

Open Policy Agent(OPA) 【1】介紹

但是對于這些場景或者軟體來說,配置它們的政策是需要與該軟體進行耦合的,彼此是不統一,不通用的。管理起來也會比較混亂,帶來了不小的維護成本。OPA 的出現可以将各處配置的政策進行統一,極大的降低了維護成本。以及将政策與對應的軟體/服務進行解耦,友善進行移植/複用。

Open Policy Agent(OPA) 【1】介紹

假設您在具有以下系統的組織中工作:

Open Policy Agent(OPA) 【1】介紹

系統中包含三種元件:

  • 伺服器暴露零層或多個協定(例如,http,ssh等等)
  • 網絡連接配接伺服器,可以是公共的或私有的。公共網絡已連接配接到Internet。
  • 端口将伺服器連接配接到網絡。

所有伺服器,網絡和端口均由腳本設定。該腳本接收系統的JSON表示作為輸入:

{
    "servers": [
        {"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
        {"id": "db", "protocols": ["mysql"], "ports": ["p3"]},
        {"id": "cache", "protocols": ["memcache"], "ports": ["p3"]},
        {"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]},
        {"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]}
    ],
    "networks": [
        {"id": "net1", "public": false},
        {"id": "net2", "public": false},
        {"id": "net3", "public": true},
        {"id": "net4", "public": true}
    ],
    "ports": [
        {"id": "p1", "network": "net1"},
        {"id": "p2", "network": "net3"},
        {"id": "p3", "network": "net2"}
    ]
}      

當天早些時候,您的老闆告訴您必須實施的新安全政策:

  • Servers reachable from the Internet must not expose the insecure ‘http’ protocol.

    從Internet可通路的伺服器不能暴露不安全的“http”協定。

  • Servers are not allowed to expose the ‘telnet’ protocol.

    伺服器不允許公開’telnet’協定

配置了伺服器,網絡和端口并且合規團隊希望定期稽核系統以查找違反政策的伺服器時,必須執行該政策。

您的老闆已要求您确定OPA是否适合實施該政策。

3. rego介紹

​OPA​

​​ 中的政策是以 ​

​Rego​

​​ 這種 ​

​DSL​

​(Domain Specific Language) 來表示的。

​Rego​

​​ 受 ​

​Datalog​

​(https://en.wikipedia.org/wiki/Datalog) 的啟發,并且擴充了 Datalog 對于結構化文檔模型的支援,友善以 JSON 的方式對資料進行處理。

​Rego​

​ 允許政策制定者可以專注于傳回内容的查詢而不是如何執行查詢。同時 OPA 中也内置了執行規則時的優化,使用者可以預設使用。

​Rego​

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

每個規則都是由頭部和主體組成。在 Rego 中,如果規則主體對于某些變量指派為真,那麼我們說規則頭為真。可以通過絕對路徑引用任何加載到 OPA 中的規則來查詢它的值。規則的路徑總是:data…(規則生成的所有值都可以通過全局 data 變量進行查詢。例如,下方示例中的 ​

​data.example.rules.any_public_networks​

完整規則是将單個值配置設定給變量的 ​

​if-then​

​ 語句。

Open Policy Agent(OPA) 【1】介紹

部分規則是生成一組值并将該組配置設定給變量的 ​

​if-then​

​ 語句

Open Policy Agent(OPA) 【1】介紹

邏輯或是要在 ​

​Rego​

​ 中定義多個具有相同名稱的規則。(查詢中将多個表達式連接配接在一起時,表示的是邏輯 AND)

Open Policy Agent(OPA) 【1】介紹

4. OPA 安裝

本節說明如何直接查詢OPA并在自己的計算機上與其互動。

1.下載下傳OPA

要開始從GitHub版本下載下傳适用于您平台的OPA二進制檔案,請執行以下操作:

在macOS(64位)上:

curl      

在Linux(64位)上:

curl -L -o opa https://openpolicyagent.org/downloads/v0.28.0/opa_linux_amd64
chmod 755 ./opa
cp opa /usr/local/bin/
$ 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      

5. OPA 運作

與OPA互動的最簡單方法是通過指令行使用opa eval子指令。​

​opa eval​

​是一把瑞士軍刀,可用于評估任意的Rego表達式和政策。opa eval支援大量用于控制評估的選項。常用标志包括:

旗幟 短的 描述

  • –bundle -b 将捆綁封包件或目錄加載到OPA中。該标志可以重複。
  • –data -d 将政策或資料檔案加載到OPA中。該标志可以重複。
  • –input -i 加載資料檔案并将其用作input。該标志不能重複。
  • –format -f 設定要使用的輸出格式。預設值為json,并且旨在用于程式設計。該pretty格式發出了更多人類可讀的輸出。
  • –fail 不适用 如果查詢未定義,則以非零的退出代碼退出。
  • –fail-defined 不适用 如果查詢不是未定義的,則以非零的退出代碼退出。

例如:

input.json:

{
    "servers": [
        {"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
        {"id": "db", "protocols": ["mysql"], "ports": ["p3"]},
        {"id": "cache", "protocols": ["memcache"], "ports": ["p3"]},
        {"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]},
        {"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]}
    ],
    "networks": [
        {"id": "net1", "public": false},
        {"id": "net2", "public": false},
        {"id": "net3", "public": true},
        {"id": "net4", "public": true}
    ],
    "ports": [
        {"id": "p1", "network": "net1"},
        {"id": "p2", "network": "net3"},
        {"id": "p3", "network": "net2"}
    ]
}      

example.rego:

package example

default allow = false                               # unless otherwise defined, allow is false

allow = true {                                      # allow is true if...
    count(violation) == 0                           # there are zero violations.
}

violation[server.id] {                              # a server is in the violation set if...
    some server
    public_server[server]                           # it exists in the 'public_server' set and...
    server.protocols[_] == "http"                   # it contains the insecure "http" protocol.
}

violation[server.id] {                              # a server is in the violation set if...
    server := input.servers[_]                      # it exists in the input.servers collection and...
    server.protocols[_] == "telnet"                 # it contains the "telnet" protocol.
}

public_server[server] {                             # a server exists in the public_server set if...
    some i, j
    server := input.servers[_]                      # it exists in the input.servers collection and...
    server.ports[_] == input.ports[i].id            # it references a port in the input.ports collection and...
    input.ports[i].network == input.networks[j].id  # the port references a network in the input.networks collection and...
    input.networks[j].public                        # the network is public.
}      

執行:

root@master:~/cks/opa# ./opa eval "1*2+3"
{
  "result": [
    {
      "expressions": [
        {
          "value": 5,
          "text": "1*2+3",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}      
root@master:~/cks/opa# ./opa eval -i input.json -d example.rego "data.example.violation[x]"
{
  "result": [
    {
      "expressions": [
        {
          "value": "ci",
          "text": "data.example.violation[x]",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ],
      "bindings": {
        "x": "ci"
      }
    },
    {
      "expressions": [
        {
          "value": "busybox",
          "text": "data.example.violation[x]",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ],
      "bindings": {
        "x": "busybox"
      }
    }
  ]
}      
root@master:~/cks/opa# ./opa eval --fail-defined -i input.json -d example.rego "data.example.violation[x]"
{
  "result": [
    {
      "expressions": [
        {
          "value": "ci",
          "text": "data.example.violation[x]",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ],
      "bindings": {
        "x": "ci"
      }
    },
    {
      "expressions": [
        {
          "value": "busybox",
          "text": "data.example.violation[x]",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ],
      "bindings": {
        "x": "busybox"
      }
    }
  ]
}
root@master:~/cks/opa# echo $?
1      

6. OPA run(互動式)

OPA包括一個互動式外殼程式或REPL(讀取-評估-列印循環)。您可以使用REPL來試驗政策并為新政策建立原型。

要啟動REPL,隻需:

./opa run      

當您在REPL中輸入語句時,OPA會對它們進行評估并列印結果。

> true
true
> 3.14
3.14
> ["hello", "world"]
[
  "hello",
  "world"
]      

大多數REPL允許您定義以後可以引用的變量。OPA允許您執行類似的操作。例如,您可以定義一個pi常量,如下所示:

> pi := 3.14      

定義“ pi”後,您将查詢該值并根據該值編寫表達式:

> pi
3.14
> pi > 3
true      

通過按​

​Control-D​

​​或鍵入​

​exit​

​以下指令退出REPL :

> exit      

您可以通過在指令行上傳遞政策和資料檔案來将它們加載到REPL中。預設情況下,JSON和YAML檔案植于下data。

opa run input.json      

運作一些查詢以查找資料:

> data.server[0].protocols[1]
undefined
> data.servers[0].protocols[1]
"ssh"
> data.servers[i].protocols[j]
+---+---+------------------------------+
| i | j | data.servers[i].protocols[j] |
+---+---+------------------------------+
| 0 | 0 | "https"                      |
| 0 | 1 | "ssh"                        |
| 1 | 0 | "mysql"                      |
| 2 | 0 | "memcache"                   |
| 3 | 0 | "http"                       |
| 4 | 0 | "telnet"                     |
+---+---+------------------------------+
> net := data.networks[_]; net.public
+-----------------------------+
|             net             |
+-----------------------------+
| {"id":"net3","public":true} |
| {"id":"net4","public":true} |      

要将資料檔案設定為inputREPL中的文檔,請在檔案路徑前添加字首:

opa run example.rego repl.input:input.json      
> data.example.public_server[s]
+-------------------------------------------------------------------+-------------------------------------------------------------------+
|                                 s                                 |                   data.example.public_server[s]                   |
+-------------------------------------------------------------------+-------------------------------------------------------------------+
| {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} | {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} |
| {"id":"ci","ports":["p1","p2"],"protocols":["http"]}              | {"id":"ci","ports":["p1","p2"],"protocols":["http"]}              |      

7. OPA run(伺服器)

要與OPA內建,您可以将其作為伺服器運作并通過HTTP執行查詢。您可以使用-s或将OPA作為伺服器啟動​

​--server​

​:

./opa run --server ./example.rego      

預設情況下,OPA在上偵聽HTTP連接配接​

​0.0.0.0:8181​

​​。請參閱參考資料​

​opa run --help​

​,以擷取用于更改偵聽位址,啟用TLS等的選項的清單。

在另一個終端内部使用curl(或類似的工具)來通路OPA的​

​HTTP API​

​。查詢/v1/dataHTTP API時,必須将輸入資料包裝在JSON對象内:

{
    "input": <value>
}      

建立輸入檔案的副本,以通過發送curl:

cat <<EOF>
{
    "input": $(cat input.json)      

執行一些curl請求并檢查輸出:

curl localhost:8181/v1/data/example/violation -d @v1-data-input.json -H 'Content-Type: application/json'
curl localhost:8181/v1/data/example/allow -d @v1-data-input.json -H 'Content-Type: application/json'      

預設情況下​

​data.system.main​

​​,用于不帶路徑的政策查詢。當您在不提供路徑的情況下執行查詢時,不必包裝輸入。如果該​

​data.system.main​

​決定未定義,則将其視為錯誤:

curl localhost:8181 -i -d @input.json -H 'Content-Type: application/json'      

您可以重新啟動OPA并将其配置為使用任何決策作為預設決策:

./opa run --server --set=default_decision=example/allow ./example.rego      

curl從上面重新運作最後一個指令:

curl localhost:8181 -i -d @input.json -H 'Content-Type: application/json'      

8. Rego 文法

OPA政策以稱為Rego的進階聲明性語言表示。Rego(發音為“ ray-go”)是專門為在複雜的分層資料結構上表達政策而建構的。有關Rego的詳細資訊,請參閱​​政策語言文檔​​。

below以下示例是互動式的!如果在包含伺服器,網絡和端口的上方編輯輸入資料,則輸出将在下面更改。同樣,如果您在下面的示例中編輯查詢或規則,則輸出将更改。在通讀本節時,請嘗試更改輸入,查詢和規則,并觀察輸出的差異。

????也可以使用以下指令在您的計算機上本地運作它們opa eval,這是設定說明。

8.1 參考

當OPA評估政策時,它将查詢中提供的資料綁定到名為的全局變量input。您可以使用.(點)運算符在輸入中引用資料。

input.servers      
[
  {
    "id": "app",
    "ports": [
      "p1",
      "p2",
      "p3"
    ],
    "protocols": [
      "https",
      "ssh"
    ]
  },
  {
    "id": "db",
    "ports": [
      "p3"
    ],
    "protocols": [
      "mysql"
    ]
  },
  {
    "id": "cache",
    "ports": [
      "p3"
    ],
    "protocols": [
      "memcache"
    ]
  },
  {
    "id": "ci",
    "ports": [
      "p1",
      "p2"
    ],
    "protocols": [
      "http"
    ]
  },
  {
    "id": "busybox",
    "ports": [
      "p1"
    ],
    "protocols": [
      "telnet"
    ]
  }
]      

要引用數組元素,可以使用熟悉的方括号文法:

input.servers[0].protocols[0]      
"https"      
keys如果鍵包含以外的其他字元,則可以使用相同的方括号文法 [a-zA-Z0-9_]。例如input[“foo~bar”]。

如果引用的值不存在,則OPA傳回undefined。未定義表示OPA無法找到任何結果。

input.deadbeef      
undefined decision      

8.2 表達式(邏輯與)

要在Rego中制定政策決策,您要針對輸入和其他資料編寫表達式。

input.servers[0].id == "app"      
true      

OPA包括一組内置函數,可用于執行常見操作,例如字元串操作,正規表達式比對,算術,聚合等。

count(input.servers[0].protocols) >= 1      
true      

有關現成的OPA支援的内置功能的完整清單,請參閱“​​政策參考​​”頁面。

多個表達式通過;(AND)運算符連接配接在一起。為了使查詢産生結果,查詢中的所有表達式必須為真或已定義。表達式的順序無關緊要。

input.servers[0].id == "app"; input.servers[0].protocols[0] == "https"      
true      

您可以;通過将表達式分成多行來省略(AND)運算符。以下查詢與上一個查詢具有相同的含義:

input.servers[0].id == "app"
input.servers[0].protocols[0] == "https"      
true      

如果查詢中的任何表達式都不為真(或未定義),則結果為未定義。在下面的示例中,第二個表達式為false:

input.servers[0].id == "app"
input.servers[0].protocols[0] == "telnet"      
undefined decision      

8.3 邏輯或

在查詢中将多個表達式連接配接在一起時,您表示的是邏輯與。要在Rego中表達邏輯OR,您可以定義多個具有相同名稱的規則。讓我們來看一個例子。

想象一下,您想知道是否有任何伺服器公開允許用戶端外殼通路的協定。為了确定這一點,你可以定義聲明了一個完整的規則 ​

​shell_accessible​

​​是​

​true​

​​,如果任何伺服器暴露"​

​telnet​

​​“或”​

​ssh​

​" 協定:

package example.logical_or

default shell_accessible = false

shell_accessible = true {
    input.servers[_].protocols[_] == "telnet"
}

shell_accessible = true {
    input.servers[_].protocols[_] == "ssh"
}      
{
    "servers": [
        {
            "id": "busybox",
            "protocols": ["http", "telnet"]
        },
        {
            "id": "web",
            "protocols": ["https"]
        }
    ]
}      
shell_accessible

true      
defaultkeyword如果未定義具有相同名稱的所有其他規則,則該關鍵字告訴OPA為該變量配置設定一個值。

當您将邏輯或與部分規則一起使用時,每個規則定義都會影響配置設定給變量的一組值。例如,可以将上面的示例修改為生成一組公開"telnet"或 的伺服器"ssh"。

package example.logical_or

shell_accessible[server.id] {
    server := input.servers[_]
    server.protocols[_] == "telnet"
}

shell_accessible[server.id] {
    server := input.servers[_]
    server.protocols[_] == "ssh"
}      
{
    "servers": [
        {
            "id": "busybox",
            "protocols": ["http", "telnet"]
        },
        {
            "id": "db",
            "protocols": ["mysql", "ssh"]
        },
        {
            "id": "web",
            "protocols": ["https"]
        }
    ]
}      
shell_accessible

[
  "busybox",
  "db"
]      

8.4 Variables變量

您可以使用​

​:=​

​​(指派)運算符将值存儲在中間變量中。可以像一樣引用變量​

​input​

s := input.servers[0]
s.id == "app"
p := s.protocols[0]
p == "https"      
+---------+-------------------------------------------------------------------+
|    p    |                                 s                                 |
+---------+-------------------------------------------------------------------+
| "https" | {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} |
+---------+-------------------------------------------------------------------+      

當OPA評估表達式時,它将查找使所有表達式都為真的變量的值。如果沒有使所有表達式都為真的變量指派,則結果是不确定的。

s := input.servers[0]
s.id == "app"
s.protocols[1] == "telnet"      
undefined decision      

變量是不可變的。如果您嘗試兩次配置設定相同的變量,OPA将報告錯誤。

s := input.servers[0]
s := input.servers[1]      
1 error occurred: 2:1: rego_compile_error: var s assigned above      

OPA必須能夠枚舉所有表達式中所有變量的值。如果OPA無法枚舉任何表達式中變量的值,則OPA将報告錯誤。

x := 1
x != y  # y has not been assigned a value      
2 errors occurred:
2:1: rego_unsafe_var_error: var _ is unsafe
2:1: rego_unsafe_var_error:      

8.5 疊代

像其他聲明性語言(例如SQL)一樣,Rego沒有顯式的循環或疊代構造。而是将變量注入表達式中時,隐式發生疊代。

要了解Rego中疊代的工作原理,請想象您需要檢查是否有任何公共網絡。回想一下,網絡是在數組中提供的:

input.networks      
[
  {
    "id": "net1",
    "public": false
  },
  {
    "id": "net2",
    "public": false
  },
  {
    "id": "net3",
    "public": true
  },
  {
    "id": "net4",
    "public": true
  }
]      

一種選擇是測試輸入中的每個網絡:

input.networks[0].public == true
false

input.networks[1].public == true
false

input.networks[2].public == true
true      

這種方法是有問題的,因為可能有太多網絡無法靜态列出,或更重要的是,可能無法事先知道網絡的數量。

在Rego中,解決方案是将數組索引替換為變量。

some i; input.networks[i].public == true      
+---+
| i |
+---+
| 2 |
| 3 |      

現在,查詢将要求該值i使整個表達式為真。當您在引用中替換變量時,OPA會自動查找滿足查詢中所有表達式的變量配置設定。就像中間變量一樣,OPA傳回變量的值。

您可以根據需要替換任意多個變量。例如,要确定是否有伺服器公開了不安全的"http"協定,您可以編寫:

some i, j; input.servers[i].protocols[j] == "http"

+---+---+
| i | j |
+---+---+
| 3 | 0 |      

如果變量出現多次,則配置設定滿足所有表達式。例如,要查找連接配接到公共網絡的端口的ID,可以編寫:

some i, j
id := input.ports[i].id
input.ports[i].network == input.networks[j].id
input.networks[j].public      
+---+------+---+
| i |  id  | j |
+---+------+---+
| 1 | "p2" | 2 |      

為變量提供好名字可能很難。如果僅引用一次變量,則可以将其替換為特殊的_(通配符變量)運算符。從概念上講,的每個執行個體_都是一個唯一變量。

input.servers[_].protocols[_] == "http"
true      

就像引用不存在的字段或無法比對的表達式的引用一樣,如果OPA無法找到滿足所有表達式的任何變量指派,則結果是不确定的。

some i; input.servers[i].protocols[i] == "ssh"  # there is no assignment of i that satisfies the expression      

8.6 規則

Rego使您可以使用規則封裝和重用邏輯。規則隻是if-then邏輯語句。規則可以是“完整”或“部分”。

8.6.1 完整規則

完整的規則是if-then語句,這些語句将單個值配置設定給變量。例如:

package example.rules

any_public_networks = true {  # is true if...
    net := input.networks[_]  # some network exists and..
    net.public                # it is public.
}      

每條規則都由一個頭和一個身體組成。在Rego中,如果規則主體對于某些變量配置設定集為true,則說規則标題為true 。在上面的示例​

​any_public_networks = true​

​​中,頭是​

​net := input.networks[_]​

​​; ​

​net.public​

​是身體。

您可以查詢規則生成的值,就像其他任何值一樣:

any_public_networks
true      

規則生成的所有值都可以通過全局data變量查詢。

data.example.rules.any_public_networks
true      

您可以通過使用絕對路徑引用OPA加載的任何規則來查詢其值。規則的路徑始終為:

​​

​data.<package-path>.<rule-name>​

​。

如果您省略​

​= <value>​

​​規則标題的一部分,則該值預設為​

​true​

​。您可以按以下方式重寫上面的示例,而無需更改其含義:

package example.rules

any_public_networks {
    net := input.networks[_]
    net.public
}      

要定義常量,請省略規則主體。省略規則正文時,預設為​

​true​

​​。由于規則主體為true,是以規則标頭始終為​

​true / defined​

​。

package example.constants

pi = 3.14      

可以像其他任何值一樣查詢這樣定義的常量:

pi > 3

true      

如果OPA無法找到滿足規則主體的變量配置設定,則可以說該規則是未定義的。例如,如果input提供給OPA的不包括公共網絡,​

​any_public_networks​

​則将是未定義的(與false相同)。下面,為OPA提供了一組不同的輸入網絡(都不是公共的):

{
    "networks": [
        {"id": "n1", "public": false},
        {"id": "n2", "public": false}
    ]
}      
any_public_networks


undefined decision      

8.6.2 部分規則

部分規則是​

​if-then​

​語句,它們生成一組值并将該組值配置設定給變量。例如:

package example.rules

public_network[net.id] {      # net.id is in the public_network set if...
    net := input.networks[_]  # some network exists and...
    net.public                # it is public.
}      

在上面的示例中​

​public_network[net.id]​

​​是規則頭,并且​

​net := input.networks[_]​

​​; ​

​net.public​

​是規則主體。您可以像查詢其他任何值一樣查詢整個值集:

public_network

[
  "net3",
  "net4"
]      

您可以通過使用變量引用​

​set​

​元素來周遊值集:

some n; public_network[n]

+--------+-------------------+
|   n    | public_network[n] |
+--------+-------------------+
| "net3" | "net3"            |
| "net4" | "net4"            |      

最後,您可以使用相同的文法檢查集合中是否存在值:

public_network["net3"]

"net3"      

除了部分定義集合外,您還可以部分定義鍵/值對(也稱為對象)。有關更多資訊,請參見 語言指南中的​​規則​​。

8.7 文法示例

以上各節介紹了Rego的核心概念。綜上所述,讓我們回顧一下所需的政策(英語):

  1. Servers reachable from the Internet must not expose the insecure ‘http’ protocol.

    從Internet可通路的伺服器不能暴露不安全的“http”協定。

  2. Servers are not allowed to expose the ‘telnet’ protocol.

    伺服器不允許公開’telnet’協定。

在較進階别,該政策需要識别違反某些條件的伺服器。為了實施此政策,我們可以定義稱為的規則violation ,這些規則生成一組違反的伺服器。

例如:

package example

allow = true {                                      # allow is true if...
    count(violation) == 0                           # there are zero violations.
}

violation[server.id] {                              # a server is in the violation set if...
    some server
    public_server[server]                           # it exists in the 'public_server' set and...
    server.protocols[_] == "http"                   # it contains the insecure "http" protocol.
}

violation[server.id] {                              # a server is in the violation set if...
    server := input.servers[_]                      # it exists in the input.servers collection and...
    server.protocols[_] == "telnet"                 # it contains the "telnet" protocol.
}

public_server[server] {                             # a server exists in the public_server set if...
    some i, j
    server := input.servers[_]                      # it exists in the input.servers collection and...
    server.ports[_] == input.ports[i].id            # it references a port in the input.ports collection and...
    input.ports[i].network == input.networks[j].id  # the port references a network in the input.networks collection and...
    input.networks[j].public                        # the network is public.
}      
some x; violation[x]

+-----------+--------------+
|     x     | violation[x] |
+-----------+--------------+
| "ci"      | "ci"         |
| "busybox" | "busybox"    |      

9. 将 OPA 用作Go庫

OPA可以作為庫嵌入到Go程式中。将OPA嵌入為庫的最簡單方法是導入​

​github.com/open-policy-agent/opa/rego​

​ 軟體包。

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

調用該​

​rego.New​

​函數以建立可以準備或評估的對象:

r := rego.New(
    rego.Query("x = data.example.allow"),
    rego.Load([]string{"./example.rego"}, nil))      

支援多種選項自定義的評價。有關詳細資訊,請參見​​GoDoc​​​頁面。構造新​

​rego.Rego​

​​對象後,您可以調用 ​

​PrepareForEval()​

​​以獲得可執行查詢。如果​

​PrepareForEval()​

​​失敗,則表明傳遞給​

​rego.New()​

​調用的選項之一無效(例如,解析錯誤,編譯錯誤等)

ctx := context.Background()
query, err := r.PrepareForEval(ctx)
if err != nil {
    // handle error
}      

可以将準備好的查詢對象緩存在記憶體中,在多個​

​goroutine​

​​中共享,并使用不同的輸入重複調用。調用​

​Eval()​

​以執行準備好的查詢。

bs, err := ioutil.ReadFile("./input.json")
if err != nil {
    // handle error
}

var input interface{}

if err := json.Unmarshal(bs, &input); err != nil {
    // handle error
}

rs, err := query.Eval(ctx, rego.EvalInput(input))
if err != nil {
    // handle error
}      

該政策決策包含在Eval()調用傳回的結果中。您可以檢查該決定并進行相應處理:

// In this example we expect a single result (stored in the variable 'x').
fmt.Println("Result:", rs[0].Bindings["x"])      

您可以将上述步驟組合到一個簡單的指令行程式中,該程式可以評估政策并輸出結果:

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)
    }

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

    // Do something with the result.
    fmt.Println(rs)
}      
go run main.go example.rego 'data.example.violation' < input.json
[{[[ci busybox]] map[]}]      
  • ​​Open Policy Agent(OPA) 【1】介紹​​
  • ​​Open Policy Agent(OPA) 【2】rego文法​​
  • ​​Open Policy Agent(OPA) 【3】實戰​​
  • ​​雲原生聖經​​
  • ​​openpolicyagent官網​​