ry's Tech blog

Cloud Native技術などについて書いていきます。

Styra DASを使ってOPAをより簡単に

はじめに

今回は、Qiita: Kubernetes Advent Calendar 2020 その3 23日目の記事です。

今回の題材は、KubeCon NA 2020においてStyraが発表したStyra DAS(Declarative Authorisation Service)のフリーエディションです!

実際に使ってみて、今まで手が出しにくかったOPAが、とても楽に管理できたのでこちらで紹介させていただければと思います。

Styra DAS Free

OPA Installation

まずこちらからアクセスして、登録を行ってください。

Styra DAS Free

登録して少し待つと、以下の画面に行けるようになります。

opa-das-free-start

ここからQuick Startに沿って、Kubernetes Clusterを登録していきます。

まずここで、名前を設定しAdd systemをクリックします。

※ Read-onlyは外しておきましょう。

その後、Quick Startの2. Install Styra OPA on your clusterをクリックします。

styra-das-cluster-set

次に、各コマンドを入力して、OPAをインストールします。

styra-das-opa-install

コマンドで起動を確認します。

% kubectl get all -n styra-system
NAME                                     READY   STATUS    RESTARTS   AGE
pod/datasources-agent-6f96f7d4c9-8z79l   1/1     Running   0          2m34s
pod/opa-7979b56c84-8bdbk                 2/2     Running   0          2m34s
pod/opa-7979b56c84-d6d87                 2/2     Running   0          2m34s
pod/opa-7979b56c84-sbmcd                 2/2     Running   0          2m34s

NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/opa   ClusterIP   10.103.17.84   <none>        443/TCP   2m34s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/datasources-agent   1/1     1            1           2m34s
deployment.apps/opa                 3/3     3            3           2m34s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/datasources-agent-6f96f7d4c9   1         1         1       2m34s
replicaset.apps/opa-7979b56c84                 3         3         3       2m34s

ここで、Quick Startを抜けていきます。

左上のロゴの近くの?マークから、Quick Startを再開することができます。

Validation

ここから、Validationを見ていきたいと思います。

Validationとは、受け取ったリクエストを通すか弾くかを決めるものです。

[Cluster name] > [Validating] > [Rules]に移動します。

styra-das-validation

Add rulesを選択します。 ここでは、prebuiltのruleが入っています。

styra-das-validation-prebuilt

今回は、

  • Ingress: Require TLS
  • Containers: Prohibit :latest Image Tag

を設定していきます。

styra-das-validation-rule

右上のValidateを選択することで、既存のClusterにおいて今回適用するruleに対して違反しているリソースがないかを確認することができます。

styra-das-validation-validate

各ruleにおいて、現在MonitorとなっているところをEnforceに変更します。

styra-das-validation-enforce

下記コマンドが、現状通ることを確認します。

% kubectl run nginx --image=nginx:latest
pod/nginx created

% kubectl apply -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml 
Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.networking.k8s.io/example-ingress created

確認ができましたので、下記コマンドにてリソースを削除します。

% kubectl delete pod nginx                                                                                                          
pod "nginx" deleted

% kubectl delete -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml
Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.networking.k8s.io "example-ingress" deleted

それでは、Publishを押します。

確認が表示されるので、Publish changesを選択します。

それでは、それぞれのruleが設定されたかを確認します。

% kubectl run nginx --image=nginx:latest                                                                                            
Error from server: admission webhook "validating-webhook.openpolicyagent.org" denied the request: Enforced: Resource Pod/default/nginx should not use the 'latest' tag on container image nginx:latest.

% kubectl apply -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml 
Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
Error from server: error when creating "https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml": admission webhook "validating-webhook.openpolicyagent.org" denied the request: Enforced: Resource Ingress/default/example-ingress should have TLS enabled.

Errorになることが確認できました!

Mutation

Mutationとは、リクエストにに対して、パラメータを追加したり変更したりすることができるものです。

DAS にアクセスし、[Cluster name] > [Mutating] > [Rules]に移動します。

Add rulesを選択します。 ここではValidationと同様に、prebuiltのruleが入っています。

今回は、

  • Containers: Add Default Memory Limit
  • Containers: Add Default CPU Limit

を追加していきます。

値は、環境に合わせて設定してください。

まず、適用していない場合の状況を確認します。

% kubectl run nginx --image=nginx:1.19.6
pod/nginx created


% kubectl describe pods nginx        
Name:         nginx
Namespace:    default
Priority:     0
Node:         minikube/192.168.49.2
Start Time:   Sat, 19 Dec 2020 20:51:24 +0900
Labels:       run=nginx
Annotations:  <none>
Status:       Running
IP:           172.17.0.7
IPs:
  IP:  172.17.0.7
