Kubernetes 的安全性和合規性日益重要,OPA 提供了強大的策略引擎,能有效控制叢集資源的存取和操作。透過 AdmissionReview 物件,OPA 能夠評估 API 伺服器的請求,確保只有符合策略的請求才能被執行。本文將詳細介紹如何整合 OPA 與 Kubernetes,實作驗證和變更兩種准入控制,並探討如何編寫 Rego 策略、組態入口點以及使用自定義函式庫,以提升策略管理效率和靈活性。此外,也將介紹 Styra DAS 集中式管理方案,簡化 OPA 資源的管理和監控。文章最後,將探討 OPA 與 Kubernetes 整合的最佳實務、注意事項以及未來發展方向,協助讀者更深入理解 OPA 在 Kubernetes 環境中的應用價值。

OPA 驗證策略與 Kubernetes 整合實務

驗證策略的核心概念

OPA(Open Policy Agent)驗證策略是 Kubernetes 環境中的重要安全機制,用於控制和管理叢集資源的存取與操作。驗證策略透過 AdmissionReview 物件來評估 API 伺服器的請求,確保叢集的安全性和合規性。

策略匹配與評估流程

驗證策略首先需要匹配到特定的請求型別,例如建立或更新 Pod。匹配成功後,策略會進一步評估請求的有效性。以範例程式碼來說,策略會檢查容器映像是否來源於允許的登入檔:

deny[msg] {
    req_kind = "Pod"
    req_op in allowed_ops
    image = pod_containers[_].image
    not reg_matches_any(image, valid_registries)
    msg = sprintf("POD_INVALID: %q image is not sourced from an authorized registry. Valid registries are %q. Resource ID (ns/name/kind): %q", [image, allowed_regs, req_id])
}

內容解密:

  1. deny[msg] 定義了一個拒絕規則,當條件滿足時傳回錯誤訊息。
  2. req_kind = "Pod" 確保該策略只適用於 Pod 型別的資源。
  3. req_op in allowed_ops 檢查請求的操作是否為允許的操作(建立或更新)。
  4. image = pod_containers[_].image 取得所有容器映像的名稱。
  5. not reg_matches_any(image, valid_registries) 檢查映像是否來源於允許的登入檔。
  6. msg 構建了詳細的錯誤訊息,包括無效映像、允許的登入檔和資源 ID。

OPA 策略入口點的重要性

OPA 需要正確組態策略入口點才能作為 Kubernetes 的動態 Webhook 服務運作。缺少正確的入口點組態將導致叢集操作失敗,例如無法建立 Pod 或檢視日誌:

Error from server (InternalError): Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy)

主要入口點策略組態

正確的入口點策略必須生成符合 AdmissionReview 合約的回應:

main = {
    "apiVersion": "admission.k8s.io/v1",
    "kind": "AdmissionReview",
    "response": response,
}

response = {
    "allowed": false,
    "uid": uid,
    "status": {
        "reason": reason,
    },
}

內容解密:

  1. main 定義了 OPA 的主要入口點,傳回一個 AdmissionReview 物件。
  2. response 包含了評估結果,包括是否允許請求、請求 UID 和狀態原因。
  3. allowed 欄位指示請求是否被允許。
  4. uid 必須與請求中的 UID 相匹配。
  5. status 物件提供了驗證失敗的詳細原因。

策略評估的實際應用

當 OPA 評估一個無效的請求時,會傳回詳細的錯誤訊息給客戶端:

Error from server (POD_INVALID: "public.ecr.aws/eks-distro/kubernetes/pause:3.2" image is not sourced from an authorized registry. Valid registries are ["GOOD_REGISTRY", "VERY_GOOD_REGISTRY"]. Resource ID (ns/name/kind): "opa-test/test-pod/Pod"): error when creating "tests/99-test-pod.yaml"

圖表說明:OPA 驗證流程

  graph LR
    A[Kubernetes API 請求] --> B[OPA Webhook]
    B --> C{匹配驗證策略}
    C -->|匹配成功| D[評估請求有效性]
    C -->|匹配失敗| E[放行請求]
    D -->|驗證透過| F[允許請求]
    D -->|驗證失敗| G[傳回錯誤訊息]

圖表翻譯: 此圖示展示了 OPA 在 Kubernetes 環境中的驗證流程:

  1. Kubernetes API 請求首先到達 OPA Webhook。
  2. OPA 嘗試匹配適當的驗證策略。
  3. 如果匹配成功,則進行請求有效性評估。
  4. 根據評估結果決定是否允許或拒絕請求。

