5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TCE で Gatekeeper をサクッとやってみる

Last updated at Posted at 2021-12-11

どうも! @hirosat です。
本日は、TUNA-JP Advent Calendar 2021 の中で、まだ紹介されてない、Gatekeeper をやろうかなと思って、エントリしました。

Gatekeeper とは?

Gatekeeper を語るためには、その前にいくつか紹介しなければ行けない背景があります。

K8s のアクセス制御の仕組みを知ってる?

(参考: Kubernetes Documentation)

k8s control
「K8sクラスタ のセキュリティをなんか強化したい!」と思った時には、必ずこのアクセス制御の仕組みを使うことになります。K8s クラスタへの全てのリクエストは、必ずこの3ステップで処理されます。

  1. Authentication (AuthN。認証)
  2. Authorization (AuthZ。認可)
  3. Admission Control

「えっ?そんなの気にしたことないよ!」という人は、実はadmin権限を持つK8s contextでクラスタを利用しているパターンだと思います。ちゃんと制御したいなら、AuthNで お前は誰? を特定し、AuthZで お前はxxする権限を持っているよ! を管理していきます。(ちなみにTCEでは、Pinniped というOSSを導入することで、外部IDリソースと連携した認証/認可が可能になります。)

そこまでは分かりやすいと思いますが、では、Admission Controlとはなんでしょうか?

Admission Control は、その通過してきたリクエストに対し、修正したり拒否したりする改変 を加えることができます。モジュール式の Admissission Controller によって、様々なきめ細かい制御を、後付けで与える ことが出来る訳です。以下は、その一例です。

  • DefaultStorageClass : PV作成時に特に指定がない場合、特定のStorageClass設定を利用する指示を与える
  • AlwaysPullImages : Pod作成時、毎回ImageをPullする設定を矯正させる
  • NamespaceExists : リクエストされたNamespaceが存在しない場合は、リクエストを拒否
  • PodNodeSelector : 特定のNamespaceで利用可能な対象ノードを制限

他にもいっぱいAdmission Controllerはあり、単なるリクエスト修正/拒否だけではなく、あいまいなリクエストを誘導する側面も、持ち合わせてます。

(参考: Using Admission Controllers)

PSPが非推奨になったよ :scream:

(参考: Kubernetes Blog)

PodSecurityPolicy (PSP) とは、クラスタ管理者がPod仕様のセキュリティに関する制御を行うための、Admission Controller の1つです。PodSecurityPolicyリソースをクラスタ内に作成すると、その定義内容の条件を満たしていないと、クラスタ内でPodが動作できなくなります。

それが、Kubernetes v1.21 から Deprecated(非推奨) という扱いに変更になりました。Kubernetes v1.25 で完全に削除される予定です。

では、何故非推奨になったのでしょうか?要は、PSP が「かゆいところに手が届かない」仕様のため、以下のような問題が起こりえました。

  • 意図したよりも広い範囲の権限を誤って与えてしまう
  • どのPSP設定が適用されているかの把握が困難
  • 設定出来る内容が限られている
  • 自分のPodが適用対象なのかが不明
    • dryrun や 監査モードがない (動かそうとして初めて気づく)

OPA: PSP代替の最有力候補!

