OPAのチュートリアル

eksworkshop.comのOPAのチュートリアルをやってみたメモ。

コンポーネント バージョン 備考
eksctl 0.31.0
Kubernetes バージョン 1.18
プラットフォームのバージョン eks.2
Gatekeeper 3.1.0

参考リンク

手順

Gatekeeperのデプロイ

OPA Gatekeeperをデプロイする。最新は3.2.1だが、eksworkshopは3.1。

$ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.1/deploy/gatekeeper.yaml
namespace/gatekeeper-system created
customresourcedefinition.apiextensions.k8s.io/configs.config.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constraintpodstatuses.status.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constrainttemplatepodstatuses.status.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constrainttemplates.templates.gatekeeper.sh created
serviceaccount/gatekeeper-admin created
role.rbac.authorization.k8s.io/gatekeeper-manager-role created
clusterrole.rbac.authorization.k8s.io/gatekeeper-manager-role created
rolebinding.rbac.authorization.k8s.io/gatekeeper-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/gatekeeper-manager-rolebinding created
secret/gatekeeper-webhook-server-cert created
service/gatekeeper-webhook-service created
deployment.apps/gatekeeper-audit created
deployment.apps/gatekeeper-controller-manager created
validatingwebhookconfiguration.admissionregistration.k8s.io/gatekeeper-validating-webhook-configuration created

Podを確認する。レプリカが3つも動いている。Helmチャートだとこの辺りカスタマイズできそう。

$ kubectl get pods -n gatekeeper-system
NAME                                             READY   STATUS    RESTARTS   AGE
gatekeeper-audit-576f6d6f8d-mpsjn                1/1     Running   0          45s
gatekeeper-controller-manager-85d8bf48c9-288r8   1/1     Running   0          45s
gatekeeper-controller-manager-85d8bf48c9-5zphf   1/1     Running   0          45s
gatekeeper-controller-manager-85d8bf48c9-wjft8   1/1     Running   0          45s

ルールの作成

ConstraintTemplateのマニフェストを作成する。ConstraintTemplateはクラスターワイドのリソース。

cat <<EOF > constrainttemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8spspprivilegedcontainer
spec:
  crd:
    spec:
      names:
        kind: K8sPSPPrivilegedContainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8spspprivileged

        violation[{"msg": msg, "details": {}}] {
            c := input_containers[_]
            c.securityContext.privileged
            msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
        }

        input_containers[c] {
            c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
            c := input.review.object.spec.initContainers[_]
        }
EOF

ConstraintTemplateを作成する。

$ kubectl apply -f constrainttemplate.yaml
constrainttemplate.templates.gatekeeper.sh/k8spspprivilegedcontainer created
$ kubectl get constrainttemplate
NAME                        AGE
k8spspprivilegedcontainer   48s

Constraintのマニフェストを作成する。こちらもクラスターワイドのリソース。ConstraintTemplateをどのリソースに適用するかを指定する。

KindがConstraintではないのがわかりにくい。

cat <<EOF > constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
EOF

Constraintを作成する

$ kubectl apply -f constraint.yaml
k8spspprivilegedcontainer.constraints.gatekeeper.sh/psp-privileged-container created
$ kubectl get constraint
NAME                       AGE
psp-privileged-container   22s

試しに特権Podを作ってみる。

cat <<EOF > example1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: bad-nginx
  labels:
    app: bad-nginx
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true
EOF
kubectl apply -f example1.yaml

ちゃんと弾かれる。

$ kubectl apply -f example1.yaml
Error from server ([denied by psp-privileged-container] Privileged container is not allowed: nginx, securityContext: {"privileged": true}): error when creating "example1.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by psp-privileged-container] Privileged container is not allowed: nginx, securityContext: {"privileged": true}

Deploymentの場合はどうなるか確認する。

cat <<EOF > example2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bad-nginx
  name: bad-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bad-nginx
  template:
    metadata:
      name: bad-nginx
      labels:
        app: bad-nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        securityContext:
          privileged: true
EOF
kubectl apply -f example2.yaml

この場合Deployment自体は作成できるが、Podは作られない。

$ kubectl apply -f example2.yaml
deployment.apps/bad-nginx created
$ kubectl get deploy
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
bad-nginx   0/1     0            0           82s
$ kubectl get rs
NAME                   DESIRED   CURRENT   READY   AGE
bad-nginx-6cdffbc58f   1         0         0       87s
$ kubectl get pod
No resources found in default namespace.

イベントでブロックされていることが確認できる。

$ kubectl get ev --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON              OBJECT                            MESSAGE
2m38s       Normal    ScalingReplicaSet   deployment/bad-nginx              Scaled up replica set bad-nginx-6cdffbc58f to 1
76s         Warning   FailedCreate        replicaset/bad-nginx-6cdffbc58f   Error creating: admission webhook "validation.gatekeeper.sh" denied the request: [denied by psp-privileged-container] Privileged container is not allowed: nginx, securityContext: {"privileged": true}

Deploymentを削除する。

$ kubectl delete -f example2.yaml
deployment.apps "bad-nginx" deleted

現在特権で動いているkube-proxyを停止してみると、予想通り起動してこない。

