どうも! @hirosat です。
本日は、TUNA-JP Advent Calendar 2021 の中で、まだ紹介されてない、Gatekeeper をやろうかなと思って、エントリしました。
Gatekeeper とは?
Gatekeeper を語るためには、その前にいくつか紹介しなければ行けない背景があります。
K8s のアクセス制御の仕組みを知ってる?
(参考: Kubernetes Documentation)
「K8sクラスタ のセキュリティをなんか強化したい!」と思った時には、必ずこのアクセス制御の仕組みを使うことになります。K8s クラスタへの全てのリクエストは、必ずこの3ステップで処理されます。
- Authentication (AuthN。認証)
- Authorization (AuthZ。認可)
- 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が非推奨になったよ
(参考: 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-audit
とgatekeeper-controller
という、2つのDeploymentが入る。
で、私が指定したgatekeeper
namespaceには、、、tokenが入っているだけのようだ。あまり指定する意味はないかもしれない・・・orz
(2021年12月12日 追記)
--namespace
オプションで指定したものについては、上記の結果の通りとなるのだが、--values-schema
オプションで確認したものは、--values-file
で指定する必要があった。
例えば、以下のようなファイルを用意。
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に関するrequired
とprovided
を比較して、条件がマッチないと、ルール違反!みたいな記載をしています。
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
これを適用することにより、Namespace
にowner
というキーがないとルール違反!というポリシーが発動されます。
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
test
namespaceも作られてなかったので、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を作ることが出来ました。
test
namespaceは作成され、owner=hirosat
のLabelが割り当てられていることが分かります。
最後に
OPA Gatekeeper Library には、様々なルールの雛形が置いてあるので、活用してみると良いでしょう。
それではよいセキュリティライフを!