Kubernetes 的多租戶環境下,資源隔離和安全策略至關重要。OPA/Gatekeeper 提供了強大的策略引擎,能有效管理和實施這些策略。本文實踐了根據節點汙點和親和性的租戶隔離策略,並使用 Mutating Webhook 自動修改 Pod 組態,確保 Pod 佈署到指定的節點。同時,Validating Webhook 則用於驗證組態的正確性,防止不合規的 Pod 資源佈署。此外,文章還探討了 Rego 策略語言的應用,如何編寫 Rego 策略來定義和驗證各種約束條件。最後,文章也詳細介紹了 Gatekeeper 的稽核模式,用於檢查現有資源的合規性,以及外部資料提供者的組態和使用,讓策略引擎能整合外部資料來源,實作更複雜的策略控制。
Kubernetes 中的 OPA/Gatekeeper 策略管理與實踐
在 Kubernetes 環境中,OPA(Open Policy Agent)與 Gatekeeper 的結合為叢集管理提供了強大的策略控制能力。本文將探討如何在 Kubernetes 中使用 OPA/Gatekeeper 進行策略管理,特別是在多租戶隔離的場景下。
節點汙點(Taints)與節點親和性(Node Affinity)
首先,我們需要了解節點汙點的概念。節點汙點是一種用於標記節點的機制,可以確保只有具備相應容忍度(Toleration)的 Pod 才能被排程到這些節點上。在我們的範例中,我們首先檢查了某些節點的汙點組態:
$ kubectl get nodes ip-10-0-10-77.us-east-2.compute.internal -ojsonpath='{.spec.taints}'
[{"effect":"NoSchedule","key":"tenant","value":"tenant1"}]
這些節點被標記為具有 tenant=tenant1
的汙點,這意味著只有具備對應容忍度的 Pod 才能被排程到這些節點上。
使用 Mutating Webhook 修改 Pod 組態
為了確保特定名稱空間中的 Pod 被排程到正確的節點上,我們使用了 Mutating Webhook 來自動修改 Pod 的組態。具體來說,我們新增了兩個 Mutating 組態:
- 為特定名稱空間中的 Pod 新增節點親和性組態
- 為這些 Pod 新增對應的容忍度
# Adds a node affinity to all Pods in a specific Namespace
...spec:
applyTo:
- groups: [""]
kinds: ["Pod"]
versions: ["v1"]
match:
namespaces: ["tenant1"]
location: >
"spec.affinity.nodeAffinity."
"requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms"
parameters:
assign:
value:
- matchExpressions:
- key: "tenant"
operator: In
values:
- "tenant1"
內容解密:
此段 YAML 組態定義了一個 Mutating Webhook,用於為 tenant1
名稱空間中的所有 Pod 新增節點親和性組態。具體來說,它設定了 requiredDuringSchedulingIgnoredDuringExecution
規則,確保 Pod 只能被排程到標有 tenant=tenant1
的節點上。
使用 Validating Webhook 驗證 Pod 組態
除了修改 Pod 組態外,我們還使用了 Validating Webhook 來驗證這些修改是否正確應用。我們定義了兩個驗證策略:
- 驗證節點親和性是否正確設定
- 驗證容忍度是否正確新增
# ConstraintTemplate Rego to validate node affinity
package k8srequirednodeaffinity
import data.lib.k8s.helpers as helpers
violation[{"msg": msg, "details": {"missing":missing}}] {
helpers.review_operation == input.parameters.ops[_]
provided := {x | x := input.review.object.spec.affinity.nodeAffinity}
required := {x | x := input.parameters.nodeAffinity}
missing := required - provided
count(missing) > 0
msg := sprintf("%v: Resource missing correct node affinity. Provided node affinity: %v, Required node affinity: %v. Resource ID (ns/name/kind): %v",
[input.parameters.errMsg,provided,required,helpers.review_id])
}
內容解密:
此 Rego 策略用於驗證 Pod 是否具備正確的節點親和性組態。它比較了實際提供的組態與所需的組態,並在發現不符時產生違規訊息。
測試與驗證
為了驗證我們的組態是否正確,我們進行了正向測試和負向測試:
- 在
tenant1
名稱空間中建立 Pod,並驗證其是否具備正確的容忍度和節點親和性組態。 - 在
default
名稱空間中建立 Pod,並驗證其是否未被應用不正確的組態。 - 在
default
名稱空間中建立帶有特定容忍度的 Pod,並驗證是否被正確拒絕。
$ kubectl -n default apply -f test/70-test-pod.yaml
Error from server (Forbidden): error when creating "test/70-test-pod.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [toleration-ns-check-pod] default namespace cannot use tolerations: [{"effect": "NoSchedule", "key": "tenant", "operator": "Equal", "value": "tenant1"}]
隨著 Kubernetes 環境的日益複雜,OPA/Gatekeeper 的應用將變得越來越重要。未來,我們可以期待看到更多根據 OPA/Gatekeeper 的創新策略管理方案,例如:
- 更細粒度的策略控制
- 自動化的策略驗證和修正
- 跨叢集的統一策略管理
這些發展將進一步鞏固 OPA/Gatekeeper 在 Kubernetes 生態系統中的重要地位。
Kubernetes 中的 OPA/Gatekeeper 工作流程
graph LR A[Kubernetes API Server] -->|請求|> B[OPA/Gatekeeper Admission Controller] B -->|驗證/修改請求|> C{策略檢查} C -->|透過|> D[允許請求] C -->|失敗|> E[拒絕請求] D --> F[資源建立/更新] E --> G[傳回錯誤訊息]
圖表翻譯: 此圖表展示了 Kubernetes 中的 OPA/Gatekeeper 工作流程。當使用者透過 Kubernetes API Server 發起請求時,OPA/Gatekeeper 的 Admission Controller 會攔截並驗證/修改該請求。根據策略檢查的結果,請求要麼被允許並繼續執行,要麼被拒絕並傳回錯誤訊息給使用者。
圖表內容解密:
- Kubernetes API Server接收使用者請求。
- OPA/Gatekeeper Admission Controller攔截請求。
- 策略檢查決定請求是否合規。
- 合規的請求被允許並繼續執行。
- 不合規的請求被拒絕並傳回錯誤訊息。
這個流程確保了 Kubernetes 叢集中的資源操作符合預定的策略要求,從而提高了叢集的安全性和可控性。
Gatekeeper 稽核模式與外部資料提供者
稽核模式
在 Kubernetes 中實施 Policy-as-Code (PaC) 時,需要考慮某些資源可能不會在 webhook 超時期間被評估,或者在政策實施後才被建立。Gatekeeper 的稽核功能旨在評估現有資源組態是否符合約束條件。
稽核功能組態
要啟用稽核功能,需要組態 Gatekeeper 的設定。以下是一個範例組態,其中非預設設定以粗體顯示:
# Gatekeeper 設定(片段)
- args:
- --audit-interval=60
- --log-level=INFO
- --constraint-violations-limit=20
- --audit-from-cache=false
- --audit-chunk-size=500
- --audit-match-kind-only=false
- --emit-audit-events=true
- --operation=audit
- --operation=status
- --operation=mutation-status
- --logtostderr
- --health-addr=:9090
- --prometheus-port=8888
- --enable-external-data=false
- --enable-generator-resource-expansion=false
- **--metrics-backend=prometheus**
- --disable-cert-rotation=true
稽核流程
當稽核控制器啟動時,如果沒有組態任何約束條件,則會每隔 60 秒輸出以下日誌記錄:
// Gatekeeper 日誌記錄(無約束條件時)
{"level":"info","ts":1672433592.7277467,"logger":"controller","msg":"no constraint is found with apiversion","process":"audit","audit_id":"2022-12-30T20:53:12Z","constraint apiversion":"constraints.gatekeeper.sh/v1beta1"}
{"level":"info","ts":1672433592.7278154,"logger":"controller","msg":"auditing is complete","process":"audit","audit_id":"2022-12-30T20:53:12Z","event_type":"audit_finished"}
一旦增加了 Gatekeeper 政策(ConstraintTemplate 和 Constraint),稽核流程就會使用該政策來檢測違規情況,如以下 JSON 日誌記錄所示:
// 稽核政策日誌記錄
{"level":"info","ts":1672434373.4897652,"logger":"controller","msg":"constraint status update","process":"audit","audit_id":"2022-12-30T21:06:12Z","object":{"apiVersion":"constraints.gatekeeper.sh/v1beta1","kind":"K8sPSPSeccomp","name":"psp-seccomp"}}
{"level":"info","ts":1672434373.4930406,"logger":"controller","msg":"handling constraint update","process":"constraint_controller","instance":{"apiVersion":"constraints.gatekeeper.sh/v1beta1","kind":"K8sPSPSeccomp","name":"psp-seccomp"}}
{"level":"info","ts":1672434373.4934077,"logger":"controller","msg":"updated constraint status violations","process":"audit","audit_id":"2022-12-30T21:06:12Z","constraintName":"psp-seccomp","count":3}
稽核結果
稽核結果可以透過以下命令檢視:
# 取得違規狀態
$ kubectl get k8spspseccomp -ojsonpath='{.items[].status}'
..."totalViolations":3,"violations":[{"enforcementAction":"deny","group":"","kind":"Pod","message":"Seccomp profile 'not configured' is not allowed for container 'coredns'. Found at: no explicit profile found. Allowed profiles: {\"RuntimeDefault\",\"docker/default\"}...
稽核限制
需要注意的是,並非所有約束條件都可以使用稽核流程。某些欄位(如 review.userInfo
、review.operation
和 review.uid
)無法被稽核流程填入,因此不應依賴這些欄位來檢測稽核違規。
外部資料提供者
Gatekeeper 的外部資料提供者功能允許編寫 Rego 程式碼,以呼叫 Gatekeeper 外部的服務來檢索資料以進行政策決策。這在政策所需的資料不是請求的 AdmissionReview 物件或現有的 Kubernetes 物件的一部分時非常有用。
外部資料提供者組態
外部資料提供者可以透過以下 YAML 組態:
# 外部資料提供者組態
apiVersion: externaldata.gatekeeper.sh/v1beta1
kind: Provider
metadata:
labels:
app: answers
billing: lob-cc
env: dev
owner: jimmy
name: answers-provider
spec:
caBundle: LS0t...
timeout: 1
url: https://answers.answers.svc/provide
使用外部資料提供者
透過使用外部資料提供者,Gatekeeper 可以擴充套件其政策評估能力,以包含外部資料來源。這使得 Gatekeeper 可以根據更廣泛的資料進行政策決策。
圖表翻譯:Gatekeeper 稽核流程圖示
graph LR; A[稽核控制器啟動] --> B[檢查約束條件]; B -->|無約束條件|> C[輸出日誌記錄]; B -->|有約束條件|> D[評估資源組態]; D --> E[檢測違規情況]; E --> F[輸出稽核結果];
圖表翻譯: 此圖示呈現了 Gatekeeper 的稽核流程。首先,稽核控制器啟動並檢查是否存在約束條件。如果沒有約束條件,則輸出日誌記錄。如果存在約束條件,則評估資源組態並檢測違規情況,最終輸出稽核結果。
隨著 Kubernetes 和 Gatekeeper 的不斷發展,未來可能會出現更多強大的政策管理功能。這些功能將進一步增強 Gatekeeper 的能力和靈活性,使其成為 Kubernetes 環境中不可或缺的工具。
外部資料提供者組態與實作詳解
外部資料提供者組態概述
外部資料提供者(External Data Provider)是用於與外部資料來源服務進行互動的組態。主要透過 spec.caBundle
和 spec.url
進行設定:
spec.caBundle
:用於與外部資料來源服務進行 TLS 通訊的憑證授權單位(CA)憑證。spec.url
:指向外部資料來源服務的 URL。
在給定的組態範例中,answers-provider
指向位於 answers
名稱空間中的 Answers 服務。Gatekeeper 使用 caBundle
欄位中的憑證與該服務進行安全通訊,該服務使用自簽名的 TLS 金鑰和憑證。
TLS 組態與指令碼實作
為了組態 TLS 連線,下面的 shell 指令碼展示瞭如何建立所需的憑證和金鑰,並將其應用於 Kubernetes 環境中。
TLS 組態指令碼
#!/usr/bin/env bash
# 錯誤處理
set -e
trap 'catch $? $LINENO' ERR
catch() {
if [ "$1" != "0" ]; then
echo "Error $1 occurred on $2"
fi
}
OWNER="jimmy"
ENV="dev"
BILLING="lob-cc"
KUBECTL="kubectl"
CA_BUNDLE=""
CONFIGS_DIRECTORY="generated/configs"
SECRETS_DIRECTORY="generated/secrets"
TEMPLATES_DIRECTORY="templates"
# 建立必要的目錄並清理舊檔案
if [ ! -d "$TEMPLATES_DIRECTORY" ]; then
echo "$TEMPLATES_DIRECTORY not found, install aborted"
exit 99
fi
mkdir -p $CONFIGS_DIRECTORY
mkdir -p $SECRETS_DIRECTORY
rm -f $CONFIGS_DIRECTORY/*
rm -f $SECRETS_DIRECTORY/*
# 生成 CA 憑證和金鑰
openssl genrsa -out $SECRETS_DIRECTORY/answers-ca.key 2048
openssl req -x509 -new -nodes -sha256 -key $SECRETS_DIRECTORY/answers-ca.key \
-days 365 -out $SECRETS_DIRECTORY/answers-ca.crt -subj /CN=admission_ca 2>&1
# 生成伺服器憑證和金鑰
openssl genrsa -out $SECRETS_DIRECTORY/answers-server.key 2048
openssl req -new -key $SECRETS_DIRECTORY/answers-server.key -sha256 -out \
$SECRETS_DIRECTORY/answers-server.csr -subj /CN=answers.answers.svc -config \
$TEMPLATES_DIRECTORY/server.conf 2>&1
openssl x509 -req -in $SECRETS_DIRECTORY/answers-server.csr -sha256 -CA \
$SECRETS_DIRECTORY/answers-ca.crt -CAkey $SECRETS_DIRECTORY/answers-ca.key \
-CAcreateserial -out $SECRETS_DIRECTORY/answers-server.crt -days 100000 \
-extensions v3_ext -extfile $TEMPLATES_DIRECTORY/server.conf
# 建立並應用 Kubernetes 資源組態
cat $TEMPLATES_DIRECTORY/ns-template.yaml | sed -e \
"s/__OWNER_VALUE__/${OWNER}/g" | sed -e "s/__ENV_VALUE__/${ENV}/g" | \
sed -e "s/__BILLING_VALUE__/${BILLING}/g" > "$CONFIGS_DIRECTORY/ns.yaml"
${KUBECTL} apply -f $CONFIGS_DIRECTORY/ns.yaml
# 建立 TLS secret
${KUBECTL} -n answers delete secret answers-server --ignore-not-found 2>&1
${KUBECTL} -n answers create secret tls answers-server \
--cert=$SECRETS_DIRECTORY/answers-server.crt \
--key=$SECRETS_DIRECTORY/answers-server.key
# 建立 Answers 應用組態並應用
cat $TEMPLATES_DIRECTORY/answers-app-template.yaml | sed -e \
"s/__OWNER_VALUE__/${OWNER}/g" | sed -e "s/__ENV_VALUE__/${ENV}/g" | \
sed -e "s/__BILLING_VALUE__/${BILLING}/g" > \
"$CONFIGS_DIRECTORY/answers-app.yaml"
${KUBECTL} apply -f $CONFIGS_DIRECTORY/answers-app.yaml
# 建立外部資料提供者組態並應用
CA_BUNDLE="$(base64 -i $SECRETS_DIRECTORY/answers-ca.crt)"
cat $TEMPLATES_DIRECTORY/provider-template.yaml | sed -e \
"s/__OWNER_VALUE__/${OWNER}/g" | sed -e "s/__ENV_VALUE__/${ENV}/g" | \
sed -e "s/__BILLING_VALUE__/${BILLING}/g" | sed -e \
"s/__CA_BUNDLE_VALUE__/${CA_BUNDLE}/g" > \
"$CONFIGS_DIRECTORY/answers-provider.yaml"
${KUBECTL} apply -f $CONFIGS_DIRECTORY/answers-provider.yaml
#### 內容解密:
此指令碼首先進行錯誤處理設定,接著定義相關變數。然後,它會檢查範本目錄是否存在,並建立必要的組態和金鑰目錄。指令碼接著生成 CA 和伺服器的憑證與金鑰,並建立 Kubernetes 的名稱空間、TLS secret 和 Answers 應用。最後,它建立並應用外部資料提供者的組態。
外部資料提供者元件架構
下圖展示了 Gatekeeper 外部資料提供者解決方案的元件架構:
graph LR; A[Kubernetes API Server] -->|Request|> B[Gatekeeper]; B -->|Forward Request|> C[Answers External Data Provider]; C -->|Response|> B; B -->|Decision|> A; A -->|Response|> D[Kubernetes Client];
圖表翻譯: 此圖展示了 Gatekeeper 如何接收來自 Kubernetes API Server 的請求,並將其轉發給 Answers 外部資料提供者。Answers 提供者回應後,Gatekeeper 根據回應做出決策,並將結果傳回給 API Server,最終傳回給 Kubernetes 使用者端。
外部資料提供者請求與回應範例
請求負載範例如下:
{
"apiVersion": "externaldata.gatekeeper.sh/v1beta1",
"kind": "ProviderRequest",
"request": {
"keys": [
"gcr.io/google-containers/pause:3.2",
"gcr.io/google-containers/pause:3.3"
]
}
}
使用 curl
命令傳送請求:
$ curl -vX POST http://localhost:8080/provide \
-d @request.json \
--header "Content-Type: application/json"
回應負載範例如下:
{
"apiVersion": "externaldata.gatekeeper.sh/v1beta1",
"kind": "ProviderResponse",
"response": {
"idempotent": true,
"items": [
{
"key": "gcr.io/google-containers/pause:3.2",
"value": "Outlook not so good.:N",
"error": "Outlook not so good.:N:DENIED"
},
{
"key": "gcr.io/google-containers/pause:3.3",
"value": "Outlook not so good.:N",
"error": "Outlook not so good.:N:DENIED"
}
]
}
}
#### 內容解密:
此範例展示瞭如何使用 JSON 格式的請求負載向外部資料提供者傳送請求,並接收 JSON 格式的回應。請求中包含了需要查詢的映像檔名稱,回應中則包含了對應的檢查結果。
ConstraintTemplate 組態範例
以下是一個參考 Gatekeeper 專案的 ConstraintTemplate 範例,該範例參照了 answers-provider
外部資料提供者:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sanswerverification
annotations:
description: >-
Calls external data provider answers app.
spec:
crd:
spec:
names:
kind: K8sAnswerVerification
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sanswerverification
violation[{"msg": msg}] {
# 建立包含映像檔的鍵列表
images := [img | img = input.review.object.spec.containers[_].image]
# 傳送外部資料請求
response := external_data({"provider": "answers-provider", "keys": images})
response_with_error(response)
msg := sprintf("invalid response: %v", [response])
}
response_with_error(response) {
count(response.errors) > 0
errs := response.errors[_]
contains(errs[1],"DENIED")
}
#### 內容解密:
此 ConstraintTemplate 名為 k8sanswerverification
,用於呼叫 answers-provider
外部資料提供者。它定義了一個違規規則,當外部資料提供者回應中包含錯誤或被拒絕時,會觸發違規並顯示相應的錯誤訊息。Rego 程式碼邏輯首先從輸入中提取容器映像檔名稱,然後傳送外部資料請求,最後檢查回應中是否包含錯誤或拒絕訊息。