$ kubectl get pod -n kube-system
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-6c4d8d8f64-rr4cb   1/1     Running   0          21d
aws-node-lzzv4                                  1/1     Running   0          6d16h
aws-node-nxwvh                                  1/1     Running   0          6d16h
coredns-86f7d88d77-9nkn6                        1/1     Running   0          21d
coredns-86f7d88d77-p7x2h                        1/1     Running   0          21d
external-dns-7855554c94-hvl48                   1/1     Running   0          21d
kube-proxy-2q6wb                                1/1     Running   0          21d
kube-proxy-9rvdt                                1/1     Running   0          21d
metrics-server-5f956b6d5f-2lqst                 1/1     Running   0          7d
$ kubectl delete pod -n kube-system kube-proxy-2q6wb
pod "kube-proxy-2q6wb" deleted
$ kubectl get pod -n kube-system
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-6c4d8d8f64-rr4cb   1/1     Running   0          21d
aws-node-lzzv4                                  1/1     Running   0          6d16h
aws-node-nxwvh                                  1/1     Running   0          6d16h
coredns-86f7d88d77-9nkn6                        1/1     Running   0          21d
coredns-86f7d88d77-p7x2h                        1/1     Running   0          21d
external-dns-7855554c94-hvl48                   1/1     Running   0          21d
kube-proxy-9rvdt                                1/1     Running   0          21d
metrics-server-5f956b6d5f-2lqst                 1/1     Running   0          7d

イベントを確認すると、ブロックされていることが確認できる。

$ kubectl get ev -n kube-system --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON         OBJECT                 MESSAGE
2m11s       Normal    Killing        pod/kube-proxy-2q6wb   Stopping container kube-proxy
45s         Warning   FailedCreate   daemonset/kube-proxy   Error creating: admission webhook "validation.gatekeeper.sh" denied the request: [denied by psp-privileged-container] Privileged container is not allowed: kube-proxy, securityContext: {"privileged": true}

ワークロードを対象としてルールを作成してみる。

cat <<EOF > constrainttemplate2.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8spspprivilegedworkload
spec:
  crd:
    spec:
      names:
        kind: K8sPSPPrivilegedWorkload
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8spspprivileged

        violation[{"msg": msg, "details": {}}] {
            c := input_containers[_]
            c.securityContext.privileged
            msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
        }

        input_containers[c] {
            c := input.review.object.spec.template.spec.containers[_]
        }

        input_containers[c] {
            c := input.review.object.spec.template.spec.initContainers[_]
        }
EOF
kubectl apply -f constrainttemplate2.yaml
cat <<EOF > constraint2.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedWorkload
metadata:
  name: psp-privileged-workload
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["ReplicaSet"]
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
      - apiGroups: ["apps"]
        kinds: ["DaemonSet"]
      - apiGroups: ["apps"]
        kinds: ["StatefulSet"]
      - apiGroups: ["batch"]
        kinds: ["Job"]
EOF
kubectl apply -f constraint2.yaml

これでもう一度Deploymentを作成してみると、ブロックできた。

$ kubectl apply -f example2.yaml
Error from server ([denied by psp-privileged-workload] Privileged container is not allowed: nginx, securityContext: {"privileged": true}): error when creating "example2.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by psp-privileged-workload] Privileged container is not allowed: nginx, securityContext: {"privileged": true}

この辺りの説明を読むと、matchセレクターを上手く使うことで除外は設定できそう。

先ほどのPodの制約について、kube-proxyを除外してみる。k8s-appラベルがついているものを除外する。

cat <<EOF > constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    labelSelector:
      matchExpressions:
        - key: k8s-app
          operator: DoesNotExist
EOF
kubectl apply -f constraint.yaml

この条件があれば、適応を除外できる。

$ kubectl get pod -n kube-system
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-6c4d8d8f64-rr4cb   1/1     Running   0          21d
aws-node-lzzv4                                  1/1     Running   0          6d17h
aws-node-nxwvh                                  1/1     Running   0          6d17h
coredns-86f7d88d77-9nkn6                        1/1     Running   0          21d
coredns-86f7d88d77-p7x2h                        1/1     Running   0          21d
external-dns-7855554c94-hvl48                   1/1     Running   0          21d
kube-proxy-r5jjz                                1/1     Running   0          29m
kube-proxy-r6zc7                                1/1     Running   0          2m15s
metrics-server-5f956b6d5f-2lqst                 1/1     Running   0          7d1h
$ kubectl delete pod -n kube-system kube-proxy-r5jjz
pod "kube-proxy-r5jjz" deleted
$ kubectl get pod -n kube-system
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-6c4d8d8f64-rr4cb   1/1     Running   0          21d
aws-node-lzzv4                                  1/1     Running   0          6d17h
aws-node-nxwvh                                  1/1     Running   0          6d17h
coredns-86f7d88d77-9nkn6                        1/1     Running   0          21d
coredns-86f7d88d77-p7x2h                        1/1     Running   0          21d
external-dns-7855554c94-hvl48                   1/1     Running   0          21d
kube-proxy-fsbn8                                1/1     Running   0          13s
kube-proxy-r6zc7                                1/1     Running   0          2m50s
metrics-server-5f956b6d5f-2lqst                 1/1     Running   0          7d1h