最佳實踐與注意事項

  1. 策略設計

    • 確保策略邏輯清晰且易於維護
    • 使用 Rego 語言的進階特性提高策略效率
  2. 效能最佳化

    • 最小化策略匹配範圍
    • 使用快取機制提高評估效能
  3. 安全性考量

    • 定期更新和審查策略
    • 監控 OPA 日誌以發現潛在問題
  4. 更智慧的策略管理

    • 結合機器學習技術實作動態策略調整
    • 自動化策略最佳化
  5. 跨叢集策略統一管理

    • 研究多叢集環境下的 OPA 佈署方案
    • 統一管理不同環境下的安全策略
  6. 與 DevOps 流程整合

    • 將 OPA 納入 CI/CD 管道
    • 實作自動化的策略測試和驗證

透過持續最佳化和創新,OPA 將在未來的雲原生安全領域扮演更加關鍵的角色。

使用Open Policy Agent(OPA)實作Kubernetes的准入控制

前言

在現代的雲原生環境中,Kubernetes已經成為容器協調的標準。然而,隨著叢集規模的擴大和複雜度的增加,如何有效地管理和控制叢集中的資源變得越來越重要。Open Policy Agent(OPA)是一種開源的通用策略引擎,可以與Kubernetes整合,實作對叢集資源的准入控制。本文將探討如何使用OPA實作Kubernetes的准入控制,包括驗證和變更准入控制。

OPA與Kubernetes的整合

OPA可以透過Kubernetes的准入控制器整合到叢集中。准入控制器是一種用於攔截和處理Kubernetes API請求的元件。OPA可以作為一個驗證或變更准入Webhook,攔截和處理建立、更新和刪除等請求。

安裝OPA

首先,需要在Kubernetes叢集中安裝OPA。可以使用Helm chart或YAML檔案進行安裝。以下是使用YAML檔案安裝OPA的範例:

apiVersion: v1
kind: Namespace
metadata:
  name: opa
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: opa
  namespace: opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opa
  template:
    metadata:
      labels:
        app: opa
    spec:
      containers:
      - name: opa
        image: openpolicyagent/opa:latest
        args:
        - "run"
        - "--server"
        - "--log-level=debug"
        - "--config-file=/etc/opa/config.yaml"
        volumeMounts:
        - name: config
          mountPath: /etc/opa
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: opa-config

組態OPA

安裝完成後,需要組態OPA以使其能夠與Kubernetes整合。需要建立一個ConfigMap來組態OPA:

apiVersion: v1
kind: ConfigMap
metadata:
  name: opa-config
  namespace: opa
data:
  config.yaml: |
    services:
      kube:
        url: https://kubernetes.default.svc:443
    labels:
      app: opa
    plugins:
      kube-mgmt:
        enabled: true

驗證准入控制

驗證准入控制是一種用於檢查Kubernetes資源是否符合特定策略的機制。OPA可以使用Rego語言編寫策略,並將其載入到叢集中。

編寫Rego策略

以下是一個簡單的Rego策略,用於檢查Deployment資源是否來自授權的倉函式庫:

package kubernetes.admission

import data.lib.k8s.helpers as helpers

deny[msg] {
  helpers.request_kind == "Deployment"
  helpers.allowed_operations[helpers.request_operation]
  image = helpers.deployment_containers[_].image
  not reg_matches_any(image, valid_deployment_registries_v2)
  msg = sprintf("%q: %q image is not sourced from an authorized registry. Resource ID (ns/name/kind): %q", [helpers.deployment_error, image, helpers.request_id])
}

valid_deployment_registries_v2 = {registry |
  allowed = "GOOD_REGISTRY"
  registries = split(allowed, ",")
  registry = registries[_]
}

reg_matches_any(str, patterns) {
  reg_matches(str, patterns[_])
}

reg_matches(str, pattern) {
  contains(str, pattern)
}

載入Rego策略

需要將Rego策略載入到OPA中,可以使用ConfigMap來實作:

apiVersion: v1
kind: ConfigMap
metadata:
  name: deployment-registry-allowed
  namespace: opa
data:
  main: |
    package kubernetes.admission
    
    # Rego policy content here...

自定義Helper函式庫

為了簡化Rego策略的編寫,可以建立自定義的Helper函式庫。以下是一個範例:

package lib.k8s.helpers

allowed_operations = allowed_ops {
  allowed_ops := {"CREATE", "UPDATE"}
}

request_operation = op {
  op := input.request.operation
}

request_metadata_labels = labels {
  labels := input.request.object.metadata.labels
}

# Other helper functions...

使用Helper函式庫

可以在Rego策略中匯入Helper函式庫,以簡化程式碼:

import data.lib.k8s.helpers as helpers

deny[msg] {
  helpers.request_kind == "Deployment"
  # Use helper functions here...
}

變更准入控制

變更准入控制是一種用於修改Kubernetes資源的機制。OPA可以組態為變更准入Webhook,以修改資源。