(参考: Open Policy Agent

PSPの代替となりそうなOSSはいくつかあるのですが、その中でOpen Policy Agent (OPA) が、非常に筋が良さそうなツールとして注目されています。

OPA自体は、ポリシーを定義するためのフレームワークであり、K8sだけではなく、EnvoyやAPP等に対しても定義することができます。

rego言語という古くからある言語を用いることで、ポリシーに関するきめ細やかな制御を実現できます。宣言型なので、Podに限らず、様々なポリシーを定義できます。

Gatekeeper: OPAのK8s実装

(参考: Github

ここでついに、Gatekeeperの話となります!以下の特徴により、OPAをK8sで使用するために最適化されています。

  • constraint templates と、 constraints という2つのCRDにより、拡張可能で柔軟なポリシーライブラリを実現
  • Audit機能

サクッとやってみる。

さて、前置きが長くなりましたが、早速試してみます。

前提条件:TCEがインストールされていて、TKCが建っていること。

ということで、TKCを作成して、K8sのcontextをそこに向けた直後の状態です。

Gatekeeperのインストール

まずは、現在のTKC上で、plugin郡を使えるようにするオマジナイ。

$ tanzu package repository add tce-repo \
>   --url projects.registry.vmware.com/tce/main:0.9.1 \
>   --namespace tanzu-package-repo-global

これにより、そのクラスタで利用可能なパッケージが一気に増えます。

$ tanzu package available list
/ Retrieving available packages...
  NAME                                           DISPLAY-NAME        SHORT-DESCRIPTION
  cert-manager.community.tanzu.vmware.com        cert-manager        Certificate management
  contour.community.tanzu.vmware.com             Contour             An ingress controller
  external-dns.community.tanzu.vmware.com        external-dns        This package provides DNS synchronization functionality.
  fluent-bit.community.tanzu.vmware.com          fluent-bit          Fluent Bit is a fast Log Processor and Forwarder
  gatekeeper.community.tanzu.vmware.com          gatekeeper          policy management
  grafana.community.tanzu.vmware.com             grafana             Visualization and analytics software
  harbor.community.tanzu.vmware.com              Harbor              OCI Registry
  knative-serving.community.tanzu.vmware.com     knative-serving     Knative Serving builds on Kubernetes to support deploying and serving of applications and functions as serverless containers
  local-path-storage.community.tanzu.vmware.com  local-path-storage  This package provides local path node storage and primarily supports RWO AccessMode.
  multus-cni.community.tanzu.vmware.com          multus-cni          This package provides the ability for enabling attaching multiple network interfaces to pods in Kubernetes
  prometheus.community.tanzu.vmware.com          prometheus          A time series database for your metrics
  velero.community.tanzu.vmware.com              velero              Disaster recovery capabilities

この中で、NAME欄が重要になってきます。下記のようにうつと、対象のパッケージで利用可能なバージョンが分かります。

$ tanzu package available list gatekeeper.community.tanzu.vmware.com
/ Retrieving package versions for gatekeeper.community.tanzu.vmware.com...
  NAME                                   VERSION  RELEASED-AT
  gatekeeper.community.tanzu.vmware.com  1.0.0

おっ、バージョンは1.0.0 なのね。

おや?TCEのドキュメントで紹介されているバージョンは、3.7.0だぞ?

どうも、OSSのGatekeeperの現在の最新バージョンは3.7.0 なのだが、TCEのレポジトリでは、別の粒度でバージョン管理されてるらしい。ややこしいな。。

ともかく、今必要な情報は、1.0.0の方だ。

次に、設定可能な変数の確認。

$ tanzu package available get gatekeeper.community.tanzu.vmware.com/1.0.0 --values-schema
| Retrieving package details for gatekeeper.community.tanzu.vmware.com/1.0.0...
  KEY        DEFAULT            TYPE    DESCRIPTION
  namespace  gatekeeper-system  string  The namespace in which to deploy gatekeeper.

どうやら Gatekeeper では、namespaceしかいじれないらしい。逆に言うと、インストール難易度は低いと言える。

せっかくなので、gatekeeperというnamespace に、入れてみる。さて、どうなることやら。

$ tanzu package install gatekeeper --package-name gatekeeper.community.tanzu.vmware.com --version 1.0.0 --namespace gatekeeper
/ Installing package 'gatekeeper.community.tanzu.vmware.com'
| Getting namespace 'gatekeeper'
/ Getting package metadata for 'gatekeeper.community.tanzu.vmware.com'
| Creating service account 'gatekeeper-gatekeeper-sa'
| Creating cluster admin role 'gatekeeper-gatekeeper-cluster-role'
| Creating cluster role binding 'gatekeeper-gatekeeper-cluster-rolebinding'
- Creating package resource
| Package install status: Reconciling

 Added installed package 'gatekeeper' in namespace 'gatekeeper'

おっ、あっさり入った。

リソースの確認

インストールされたリソースを探してみる。

$ kubectl get all -A
NAMESPACE           NAME                                                         READY   STATUS    RESTARTS   AGE
gatekeeper-system   pod/gatekeeper-audit-5fd6d7bf7b-jtb9h                        1/1     Running   0          3m45s
gatekeeper-system   pod/gatekeeper-controller-manager-6b6bdd8df8-68fc6           1/1     Running   0          3m45s
(省略)

NAMESPACE           NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
gatekeeper-system   service/gatekeeper-webhook-service   ClusterIP   100.71.252.11    <none>        443/TCP                  3m45s
(省略)

NAMESPACE           NAME                                                    READY   UP-TO-DATE   AVAILABLE   AGE
gatekeeper-system   deployment.apps/gatekeeper-audit                        1/1     1            1           3m45s
gatekeeper-system   deployment.apps/gatekeeper-controller-manager           1/1     1            1           3m45s
(省略)

NAMESPACE           NAME                                                               DESIRED   CURRENT   READY   AGE
gatekeeper-system   replicaset.apps/gatekeeper-audit-5fd6d7bf7b                        1         1         1       3m45s
gatekeeper-system   replicaset.apps/gatekeeper-controller-manager-6b6bdd8df8           1         1         1       3m45s
(省略)

ubuntu@step01:~$ kubectl get secrets -A
NAMESPACE                   NAME                                             TYPE                                  DATA   AGE
gatekeeper-system           default-token-mcqpx                              kubernetes.io/service-account-token   3      5m16s
gatekeeper-system           gatekeeper-admin-token-c75v7                     kubernetes.io/service-account-token   3      5m15s
gatekeeper-system           gatekeeper-webhook-server-cert                   Opaque                                4      5m14s
gatekeeper                  default-token-llkfz                              kubernetes.io/service-account-token   3      5m26s
gatekeeper                  gatekeeper-gatekeeper-sa-token-d44w8             kubernetes.io/service-account-token   3      5m19s
(省略)

・・なるほど。まず、自分の指定したnamespaceは何であれ、gatekeeper-systemという場所に、基本的なリソースがインストールされる訳ね。

そのリソースは主に、gatekeeper-auditgatekeeper-controllerという、2つのDeploymentが入る。

で、私が指定したgatekeepernamespaceには、、、tokenが入っているだけのようだ。あまり指定する意味はないかもしれない・・・orz

(2021年12月12日 追記)

--namespace オプションで指定したものについては、上記の結果の通りとなるのだが、--values-schemaオプションで確認したものは、--values-fileで指定する必要があった。

例えば、以下のようなファイルを用意。

values.yaml
namespace: custom-namespace

その後、以下のように適用することで、CRDのインストール場所も変えられた、はず。。

$ tanzu package install gatekeeper --package-name gatekeeper.community.tanzu.vmware.com --version 1.0.0 --namespace gatekeeper --values-file values.yaml

使ってみる

ちょうど、おあつらえ向きのサンプルが、TCEのGatekeeperのページに掲載されているので、これを試してみる。

1. Constraint Templateの適用

上記のページに掲載している雛形をyamlに保存して適用してみた。

$ vi ct.yaml
(↓↓ここからコピー)
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
  annotations:
    description: Requires all resources to contain a specified label with a value
      matching a provided regular expression.
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            message:
              type: string
            labels:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  allowedRegex:
                    type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels
        get_message(parameters, _default) = msg {
          not parameters.message
          msg := _default
        }
        get_message(parameters, _default) = msg {
          msg := parameters.message
        }
        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_].key}
          missing := required - provided
          count(missing) > 0
          def_msg := sprintf("you must provide labels: %v", [missing])
          msg := get_message(input.parameters, def_msg)
        }
        violation[{"msg": msg}] {
          value := input.review.object.metadata.labels[key]
          expected := input.parameters.labels[_]
          expected.key == key
          # do not match if allowedRegex is not defined, or is an empty string
          expected.allowedRegex != ""
          not re_match(expected.allowedRegex, value)
          def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex])
          msg := get_message(input.parameters, def_msg)
        }
