OPA Gatekeeperとは
この記事に辿ってこれた人ならあえて説明は不要だと思いますので省略で。Kubernetesにポリシーを強制できます。
認識齟齬無いように、リンクはこれです。 github.com
作業環境
- EKS v18
- kubectl v1.19.2
- Helm v3.4.0
- gatekeeper-3.2.1
インストール
Helmを使えば簡単にインストール可能です。
$ helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
$ helm install gatekeeper/gatekeeper --generate-name
インストール時のオプションはこの辺りが参考になります。
gatekeeper/README.md at master · open-policy-agent/gatekeeper · GitHub
gatekeeper-system
という名前空間が作成されています。
$ kubectl get ns NAME STATUS AGE default Active 45m gatekeeper-system Active 10s kube-node-lease Active 45m kube-public Active 45m kube-system Active 45m
Podが動き出しているか確認してみます。
$ kubectl get all -n gatekeeper-system NAME READY STATUS RESTARTS AGE pod/gatekeeper-audit-6b6986d74-dxsft 1/1 Running 0 28s pod/gatekeeper-controller-manager-bfbbfdf6f-2tcb5 1/1 Running 0 28s pod/gatekeeper-controller-manager-bfbbfdf6f-5p8rt 1/1 Running 0 28s pod/gatekeeper-controller-manager-bfbbfdf6f-8rl5c 1/1 Running 0 28s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/gatekeeper-webhook-service ClusterIP 172.20.74.16 <none> 443/TCP 28s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/gatekeeper-audit 1/1 1 1 28s deployment.apps/gatekeeper-controller-manager 3/3 3 3 28s NAME DESIRED CURRENT READY AGE replicaset.apps/gatekeeper-audit-6b6986d74 1 1 1 28s replicaset.apps/gatekeeper-controller-manager-bfbbfdf6f 3 3 3 28s
上記コマンドで取得できないものも作成されています。例えば以下のリソースなど。
$ kubectl get validatingwebhookconfigurations/gatekeeper-validating-webhook-configuration -oyaml
$ kubectl api-resources | grep .gatekeeper.sh configs config config.gatekeeper.sh true Config k8srequiredlabels constraints.gatekeeper.sh false K8sRequiredLabels constraintpodstatuses status.gatekeeper.sh true ConstraintPodStatus constrainttemplatepodstatuses status.gatekeeper.sh true ConstraintTemplatePodStatus constrainttemplates templates.gatekeeper.sh false ConstraintTemplate
必須Labelを強制する
早速使ってみます。
ポリシーテンプレートの作成(ラベル強制)
まずはシンプルにLabelを強制するポリシーを作ってみます。
metadata.name
は以下の制約がありますので、Kindを小文字にしたものにします。
DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.'
cat <<EOF > mylabelconstraints_template.yaml apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: mylabelconstraints spec: crd: spec: names: kind: MyLabelConstraints validation: openAPIV3Schema: properties: labels: type: array items: string targets: - target: admission.k8s.gatekeeper.sh rego: | package mylabelconstraints violation[{"msg": msg, "details": {"missing_labels": missing}}] { provided := {label | input.review.object.metadata.labels[label]} required := {label | label := input.parameters.labels[_]} missing := required - provided count(missing) > 0 msg := sprintf("you must provide labels: %v", [missing]) } EOF
では、適用します。
$ kubectl apply -f mylabelconstraints_template.yaml
constrainttemplate.templates.gatekeeper.sh/mylabelconstraints created
制約の作成・適用(ラベル強制)
次に先程作成したテンプレートを使って名前空間の作成にラベルを強制します。
cat <<EOF > mylabelconstraints_ns_musthave_mylabel.yml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: MyLabelConstraints metadata: name: ns-must-have-mylabels spec: match: kinds: - apiGroups: [""] kinds: ["Namespace"] parameters: labels: ["mylabel","mysecond_label"] EOF
以下のように、テンプレート側でパラメータ化したものに、
required := {label | label := input.parameters.labels[_]}
制約側でパラメータ渡します。ここが便利ポイントですね。
parameters: labels: ["mylabel","mysecond_label"]
では制約も適用します。
$ kubectl apply -f mylabelconstraints_ns_musthave_mylabel.yml
mylabelconstraints.constraints.gatekeeper.sh/ns-must-have-mylabels created
ポリシー違反のテスト
Label強制の設定ができたので、Label無しで名前空間を作成してみるとエラーが出ました。制約の強制ができてそうです。
$ kubectl create ns mytest-ns Error from server ([denied by ns-must-have-mylabels] you must provide labels: {"mylabel", "mysecond_label"}): admission webhook "validation.gatekeeper.sh" denied the request: [denied by ns-must-have-mylabels] you must provide labels: {"mylabel", "mysecond_label"}
ポリシー違反のテスト(正常系)
次は強制したLabel付きで名前空間作ってみます。今度はエラー無く作成可能です。
$ kubectl apply -f - <<EOF kind: Namespace apiVersion: v1 metadata: name: mytest-ns labels: mylabel: aaa mysecond_label: bbb EOF namespace/mytest-ns created
掃除
掃除しておきます。
kubectl delete ns mytest-ns kubectl delete -f mylabelconstraints_ns_musthave_mylabel.yml kubectl delete -f mylabelconstraints_template.yaml
Container Registryを強制する
次はレジストリを自身のアカウントのECRに制限してみようと思います。
以下のようなnginxのPod定義を利用します。
cat > nginx.yml <<EOF kind: Pod apiVersion: v1 metadata: namespace: default name: nginx-app labels: app: nginx spec: containers: - name: nginx-app image: nginx EOF
当然、現時点では普通に適用できます。このままだとDockerhubから取ってくるので、指定したECRからの取得に制限してみます。
$ kubectl apply -f nginx.yml pod/nginx created $ kubectl delete -f nginx.yml pod "nginx" deleted
ポリシーテンプレートの作成(レジストリ強制)
先程より少し複雑になってますが、全体の構成としてはほとんど変わって無いです。
propertiesを registries
という配列で受け取ることにして、regoもそれに合わせて書き直しています。
cat <<EOF > myregistriesconstraints_template.yml apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: myregistriesconstraints spec: crd: spec: names: kind: MyRegistriesConstraints validation: openAPIV3Schema: properties: registries: type: array items: string targets: - target: admission.k8s.gatekeeper.sh rego: | package myregistriesconstraints violation[{"msg": msg, "details": {"invalid_registry": image}}] { input.review.kind.kind == "Pod" image := input.review.object.spec.containers[_].image name := input.review.object.metadata.name not registry_whitelisted(image,whitelisted_registries) msg := sprintf("pod %q has invalid registry %q. valid registries [%v]", [name, image,whitelisted_registries]) } whitelisted_registries := { registries | registries := input.parameters.registries[_] } registry_whitelisted(str, patterns) { registry_matches(str, patterns[_]) } registry_matches(str, pattern) { contains(str, pattern) } EOF
適用します。
$ kubectl apply -f myregistriesconstraints_template.yml
constrainttemplate.templates.gatekeeper.sh/myregistriesconstraints created
制約の作成・適用(レジストリ強制)
自身のアカウントと、AWSの提供しているECRの二つを許可するように設定してみます。
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) AWS_REGION=ap-northeast-1 cat <<EOF > myregistriesconstraints_constraints.yml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: MyRegistriesConstraints metadata: name: my-registry-constraints spec: match: kinds: - apiGroups: [""] kinds: ["Pod"] parameters: registries: - "602401143452.dkr.ecr.${AWS_REGION}.amazonaws.com" - "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" EOF
適用します。
kubectl apply -f myregistriesconstraints_constraints.yml
ポリシー違反テスト
最初にapplyできたnginxのPodが作成できなくなっています。
$ kubectl apply -f nginx.yml Error from server ([denied by my-registry-constraints] pod "nginx-app" has invalid registry "nginx". valid registries [{"602401143452.dkr.ecr.ap-northeast-1.amazonaws.com", "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com"}]): error when creating "nginx.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by my-registry-constraints] pod "nginx-app" has invalid registry "nginx". valid registries [{"602401143452.dkr.ecr.ap-northeast-1.amazonaws.com", "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com"}]
ポリシー違反テスト(正常系)
自身のECRにnginxをアップロードして、以下のようにnginxのPodのimage取得先を変えてみるとPodを作成できるようになります。
cat nginx.yml #省略 spec: containers: - name: nginx-app image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx #image: nginx
掃除
$ kubectl delete -f nginx.yml $ kubectl delete -f myregistriesconstraints_constraints.yml $ kubectl delete -f myregistriesconstraints_template.yml
Tips
Regoのエラー検知
Regoのエラーはapply時にチェックしてくれるものもあるみたいですが、applyは正常だったのに動かない場合もあります。その場合は以下のコマンドで出力される項目のeventにエラーが出てる時がありますのでチェックしてみてください。
エラーが無ければ Events: <none>
となるはずです。
全て正常にapplyできているはずなのに、 no matches for kind
のエラーが出て少しハマりました。以下のコマンドで見たら リソースはCREATEされていたのですが、Regoでエラーが出ていると、このエラーになるようでした。
kubectl describe constrainttemplates.templates.gatekeeper.sh myregistriesconstraints
サンプル集
REGOのサンプルはここに沢山置いてあります。