返回文章列表

Rego 策略編寫與 GateKeeper 佈署

本文探討如何使用 Rego 語言編寫 Kubernetes 策略,並結合 GateKeeper 實作動態准入控制,強化叢集安全性。文章涵蓋了 Rego 策略的開發、測試、除錯以及與 GateKeeper 的整合,並提供實際案例說明如何限制 Pod

Kubernetes 資安

Rego 作為 OPA 的專用策略語言,其宣告式特性簡化了策略邏輯的表達。本文以限制容器映像來源為例,逐步講解如何編寫 Rego 策略,並整合到 GateKeeper 中實作動態准入控制。首先,我們定義了 invalidRegistry 規則,用於判斷映像是否來自允許的登入檔。接著,為了滿足 GateKeeper 的要求,增加了 violation 規則,用於傳回違規訊息。為了確保策略的可靠性,我們編寫了多個測試案例,涵蓋了 Pod、Deployment 和 CronJob 等不同資源型別,並驗證了錯誤訊息的正確性。在修復 CronJob 相關測試失敗後,我們將策略佈署到 GateKeeper。首先,建立 ConstraintTemplate 來儲存 Rego 程式碼,並定義了自定義資源。然後,建立 Constraint 物件,指定策略適用的資源和名稱空間。為了提升策略的靈活性,我們將策略修改為動態版本,允許根據不同名稱空間組態不同的允許登入檔。最後,我們討論瞭如何使用 OPA 的 trace 功能除錯 Rego 程式碼,並提供了一個流程圖,展示了 OPA 策略的執行流程。

使用 Rego 編寫策略

在學習 Rego 的過程中,推斷控制陳述式和明確控制陳述式之間的差異往往是最大的挑戰之一。雖然這種差異可能使得學習曲線比其他語言更陡峭,但 Rego 透過使測試和構建策略變得自動化和可管理來彌補了這一點。

開放策略代理(OPA)可用於自動化策略的測試。當編寫叢集安全所依賴的程式碼時,這一點非常重要。自動化測試將有助於加快開發速度,並透過捕捉因新程式碼而引入到先前工作程式碼中的任何錯誤來提高安全性。

開發 OPA 策略

使用 OPA 的一個常見示例是限制 Pod 可以來自哪些登入檔。這是叢集中的一種常見的安全措施,用於幫助限制可以在叢集上執行的 Pod。例如,我們曾多次提到比特幣挖礦者。如果叢集不接受來自您自己的內部登入檔以外的 Pod,那麼這是阻止惡意行為者濫用您的叢集的又一步。

首先,讓我們編寫我們的策略,該策略取自 OPA 檔案網站:

package k8sallowedregistries

invalidRegistry {
    input_images[image]
    not startswith(image, "quay.io/")
}

input_images[image] {
    image := input.review.object.spec.containers[_].image
}

input_images[image] {
    image := input.review.object.spec.template.spec.containers[_].image
}

內容解密:

  1. package k8sallowedregistries 聲明瞭我們的策略所在的包。在 OPA 中,所有資料和策略都儲存在包中。包在 OPA 中就像檔案系統上的目錄。
  2. invalidRegistry 規則定義了一個條件:如果 Pod 的映像來自 quay.io/,則該規則未定義;否則傳回 true,表示登入檔無效。GateKeeper 將其解釋為失敗,並在動態准入審查期間向 API 伺服器傳回 false。
  3. 兩個 input_images 規則提取 Pod 或 Deployment 物件中容器的映像值。

新增 GateKeeper 所需的違規規則

violation[{"msg": msg, "details": {}}] {
    invalidRegistry
    msg := "Invalid registry"
}

內容解密:

  1. invalidRegistry 為 true 時,violation 規則傳回一個包含錯誤訊息的物件,通知 API 伺服器評估失敗。

測試 OPA 策略

一旦我們編寫了策略,就需要設定自動化測試。與測試其他程式碼一樣,重要的是測試使用案例涵蓋預期和非預期的輸入,以及正面和負面的結果。

以下是八個測試使用案例:

package k8sallowedregistries

test_deployment_registry_allowed {
    not invalidRegistry with input as {"apiVersion": "..."}
}

test_deployment_registry_not_allowed {
    invalidRegistry with input as {"apiVersion": "..."}
}

test_pod_registry_allowed {
    not invalidRegistry with input as {"apiVersion": "..."}
}

test_pod_registry_not_allowed {
    invalidRegistry with input as {"apiVersion": "..."}
}

test_cronjob_registry_allowed {
    not invalidRegistry with input as {"apiVersion": "..."}
}

test_cronjob_registry_not_allowed {
    invalidRegistry with input as {"apiVersion": "..."}
}

test_error_message_not_allowed {
    control := {"msg": "Invalid registry", "details": {}}
    result = violation with input as {"apiVersion": "admissi..."}
    result[_] == control
}

test_error_message_allowed {
    result = violation with input as {"apiVersion": "admissi..."}
    control := {"msg": "Invalid registry", "details": {}}
}

內容解密:

  1. 八個測試使用案例涵蓋了三種型別的輸入(Pod、Deployment 和 CronJob)的正面和負面結果。
  2. 測試使用案例驗證了正確的錯誤訊息是否在適當的情況下傳回。

執行測試

執行測試後,我們發現 test_cronjob_registry_not_allowed 失敗了。原因是 CronJob 物件的結構與 Pod 和 Deployment 不同,我們的 input_images 規則沒有正確處理它。

修復問題

新增新的 input_images 規則以匹配 CronJob 物件中的容器映像:

input_images[image] {
    image := input.review.object.spec.jobTemplate.spec.template.spec.containers[_].image
}

現在,所有測試都透過了:

$ opa test .
PASS: 8/8

將策略整合到 GateKeeper 中

經過測試的策略現在可以整合到 GateKeeper 中,以加強叢集的安全性。

使用 Rego 編寫策略並佈署到 GateKeeper

佈署策略到 GateKeeper

我們建立的策略需要佈署到 GateKeeper,它提供了 Kubernetes 自定義資源來載入策略。首先,我們需要建立 ConstraintTemplate,這是儲存 Rego 策略程式碼的地方。這個物件讓我們能夠指定與策略執行相關的引數。為了簡化,我們先建立一個沒有引數的範本:

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sallowedregistries
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRegistries
        listKind: K8sAllowedRegistriesList
        plural: k8sallowedregistries
        singular: k8sallowedregistries
      validation: {}
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8sallowedregistries
      # Rego 程式碼內容

內容解密:

  • apiVersionkind 指定了 GateKeeper 的自定義資源型別為 ConstraintTemplate
  • metadata.name 定義了範本的名字。
  • spec.crd.spec.names 定義了自定義資源的名稱,包括單數、複數、種類別等。
  • spec.targets 指定了策略的目標,這裡是 Kubernetes 的准入控制。
  • rego 欄位包含了實際的 Rego 策略程式碼。

建立約束條件

建立範本後,下一步是根據範本建立約束條件。約束條件是根據 ConstraintTemplate 組態的 Kubernetes 物件。我們的範本定義了一個自定義資源定義(CRD),這會被新增到 constraints.gatekeeper.sh API 群組中。

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: restrict-openunison-registries
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    - apiGroups: ["apps"]
      kinds:
      - StatefulSet
      - Deployment
    - apiGroups: ["batch"]
      kinds:
      - CronJob
    namespaces: ["openunison"]
  parameters: {}

內容解密:

  • apiVersionkind 指定了約束條件的型別,根據 ConstraintTemplate 建立。
  • metadata.name 定義了約束條件的名稱。
  • spec.match 指定了約束條件適用的資源型別和名稱空間。
  • spec.parameters 在此為空,因為我們的範本目前不支援引數。

這個約束條件限制了策略僅適用於 openunison 名稱空間中的 Deployment、CronJob 和 Pod 物件。

建立動態策略