Containers:
  nginx:
    Container ID:   docker://9cfd38b13211ab997d362f59c6849662b50e844c05c6af4392978f167d5234c1
    Image:          nginx:1.19.6
    Image ID:       docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 19 Dec 2020 20:51:29 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-85jn9 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-85jn9:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-85jn9
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  93s   default-scheduler  Successfully assigned default/nginx to minikube
  Normal  Pulling    92s   kubelet            Pulling image "nginx:1.19.6"
  Normal  Pulled     89s   kubelet            Successfully pulled image "nginx:1.19.6" in 3.5937294s
  Normal  Created    88s   kubelet            Created container nginx
  Normal  Started    88s   kubelet            Started container nginx

リソースを消していきます。

% kubectl delete pod nginx
pod "nginx" deleted

Enforceに移動させ、Publishをクリックして、ruleを適用します。

再度、同じリソースを作成し確認していきます。

% kubectl run nginx --image=nginx:1.19.6
pod/nginx created

% kubectl describe pods nginx            
Name:         nginx
Namespace:    default
Priority:     0
Node:         minikube/192.168.49.2
Start Time:   Sat, 19 Dec 2020 20:58:47 +0900
Labels:       run=nginx
Annotations:  <none>
Status:       Running
IP:           172.17.0.7
IPs:
  IP:  172.17.0.7
Containers:
  nginx:
    Container ID:   docker://86d42940ef4206bdee92c88919f4560b8e7f8ccecc105d2e333eb7dcf375114a
    Image:          nginx:1.19.6
    Image ID:       docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 19 Dec 2020 20:58:48 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     2
      memory:  100Mi
    Requests:
      cpu:        2
      memory:     100Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-85jn9 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-85jn9:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-85jn9
    Optional:    false
QoS Class:       Guaranteed
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  38s   default-scheduler  Successfully assigned default/nginx to minikube
  Normal  Pulled     37s   kubelet            Container image "nginx:1.19.6" already present on machine
  Normal  Created    37s   kubelet            Created container nginx
  Normal  Started    37s   kubelet            Started container nginx

適用されていることが確認できました!

終わりに

ここまで、簡単にOPAを導入していく方法を見ていきました。

prebuiltのruleを用いることで、とても簡単に制御できることがわかったと思います。

ここまで読んでいただき、ありがとうございました!

引き続き、Cloud Nativeを盛り上げていきましょう!

Appendixとして、自分でregoを書いていく際に役に立つ機能について書かせていただきますので、ぜひこちらも読んでいただけると嬉しいです。

Appendix

Admission Review

これまで、prebuiltのruleを用いてPolicyを作成してきましたが、 実運用をしていく中で、自分でregoを書いてruleを作っていく必要が出てきます。

OPAにおいて、Regoを用いてruleを記述する対象となるのがAdmission Reviewになります。

regoとAdmissoin Reviewの関係は、OPA Playgroundで、簡単に試すことができます。

ただ、あらゆるリソースに対してこのAddmission Reviewを追うのは大変です。

Styra DAS Freeでは、このAddmission ReviewをLogとして記録してくれています。

[Cluster name]を選択し、Decisionsタブを選択することで確認することができます。

今後ruleを適用しようと考えているリソースを作成します。

% kubectl create deployment nginx-deploy --image=nginx:1.19.6
deployment.apps/nginx-deploy created

下のFilterにて、作成したリソース名を入力します。

それでは、実際に作成したDeploymentに対してValidatingを行なっている箇所を選択します。

Inputのところを抜き出します。

Admission Review