組態變更准入控制

需要建立一個MutatingWebhookConfiguration資源,以組態OPA為變更准入Webhook:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: opa-mutating-webhook
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    service:
      name: opa
      namespace: opa
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: mutating-webhook.openpolicyagent.org
  namespaceSelector:
    matchExpressions:
    - key: openpolicyagent.org/webhook
      operator: NotIn
      values:
      - ignore
  rules:
  - apiGroups:
    - '*'
    apiVersions:
    - '*'
    operations:
    - CREATE
    - UPDATE
    resources:
    - pods
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10
圖表翻譯:

此圖示展示了OPA與Kubernetes整合的架構圖。

  graph LR;
    A[Kubernetes API] -->|請求|> B[OPA];
    B -->|驗證或變更|> C[Kubernetes 資源];
    C -->|結果|> B;
    B -->|回應|> A;

圖表翻譯: 此圖表呈現了 Kubernetes API 請求經過 OPA 處理後傳回結果給 Kubernetes 資源並最終回應給 Kubernetes API 的流程。整個過程中,OPA 負責驗證或變更請求,最終由 Kubernetes 資源處理並傳回結果給 OPA,再由 OPA 回應給 Kubernetes API。

隨著雲原生技術的不斷發展,Kubernetes和OPA的整合將會變得越來越重要。未來,我們可以期待看到更多根據OPA的創新應用,例如更複雜的策略管理和更強大的准入控制功能。同時,隨著社群的不斷貢獻和改進,OPA的功能和效能也將會不斷提升。

組態規則以處理 Kubernetes 資源型別

再次強調,准入 Webhook 只會在 CREATE 或 UPDATE 操作期間影響 Pods,並且只會在未標記 openpolicyagent.org/webhook=ignore 的 Namespaces 中生效。對傳入資源(包含在 API 伺服器請求中)的變更是使用 RFC 6902 中定義的 JSON patch 架構進行的。

正如我在本文前面提到的,OPA 社群非常龐大,GitHub 上的 OPA 組織至少有 22 個倉函式庫。為了展示變更准入的工作原理,我使用了舊的 GitHub 專案 OPA Library 中的一部分。我透過 ConfigMap 安裝了 kubernetes/mutating-admission/main.rego 政策,以建立入口點政策,該政策建立了 Kubernetes AdmissionReview.response 物件的合約欄位。由於 Rego 套件不是系統套件,我決定更改 OPA 伺服器的 default_decision 引數,如下所示;另一個選擇是更改套件到系統:

# OPA 准入控制器伺服器引數
args:
  - "run"
  - "--server"
  - "--tls-cert-file=/certs/tls.crt"
  - "--tls-private-key-file=/certs/tls.key"
  - "--addr=0.0.0.0:8443"
  - "--addr=http://127.0.0.1:8181"
  # - "--set=bundles.default.resource=bundle.tar.gz"
  - "--log-format=json"
  - "--set=status.console=true"
  - "--set=decision_logs.console=true"
  - "--set=default_decision=/library/kubernetes/admission/mutating/main"

內容解密:

此組態更改了 OPA 伺服器的預設決策路徑,以匹配入口點政策的位置。這使得 OPA 能夠正確處理變更准入請求。

另一個匹配選項是不修改 OPA 伺服器的 default_decision 引數,而是向 Webhook 組態新增 path 欄位,如下所示:

# 變更 Webhook 服務組態
service:
  namespace: opa
  name: opa
  path: /v0/data/library/kubernetes/admission/mutating/main
  port: 443

內容解密:

此組態透過指定正確的決策路徑,使 Kubernetes API 伺服器能夠正確地將准入請求傳送到 OPA。

在第 2 章中,我向您介紹了 OPA REST API 的 /v1/... 端點。當使用 post 查詢時,v1 API 端點需要一個定義的輸入 JSON 檔案,如下所示:

// 示例輸入檔案
{
  "input": {
    "message": "world"
  }
}

當 Kubernetes API 伺服器將 AdmissionReview 檔案釋出到 OPA 以進行變更或驗證決策時,它不使用輸入檔案結構。v0 API 端點不需要這種結構;它處理原始輸入,隨後的規則處理 POST 請求。

由於 OPA 的預設決策路徑是 /system/main,我需要更新入口點套件和主規則以匹配預設的 OPA 決策路徑,或者更新 default_decision 引數以匹配入口點套件和主規則,或者更新 Webhook service.path 欄位以匹配入口點套件和主規則。

如果沒有這個匹配,我將在 OPA 決策日誌中看到以下錯誤:

# 主入口點政策不匹配錯誤
{
  "decision_id": "1739aacc-48e3-4346-a19a-6ba479dd6b26",
  "error": {
    "code": "undefined_document",
    "message": "document missing: data.system.main"
  },
  ...
}

