Tanzu Mission Controlで学ぶOpen Policy Agent Part-3

この記事はTanzu Mission Controlで学ぶ Open Policy Agentシリーズです。

シリーズ

第一回 : 概要
第二回 : TMCのOpen Policy Agentを遊んでみる
第三回 : TMCのOpen Policy Agentを解剖する < いまここ
第四回 : TMCからOPAポリシーを自作する

はじめに

概要編の記事にあるよう、OPAは以下のようなフローでKubernetesのリクエストに対しポリシーを定義します。

TMCからSecurity Policyを有効にした際これと同じ仕組みのものがうごいています。

以下TMCで、Security Policyを有効にした際に、どのような方法で第二回目でのPodの起動を阻止していたのかを解剖します。

TMCで管理された環境がある前提です。上の"With Admission Controller"のフロー図をみながら参照してください。

API > Admission Controller

TMCでは、以下のリソースがAPIをインターセプトするAdmission Controllerとして定義されています。

1kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration
2NAME                                          CREATED AT
3gatekeeper-validating-webhook-configuration   2020-09-30T14:43:48Z

validatingwebhookconfigurationsリソースがAdmission Controllerの挙動を定義しています。詳細はマニュアルのDynamic Admission Controlを参照してください。

筆者が確認した時点では、中身は以下のようになっていました。 主要な部分のみ抜粋します。

 1apiVersion: admissionregistration.k8s.io/v1
 2kind: ValidatingWebhookConfiguration
 3metadata:
 4  name: gatekeeper-validating-webhook-configuration
 5webhooks:
 6- admissionReviewVersions:
 7  - v1beta1
 8  clientConfig:
 9    service:
10      name: gatekeeper-webhook-service
11      namespace: gatekeeper-system
12      path: /v1/admit
13      port: 443
14  failurePolicy: Ignore
15  matchPolicy: Exact
16  name: validation.gatekeeper.sh
17  namespaceSelector:
18    matchExpressions:
19    - key: admission.gatekeeper.sh/ignore
20      operator: DoesNotExist
21  objectSelector: {}
22  rules:
23  - apiGroups:
24    - '*'
25    apiVersions:
26    - '*'
27    operations:
28    - CREATE
29    - UPDATE
30    resources:
31    - '*'
32    scope: '*'

この中の以下の部分ですが、中身を見ると、全てのAPI、全てのリソースのCREATE、UPDATEの際にAdmission Webhookを発報することを意味しています。

 1rules:
 2- apiGroups:
 3  - '*'
 4  apiVersions:
 5  - '*'
 6  operations:
 7  - CREATE
 8  - UPDATE
 9  resources:
10  - '*'
11  scope: '*'

さらに、以下の部分で、Webhookを送信先が記載されています。

1clientConfig:
2  service:
3    name: gatekeeper-webhook-service
4    namespace: gatekeeper-system
5    path: /v1/admit
6    port: 443

つまり、まとめると以下のことがわかります。

  • validatingwebhookconfigurationsリソースでAdmission Controllerが定義されている
  • 全てのAPI、全てのリソースのCREATE、UPDATEの際にAdmission Webhookを発報している
  • Admisson Webhookの送信先は、gatekeeper-systemgatekeeper-webhook-service:443/v1/admit

次にいきます。

Admission Controller > Policy Engine

Webhook先の稼働しているサービスを確認します。 前のステップのgatekeeper-webhook-service:443でサービスが起動していることがわかります。

1kubectl get svc -n gatekeeper-system
2NAME                         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
3gatekeeper-webhook-service   ClusterIP   10.101.28.83   <none>        443/TCP   18h

このサービスのSelectorは以下の通りです。

 1# kubectl get svc -n gatekeeper-system -o yaml
 2apiVersion: v1
 3items:
 4...
 5- spec:
 6...
 7    selector:
 8      control-plane: controller-manager
 9      gatekeeper.sh/operation: webhook
10      gatekeeper.sh/system: "yes"

該当するラベルで検索をかけます。すると以下のgatekeeper-controller-manager-*が3つ、つまりHAの状態でデプロイされていることが確認できます。 なお、これ自体はOPA Gatekeeperと呼ばれる、Kubernetesに対応したOPAのイメージです。

1kubectl get pods -l control-plane=controller-manager -n gatekeeper-system
2NAME                                            READY   STATUS    RESTARTS   AGE
3gatekeeper-controller-manager-c97765cd6-bgkjx   1/1     Running   0          18h
4gatekeeper-controller-manager-c97765cd6-hnvx2   1/1     Running   0          18h
5gatekeeper-controller-manager-c97765cd6-znhqk   1/1     Running   0          18h

つまり、まとめると以下のことがわかります。

  • Webhookを発報した先には、OPA GatekeeperがHA化された状態で起動している

次に行きます。

PolicyそしてRego言語

さて、いよいよOPAの中身をみていきます。 OPAには2つのリソースがあります。

  • constrainttemplates : 各リソースについて、どのようなポリシーを定義するかをRego言語で記載
  • constraints : constrainttemplates 内容を元にどのように、Constraint(縛り)を設けるか定義