目前的登入檔策略是靜態且只支援單一登入檔。Rego 和 GateKeeper 都提供了建立動態策略的功能,使我們能夠根據個別名稱空間的需求進行組態。這樣,我們只需要維護一套程式碼函式庫。

新的 Rego 程式碼中,invalidRegistry 規則使用了 comprehension 來決定哪些映像來自允許的登入檔:

invalidRegistry {
  ok_images = [image | startswith(input_images[i], input.parameters.registries[_]); image = input_images[i]]
  count(ok_images) != count(input_images)
}

內容解密:

  • ok_images 陣列透過 comprehension 建立,包含所有來自允許登入檔的映像。
  • startswith 函式檢查映像是否來自允許的登入檔。
  • 如果 ok_images 的數量與輸入映像的數量不同,則表示有映像來自不允許的登入檔,觸發 invalidRegistry 規則。

這種動態策略使我們能夠靈活地為不同的名稱空間組態允許的登入檔,提高了策略的可重用性和可維護性。

使用 Open Policy Agent 擴充套件安全性

Rego 語言的強大之處

Rego 語言是一種專為 Open Policy Agent(OPA)設計的策略語言,其強大之處在於能夠以簡潔的方式表達複雜的策略邏輯。以檢查容器映像是否來自預先核准的註冊中心為例,Rego 能夠以單行程式碼實作原本需要多行 Java 程式碼才能完成的功能。

ok_images = [image | 
    startswith(input_images[i], registries[_]) ; 
    image = input_images[i]
]

內容解密:

  • ok_images 是一個陣列,包含了所有透過檢查的容器映像名稱。
  • input_images[i] 代表輸入的容器映像名稱陣列中的每個元素。
  • registries[_] 代表預先核准的註冊中心陣列中的每個元素。
  • startswith 函式用於檢查容器映像名稱是否以指定的註冊中心名稱開頭。
  • startswith 函式傳回 true 時,表示該容器映像來自預先核准的註冊中心,因此將其加入 ok_images 陣列中。

建立可重複使用的策略範本

為了使策略範本可重複使用,我們需要在 ConstraintTemplate 中加入 schema,以驗證輸入引數的有效性。

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sallowedregistries
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRegistries
      validation:
        openAPIV3Schema:
          properties:
            registries:
              type: array
              items: string

內容解密:

  • crd.spec.names.kind 指定了自定義資源的種類別名稱為 K8sAllowedRegistries
  • validation.openAPIV3Schema 定義了輸入引數的驗證規則,確保 registries 欄位是一個字串陣列。

建立策略例項

建立策略例項時,我們可以指定哪些註冊中心是有效的。

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: restrict-openunison-registries
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces: ["openunison"]
  parameters:
    registries: ["docker.io/tremolosecurity/"]

內容解密:

  • match.kinds 指定了要匹配的資源種類別,例如 Pod。
  • namespaces 指定了要限制的名稱空間,例如 openunison
  • parameters.registries 指定了有效的註冊中心,例如 docker.io/tremolosecurity/

除錯 Rego 程式碼

由於 Rego 程式碼無法直接進行逐步除錯,因此 OPA 提供了 trace 功能來幫助除錯。

invalidRegistry {
    trace(sprintf("input_images : %v",[input_images]))
    ok_images = [image |
        trace(sprintf("image %v",[input_images[j]]))
        startswith(input_images[j],input.parameters.registries[_])
        ;
        image = input_images[j]
    ]
    trace(sprintf("ok_images %v",[ok_images]))
    count(ok_images) != count(input_images)
}

內容解密:

  • trace 函式用於輸出除錯資訊。
  • sprintf 函式用於格式化輸出字串。
  • 當測試執行時,OPA 將輸出詳細的 trace 資訊,以幫助開發者除錯。

OPA 策略執行流程

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Rego 策略編寫與 GateKeeper 佈署

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

圖表翻譯: 此圖表展示了 OPA 策略執行流程。首先,建立 ConstraintTemplate,然後建立 K8sAllowedRegistries 策略例項,並指定有效的註冊中心。接著,OPA 評估策略,根據評估結果決定是否允許或拒絕容器映像。