"input": {
    "apiVersion": "admission.k8s.io/v1",
    "kind": "AdmissionReview",
    "request": {
      "dryRun": false,
      "kind": {
        "group": "apps",
        "kind": "Deployment",
        "version": "v1"
      },
      "name": "nginx-deploy",
      "namespace": "default",
      "object": {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "metadata": {
          "creationTimestamp": "2020-12-19T12:10:57Z",
          "generation": 1,
          "labels": {
            "app": "nginx-deploy"
          },
          "managedFields": [
            {
              "apiVersion": "apps/v1",
              "fieldsType": "FieldsV1",
              "fieldsV1": {
                "f:metadata": {
                  "f:labels": {
                    ".": {},
                    "f:app": {}
                  }
                },
                "f:spec": {
                  "f:progressDeadlineSeconds": {},
                  "f:replicas": {},
                  "f:revisionHistoryLimit": {},
                  "f:selector": {
                    "f:matchLabels": {
                      ".": {},
                      "f:app": {}
                    }
                  },
                  "f:strategy": {
                    "f:rollingUpdate": {
                      ".": {},
                      "f:maxSurge": {},
                      "f:maxUnavailable": {}
                    },
                    "f:type": {}
                  },
                  "f:template": {
                    "f:metadata": {
                      "f:labels": {
                        ".": {},
                        "f:app": {}
                      }
                    },
                    "f:spec": {
                      "f:containers": {
                        "k:{\"name\":\"nginx\"}": {
                          ".": {},
                          "f:image": {},
                          "f:imagePullPolicy": {},
                          "f:name": {},
                          "f:resources": {},
                          "f:terminationMessagePath": {},
                          "f:terminationMessagePolicy": {}
                        }
                      },
                      "f:dnsPolicy": {},
                      "f:restartPolicy": {},
                      "f:schedulerName": {},
                      "f:securityContext": {},
                      "f:terminationGracePeriodSeconds": {}
                    }
                  }
                }
              },
              "manager": "kubectl-create",
              "operation": "Update",
              "time": "2020-12-19T12:10:57Z"
            }
          ],
          "name": "nginx-deploy",
          "namespace": "default",
          "uid": "8d27da1b-c9cb-4453-ba5e-d79f051fdb48"
        },
        "spec": {
          "progressDeadlineSeconds": 600,
          "replicas": 1,
          "revisionHistoryLimit": 10,
          "selector": {
            "matchLabels": {
              "app": "nginx-deploy"
            }
          },
          "strategy": {
            "rollingUpdate": {
              "maxSurge": "25%",
              "maxUnavailable": "25%"
            },
            "type": "RollingUpdate"
          },
          "template": {
            "metadata": {
              "creationTimestamp": null,
              "labels": {
                "app": "nginx-deploy"
              }
            },
            "spec": {
              "containers": [
                {
                  "image": "nginx:1.19.6",
                  "imagePullPolicy": "IfNotPresent",
                  "name": "nginx",
                  "resources": {},
                  "terminationMessagePath": "/dev/termination-log",
                  "terminationMessagePolicy": "File"
                }
              ],
              "dnsPolicy": "ClusterFirst",
              "restartPolicy": "Always",
              "schedulerName": "default-scheduler",
              "securityContext": {},
              "terminationGracePeriodSeconds": 30
            }
          }
        },
        "status": {}
      },
      "oldObject": null,
      "operation": "CREATE",
      "options": {
        "apiVersion": "meta.k8s.io/v1",
        "fieldManager": "kubectl-create",
        "kind": "CreateOptions"
      },
      "requestKind": {
        "group": "apps",
        "kind": "Deployment",
        "version": "v1"
      },
      "requestResource": {
        "group": "apps",
        "resource": "deployments",
        "version": "v1"
      },
      "resource": {
        "group": "apps",
        "resource": "deployments",
        "version": "v1"
      },
      "uid": "1040a718-e0c6-47ec-93c2-514d55c41d37",
      "userInfo": {
        "groups": [
          "system:masters",
          "system:authenticated"
        ],
        "username": "minikube-user"
      }
    }
  },

これが、Inputとして入ってきたAddmission Reviewになります。 OPAはこれに対してruleを用いて、ValidationやMutationを行なっていることが分かります。

Writing Mutating Rule

では、上のリソースに対して実際にruleを書いていきます。

今回はこのようなruleを書いてみました。

Mutating Rule

enforce[decision] {
  #title: Restrict Replica
  input.request.object.kind == "Deployment"

  #   Check if the replica count is more than 2
  input.request.object.spec.replicas > 2

  # As all of the above conditions are true, the request will be updated with
  # the following JSON patch and respective decision will be returned.
  decision := {
    "allowed": true,
    "message": "replacing replicas",
    "patch": [
      {
        "op": "replace",
        "path": "/spec/replicas",
        "value": 2
      }
    ]
  }
}

内容としては、replica数が2より大きい場合に、replica数を2に書き換えるという内容になっています。

では、コマンドで確認していきたいと思います。

% kubectl create deployment nginx-deploy --image=nginx:1.19.6 --replicas=5
deployment.apps/nginx-deploy created

 % kubectl get pods                                              
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-5dbf8f84ff-29lbm   1/1     Running   0          7s
nginx-deploy-5dbf8f84ff-rh54m   1/1     Running   0          7s

replicaを5にしているにもかかわらず、ちゃんとreplicaが2として生成されました。

DASにおいても、Adviceという項目でMutationが実施されたことを確認できます。

まだ、regoを完全に理解できているわけではないので、分かっていない部分ではありますが、

一般的なOPAを使う時とDASを通して使う時で、regoの文法等は一緒なのですが、表現方法が若干違うように感じました。

この辺り、もっと突き詰めていきたいと思います。