詳細はOPA GatekeeperのGithubのREADMEを参照してください

ここでは、第二回目でPod起動の際の以下のエラーがどのように出力されたかをみてきます。

1[denied by tmc.cgp.strict] Sharing the host namespace is not allowed: verybad

ConstraintTemplates

上記のエラーの定義は以下のファイルで行われています。

1kubectl get constrainttemplate vmware-system-tmc-block-host-namespace-v1
2NAME                                        AGE
3vmware-system-tmc-block-host-namespace-v1   22h

筆者が確認したときは中身は以下のようになっていました。 主要な部分のみ抜粋しています。

 1apiVersion: templates.gatekeeper.sh/v1beta1
 2kind: ConstraintTemplate
 3metadata:
 4  name: vmware-system-tmc-block-host-namespace-v1
 5spec:
 6  crd:
 7    spec:
 8      names:
 9        kind: vmware-system-tmc-block-host-namespace-v1
10  targets:
11  - rego: |-
12      package k8spsphostnamespace
13      violation[{"msg": msg, "details": {}}] {
14          input_share_hostnamespace(input.review.object)
15          msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
16      }
17      input_share_hostnamespace(o) {
18          o.spec.hostPID
19      }
20      input_share_hostnamespace(o) {
21          o.spec.hostIPC
22      }      
23    target: admission.k8s.gatekeeper.sh

ポイントが以下の箇所です。

 1- rego: |-
 2    package k8spsphostnamespace
 3    violation[{"msg": msg, "details": {}}] {
 4        input_share_hostnamespace(input.review.object)
 5        msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
 6    }
 7    input_share_hostnamespace(o) {
 8        o.spec.hostPID
 9    }
10    input_share_hostnamespace(o) {
11        o.spec.hostIPC
12    }

ここで使われているのがRegoといわれる言語です。 これをもう一段階理解するために、RegoのOnline Viewerを使います。

https://play.openpolicyagent.org/p/EWQB9RPi3K

上のリンクでは、Inputに第二回目verybad.yamlをAdmissionControllerから変換したものを記載しています。

コードには、ここに含まれているコードを含めています。

Input側の"hostPID"をTrue、Falseに切り替えてEvaluate結果をみてましょう。

Trueのときは以下のようにOutputが表示されます。

1{
2    "violation": [
3        {
4            "details": {},
5            "msg": "Sharing the host namespace is not allowed: verybad"
6        }
7    ]
8}

Falseのときは以下のようにOutputが表示されます。

1{
2    "violation": []
3}

結果からみて分かる通り、"hostPID": "true"の時に"violation"に値をいれます。 そのなかには、msgは実際にうけとったメッセージと一致しています。

つまり、このRegoという言語で、TMCのSecurity Policyがどのように定義されているかわかります。

Constraint

そしてもう一つ確認するものがConstraintです。これは先ほどRegoで定義したルールがどういう場合に有効かを定義します。

違和感ありますが、ConstraintTemplate名でkubectl getをします。 すると以下のようにみえてきます。

1kubectl get vmware-system-tmc-block-host-namespace-v1
2NAME             AGE
3tmc.cgp.strict   23h

筆者が確認した時点では中身が以下のようになっていました。 主要な部分のみ抜粋しています。

 1apiVersion: v1
 2items:
 3- apiVersion: constraints.gatekeeper.sh/v1beta1
 4  kind: vmware-system-tmc-block-host-namespace-v1
 5  metadata:
 6    name: tmc.cgp.strict
 7  spec:
 8    match:
 9      kinds:
10      - apiGroups:
11        - ""
12        kinds:
13        - Pod
14      namespaceSelector:
15        matchExpressions:
16        - key: e2e-run
17          operator: DoesNotExist

spec.matchにどういった場合に、このConstraintが定義されているか記載されています。このルールではPodの操作がすべて定義されています。

よってまとめると

  • TMCでは、自動でConstraintTemplate,Constraintsが作成され、ユーザーから透過的に操作がされる

Policy Insightsのからくり

さて、前回はPolicy Insigtsというすごい機能で、ルール違反を一覧できるというものを紹介しました。

このカラクリですが、OPAのAudit機能を使っています。

https://github.com/open-policy-agent/gatekeeper#audit

これもConstraintの中身を確認するとCLIからも確認できます。 例えば、以下のようになっている場合

1status:
2  totalViolations: 1
3  violations:
4  - enforcementAction: dryrun
5    kind: Pod
6    message: 'Sharing the host namespace is not allowed: verybad'
7    name: verybad
8    namespace: default

PolicyInsight側では以下のようになります。

つまりこの機能もOPAをつかっていることがわかります。

まとめ

  • TMCのSecurity PolicyはAdmissionController、OPA、そしてConstraintTemplateとConstraintsの仕組みを使っている
  • ConstraintTemplateはRegoとよばれる言語で記載できる
  • OPAはPolicy Insights機能にもつかわれている

なお、全三回の予定でしたが、この記事をかいた数日後にまた新しい機能が追加されることがアナウンスされました。この内容も第四回としてまとめます。