內容解密:

此錯誤表示 OPA 無法找到與預設決策路徑匹配的政策。需要更新組態以確保正確的匹配。

接下來,我使用了上述 OPA Library 專案中的政策模式來修補 Pods,如果標籤不存在。我的政策使用了入口點 Rego 中包含的多個輔助函式。為了使用它們,我在與入口點 Rego 同一個套件中定義了我的政策 Rego:

# Configmap with mutating policy
kind: ConfigMap
apiVersion: v1
metadata:
  name: label-pods
  namespace: opa
  labels:
    openpolicyagent.org/policy: rego
data:
  main: |
    package system
    
    ############################################################
    # PATCH rules
    # Note: All patch rules should start with `isValidRequest` and
    # `isCreateOrUpdate`
    
    ############################################################
    # add billing,env,owner labels to pods
    patch[patchCode] {
      isValidRequest
      isCreateOrUpdate
      input.request.kind.kind == "Pod"
      not hasLabelValue(input.request.object, "billing", "lob-cc")
      patchCode = makeLabelPatch("add", "billing", "lob-cc", "")
    }
    
    patch[patchCode] {
      isValidRequest
      isCreateOrUpdate
      input.request.kind.kind == "Pod"
      not hasLabelValue(input.request.object, "env", "dev")
      patchCode = makeLabelPatch("add", "env", "dev", "")
    }
    
    patch[patchCode] {
      isValidRequest
      isCreateOrUpdate
      input.request.kind.kind == "Pod"
      not hasLabelValue(input.request.object, "owner", "jimmy")
      patchCode = makeLabelPatch("add", "owner", "jimmy", "")
    }

內容解密:

此 Rego 程式碼定義了三個修補規則,用於為 Pods 新增 billingenvowner 標籤,如果這些標籤不存在。

變更過程使用入口點 Rego 中的函式來建立一個扁平化的修補陣列,將陣列編碼為 JSON,然後將修補內容 Base64 編碼,以便傳送回 Kubernetes API 伺服器:

# marshaling JSON from patch array, then base64 encoding
x := {
  "allowed": true,
  "uid": response_uid,
  "patchType": "JSONPatch",
  "patch": base64.encode(json.marshal(fullPatches)),
}

內容解密:

此程式碼將修補陣列編碼為 JSON,然後進行 Base64 編碼,以便在 AdmissionReview 回應中傳回 Kubernetes API 伺服器。

如果我們從 OPA 取得決策日誌,並解碼 Base64 編碼的字串,我們可以看到應用於 Pod 請求的所有三個修補:

// AdmissionReview 回應傳回到 API 伺服器
..."result": {
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "allowed": true,
    "patch": "W3sib...fV0=",
    "patchType": "JSONPatch",
    "uid": "32c8e37c-8f01-4787-bf47-58e70d07fed1"
  }
},...
# base64 decoded JSON patches
$ echo "W3si...fV0=" | base64 -d
[
  {"op":"add","path":"/metadata/labels","value":{}},
  {"op":"add","path":"/metadata/labels/billing","value":"lob-cc"},
  {"op":"add","path":"/metadata/labels/env","value":"dev"},
  {"op":"add","path":"/metadata/labels/owner","value":"jimmy"}
]

內容解密:

此輸出顯示了應用於 Pod 請求的三個 JSON 修補物件。

既然我們已經探討瞭如何使用 OPA 的驗證和變更 Webhook,讓我們來看看如何集中管理 OPA 資源。

使用 Styra DAS 的集中式 OPA 管理

在第 3 章中,我們探討了使用 Styra DAS 中央管理 OPA Agents 和政策包。事實上,使用 OPA(經典版)進行 Kubernetes 的驗證和變更 Webhook,使得您能夠使用 OPA 管理功能,例如包和決策日誌。

要在本地 minikube 環境中使用 Styra DAS,我在 Styra DAS(免費版)工作區中建立了一個系統。然後,我使用組態的 Styra 系統中的安裝命令,在本地叢集中建立 Styra 資源。

  graph LR;
    A[建立 Styra DAS 系統] --> B[生成安裝命令];
    B --> C[在本地叢集中執行安裝命令];
    C --> D[建立 Styra 資源];

圖表翻譯: 此圖表顯示了在本地 minikube 環境中使用 Styra DAS 的步驟。首先,在 Styra DAS 工作區中建立一個系統,然後生成安裝命令,最後在本地叢集中執行這些命令以建立 Styra 資源。

圖表說明:

此圖表清晰地展示了使用 Styra DAS 的集中式 OPA 管理流程,從建立系統到在本地叢集中佈署資源的整個過程。這種方法簡化了 OPA 資源的管理,並提供了集中式的控制和監控能力。