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

· 11 分で読めます

この記事は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として定義されています。

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

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

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

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: gatekeeper-validating-webhook-configuration
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    service:
      name: gatekeeper-webhook-service
      namespace: gatekeeper-system
      path: /v1/admit
      port: 443
  failurePolicy: Ignore
  matchPolicy: Exact
  name: validation.gatekeeper.sh
  namespaceSelector:
    matchExpressions:
    - key: admission.gatekeeper.sh/ignore
      operator: DoesNotExist
  objectSelector: {}
  rules:
  - apiGroups:
    - '*'
    apiVersions:
    - '*'
    operations:
    - CREATE
    - UPDATE
    resources:
    - '*'
    scope: '*'

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

rules:
- apiGroups:
  - '*'
  apiVersions:
  - '*'
  operations:
  - CREATE
  - UPDATE
  resources:
  - '*'
  scope: '*'

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

clientConfig:
  service:
    name: gatekeeper-webhook-service
    namespace: gatekeeper-system
    path: /v1/admit
    port: 443

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

次にいきます。

Admission Controller > Policy Engine

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

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

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

# kubectl get svc -n gatekeeper-system -o yaml
apiVersion: v1
items:
...
- spec:
...
    selector:
      control-plane: controller-manager
      gatekeeper.sh/operation: webhook
      gatekeeper.sh/system: "yes"

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

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

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

次に行きます。

PolicyそしてRego言語

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

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

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

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

ConstraintTemplates

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

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

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

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: vmware-system-tmc-block-host-namespace-v1
spec:
  crd:
    spec:
      names:
        kind: vmware-system-tmc-block-host-namespace-v1
  targets:
  - rego: |-
      package k8spsphostnamespace
      violation[{"msg": msg, "details": {}}] {
          input_share_hostnamespace(input.review.object)
          msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
      }
      input_share_hostnamespace(o) {
          o.spec.hostPID
      }
      input_share_hostnamespace(o) {
          o.spec.hostIPC
      }
    target: admission.k8s.gatekeeper.sh

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

- rego: |-
    package k8spsphostnamespace
    violation[{"msg": msg, "details": {}}] {
        input_share_hostnamespace(input.review.object)
        msg := sprintf("Sharing the host namespace is not allowed: %v", [input.review.object.metadata.name])
    }
    input_share_hostnamespace(o) {
        o.spec.hostPID
    }
    input_share_hostnamespace(o) {
        o.spec.hostIPC
    }

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

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

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

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

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

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

{
    "violation": [
        {
            "details": {},
            "msg": "Sharing the host namespace is not allowed: verybad"
        }
    ]
}

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

{
    "violation": []
}

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

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

Constraint

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

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

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

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

apiVersion: v1
items:
- apiVersion: constraints.gatekeeper.sh/v1beta1
  kind: vmware-system-tmc-block-host-namespace-v1
  metadata:
    name: tmc.cgp.strict
  spec:
    match:
      kinds:
      - apiGroups:
        - ""
        kinds:
        - Pod
      namespaceSelector:
        matchExpressions:
        - key: e2e-run
          operator: DoesNotExist

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

よってまとめると

Policy Insightsのからくり

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

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

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

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

status:
  totalViolations: 1
  violations:
  - enforcementAction: dryrun
    kind: Pod
    message: 'Sharing the host namespace is not allowed: verybad'
    name: verybad
    namespace: default

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

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

まとめ

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