(↑↑ここまでコピー)

$ kubectl apply -f ct.yaml

今適用したものは、制約の雛形ファイルなので、これ単体を宛てただけでは何も起こりません。

この中で注目すべきは、rego:と書かれている行より下の部分です。ここには、Rego言語と呼ばれるOepn Poliicy Agentで採用されたセキュリティルールを記述するための言語であり、従来のK8sのAdmission Controlより、木目細かいルールが書けるようになります。

ちなみにここに書かれているルール内容を一部紹介すると、labelのkeyに関するrequiredprovidedを比較して、条件がマッチないと、ルール違反!みたいな記載をしています。

2. Constraintsの適用

次は、先ほどの雛形に関する、実際の条件を適用します。

$ vi const.yaml
(↓↓ここからコピー)
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: all-must-have-owner
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    message: "All namespaces must have an `owner` label"
    labels:
      - key: owner
(↑↑ここまでコピー)

$ kubectl apply -f const.yaml

これを適用することにより、Namespaceownerというキーがないとルール違反!というポリシーが発動されます。

3. 実際に怒られてみる

$ kubectl create ns test
Error from server ([denied by all-must-have-owner] All namespaces must have an `owner` label): admission webhook "validation.gatekeeper.sh" denied the request: [denied by all-must-have-owner] All namespaces must have an `owner` label

よしよし、ちゃんと怒られたw

testnamespaceも作られてなかったので、Gatekeeperが正しく機能していることが分かります。

4. ルールに沿ってnamespaceを作ってみる

$ vi ns.yaml
(↓↓ここからコピー)
apiVersion: v1
kind: Namespace
metadata:
  name: test
  labels:
    owner: hirosat
(↑↑ここまでコピー)

$ kubectl apply -f ns.yaml
$ kubectl get ns test --show-labels
NAME   STATUS   AGE   LABELS
test   Active   78s   kubernetes.io/metadata.name=test,owner=hirosat

ルールはownerというkeyに関するものだったので、valueは元のサンプルから、hirosatに変えてみましたw

ちゃんと怒られずにNamespaceを作ることが出来ました。
testnamespaceは作成され、owner=hirosatのLabelが割り当てられていることが分かります。

最後に

OPA Gatekeeper Library には、様々なルールの雛形が置いてあるので、活用してみると良いでしょう。

それではよいセキュリティライフを!

5
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?