gatekeeper で ValidatingAdmissionPolicy を扱う
Validating Admission Policy について
Kubernetes 1.30 で Validating Admission Policy が GA になりました。
Kubernetes にはリソース作成前にバリデーションをかける方法として Validating Admission Webhook を使う方法が存在していますが、Validating Admission Policy (以後 VAP) を使用することで、外部の webhook のサーバーへのアクセスや証明書の設定を行うことなく kube-apiserver 内部でバリデーションを完結することができます。
この辺りの利点は他の方が書かれた記事でも記載されていたのでこれ以上は割愛します。
私の所属するチームでは Validating Admission Policy を使用して、ユーザーに課したい Policy があったのでこの機能の検証を行っていました。
いざクラスタに適用しようと思った際にチームメンバーから「テストなどの諸々を考えるのであれば Gatekeeper の機能を使えないか試したい」という話を受けて、最終的にそちらを使用する形に落ち着いたので紹介します。
Gatekeeper の VAP Integration
Gatekeeper は v3.18 から VAP の CEL 記法に対応しています。
詳細は gatekeeper の用意してくれている demo を見るのが良いのですが、ConstraintTemplate の generateVAP(spec.targets[].code[].source.generateVAP
) を true に設定することによって validatinons(spec.targets[].code[].source.validations
) に書かれた CEL 式をもとにした VAP を作成することができます。
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
message:
type: string
labels:
type: array
items:
type: object
properties:
key:
type: string
allowedRegex:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: K8sNativeValidation
source:
generateVAP: true
validations:
- expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels))'
messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
- expression: '(has(variables.anyObject.metadata) && variables.params.labels.all(entry, has(variables.anyObject.metadata.labels) && entry.key in variables.anyObject.metadata.labels && string(variables.anyObject.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))'
message: "regex mismatch"
また VAP を適用する場合は Validating Admission Policy Binding (以後 VAPB) を作成する必要があります。
そのためには上記の ConstraintTemplates によって生成された CRD(K8sRequiredLabels
) である Constraint Resource で
enforcementAction(spec.enforcementAction
) を scoped にし scopedEnforcementActions(spec.scopedEnforcementActions
) に vap.k8s.io
を含める必要があります。
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: all-must-have-owner
spec:
enforcementAction: scoped
scopedEnforcementActions:
- action: deny
enforcementPoints:
- name: validation.gatekeeper.sh
- name: audit.gatekeeper.sh
- action: warn
enforcementPoints:
- name: vap.k8s.io
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
message: "All namespaces must have an `owner` label that points to your company username"
labels:
- key: owner
allowedRegex: "^[a-zA-Z]+.agilebank.demo$"
これで VAP と VAPB が作成されました。
gator を用いて VAP と VAPB のテストを行う
Gatekeeper では gator と呼ばれる CLI ツールが提供されており、実際のクラスタに適用せずとも CLI で動作を確認することが可能です。
gator でテストを行うためには Suite の CRD にそって Constraint と Template そして、それぞれのテストケースで使用する k8s のリソースの manifest を用意することで実行することができます。
今回は gatekeeper チームの用意してくれている demo に suite とテスト用の manifest を追加してみます。
まず gator を使用するためには Constraint の enforcementPoints(spec.scopedEnforcementActions[].enforcementPoints
) に - name: gator.gatekeeper.sh
を追加する必要があります、この記述がないと gator の対象として指定した Constraint が認識されません。
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: all-must-have-owner
spec:
enforcementAction: scoped
scopedEnforcementActions:
- action: deny
enforcementPoints:
- name: gator.gatekeeper.sh # gator を追加する
- name: validation.gatekeeper.sh
- name: audit.gatekeeper.sh
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
message: "All namespaces must have an `owner` label that points to your company username"
labels:
- key: owner
allowedRegex: "^[a-zA-Z]+.agilebank.demo$"
demo の VAP / VAPB は Namespace が特定の label を所持しているかを確認するものになっています。
error-ns では label が指定されていません。
apiVersion: test.gatekeeper.sh/v1alpha1
kind: Suite
metadata:
name: all-must-have-owner
tests:
- name: all-must-have-owner
template: k8srequiredlabels_template_usevap.yaml
constraint: owner_must_be_provided_usevap.yaml
cases:
- name: success-ns
object: testdata/success-ns.yaml
assertions:
- violations: 0
- name: error-ns
object: testdata/error-ns.yaml
assertions:
- violations: 2
apiVersion: v1
kind: Namespace
metadata:
name: error
apiVersion: v1
kind: Namespace
metadata:
name: success
labels:
"owner": "test.agilebank.demo"
そして以下のような gator のコマンドを実行すると定義した Suite にそって実行され、正しく定義されているかを確認することができます。
$ gator verify -v demo/scoped-enforcement-actions/suite.yaml
=== RUN all-must-have-owner
=== RUN allowed-ns
--- PASS: allowed-ns (0.007s)
=== RUN error-ns
--- PASS: error-ns (0.004s)
--- PASS: all-must-have-owner (0.026s)
ok demo/scoped-enforcement-actions/suite.yaml 0.026s
PASS
実際に CI などで実行したい場合は以下のような make コマンドを実行すると ${PWD}/target
に対して gator での test を実行することができます。
GATOR_VERSION ?= v3.18.2
GATOR_CMD ?= docker run -v ${PWD}/target:/target --rm ghcr.io/open-policy-agent/gator:$(GATOR_VERSION)
GATOR_TARGET ?= /target/...
vap-test:
$(GATOR_CMD) verify -v $(GATOR_TARGET)
まとめ
今回は VAP / VAPB を gatekeeper の機能を使って定義、作成し、gator という CLI でテストを行う方法を紹介しました。
ただ manifest を定義してクラスタに反映し、クラスタ上でテストを行っても良いですが、 gator を使用することでより簡潔にテストを行うことができ CI でも確認することができます。
ぜひ使ってみてください!
Discussion