メインコンテンツにスキップ

Admission Webhook for K8s

1. Casbin K8s-Gatekeeper の概要とドキュメント

Casbin K8s-GateKeeper は、Casbin をアクセス制御ツールとして統合する Kubernetes のアドミッションウェブフックです。 Casbin K8s-GateKeeper を使用することで、コードを一行も書かずに、Casbin の ACL(アクセス制御リスト)言語の一部である Casbin モデルとポリシーの宣言的な設定を数行書くだけで、K8s リソースに対するあらゆる操作を認可または遮断する柔軟なルールを確立できます。

Casbin K8s-GateKeeper は、Casbin コミュニティによって開発およびメンテナンスされています。 このプロジェクトのリポジトリはこちらです: https://github.com/casbin/k8s-gatekeeper

0.1 簡単な例

例えば、コードを書く必要はありませんが、以下の設定行を使用してこの機能を実現します:「特定のタグを持つ画像をどのデプロイメントでも使用できないようにする」:

Model:

[request_definition]
r = obj

[policy_definition]
p = obj,eft

[policy_effect]
e = !some(where (p.eft == deny))

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(${OBJECT}.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)

And Policy:

p, "1.14.1",deny

これらは通常のCasbin ACL言語です。 それらについての章を既に読んでいると仮定すると、理解するのは非常に簡単です。

Casbin K8s-Gatekeeperには以下の利点があります:

  • 使いやすい。 多くのコードを書くよりも、数行のACLを書く方がはるかに優れています。
  • 設定のホットアップデートを可能にします。 設定を変更するためにプラグイン全体をシャットダウンする必要はありません。
  • 柔軟です。 任意のK8sリソースに対して任意のルールを作成でき、kubectl gatekeeperで調査できます。
  • 非常に複雑なK8sアドミッションウェブフックの実装を簡素化します。 K8sアドミッションウェブフックが何であるか、またはそれに対するコードを書く方法を知る必要はありません。 必要なのは、制約をかけたいリソースを知ることと、Casbin ACLを書くことだけです。 K8sが複雑であることは誰もが知っていますが、Casbin K8s-Gatekeeperを使用することで時間を節約できます。
  • Casbinコミュニティによってメンテナンスされています。 このプラグインについて何か混乱する点がある場合や、試してみて問題が発生した場合は、お気軽にお問い合わせください。

1.1 Casbin K8s-Gatekeeperはどのように機能しますか?

K8s-Gatekeeperは、管理者が望まないK8s上の操作を防ぐために、Casbinを使用して任意のユーザー定義のアクセス制御ルールを適用するK8sのアドミッションウェブフックです。

Casbinは、強力で効率的なオープンソースのアクセス制御ライブラリです。 さまざまなアクセス制御モデルに基づいて認可を実施するためのサポートを提供します。 Casbinの詳細については、概要を参照してください。

K8sのアドミッションウェブフックは、'admission requests'を受け取り、それらに対して何らかの処理を行うHTTPコールバックです。 特に、K8s-Gatekeeperは特殊なタイプのアドミッションウェブフックである'ValidatingAdmissionWebhook'で、このアドミッションリクエストを受け入れるか拒否するかを決定できます。 アドミッションリクエストについては、K8sの特定のリソース(例えば、デプロイメントの作成/削除)に対する操作を記述したHTTPリクエストです。 アドミッションウェブフックの詳細については、K8s公式ドキュメントを参照してください。

1.2 それがどのように機能するかを示す例

例えば、誰かがnginxを実行するポッドを含むデプロイメントを作成したい場合(kubectlまたはK8sクライアントを使用)、K8sはアドミッションリクエストを生成します。これは(YAML形式に翻訳すると)次のようになります:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.1
ports:
- containerPort: 80

このリクエストは、図に示されているすべてのミドルウェア、そして私たちのK8s-Gatekeeperを含むプロセスを通過します。 K8s-Gatekeeperは、K8sのetcdに保存されているすべてのCasbinエンフォーサーを検出できます。これらはユーザーによって作成および管理されており(kubectlまたは私たちが提供するGoクライアントを介して)、 各エンフォーサーはCasbinモデルとCasbinポリシーを含んでいます。 アドミッションリクエストは、すべてのエンフォーサーによって順番に処理され、すべてのエンフォーサーを通過したリクエストのみがこのK8s-Gatekeeperによって受け入れられます。

(Casbinエンフォーサー、モデル、またはポリシーが何かわからない場合は、このドキュメントを参照してください:はじめに)。

例えば、何らかの理由で、管理者はイメージ'nginx:1.14.1'の出現を禁止し、'nginx:1.3.1'を許可したいと考えています。 以下のルールとポリシーを含むエンフォーサーを作成できます(エンフォーサーの作成方法、これらのモデルとポリシーが何であるか、そしてそれらをどのように記述するかについては、次の章で説明します)。

Model:

[request_definition]
r = obj

[policy_definition]
p = obj,eft

[policy_effect]
e = !some(where (p.eft == deny))

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj

Policy:

p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny

上記のモデルとポリシーを含むエンフォーサーを作成することで、前述のアドミッションリクエストはこのエンフォーサーによって拒否され、つまりK8sはこのデプロイメントを作成しません。

K8s-gatekeeperのインストール

K8s-gatekeeper をインストールするためには、外部ウェブフック、内部ウェブフック、そして Helm の3つの方法が利用可能です。

メモ

注意: これらの方法は K8s-gatekeeper を試すためのものであり、セキュアではありません。 本番環境で使用する場合は、第5章 高度な設定 を読み、インストール前に必要な変更を行ってください。 2.1 内部ウェブフック

2.1.1 ステップ1: イメージのビルド

内部ウェブフック方式では、ウェブフック自体が Kubernetes 内でサービスとして実装されます。

必要なサービスとデプロイメントを作成するために、K8s-gatekeeper のイメージをビルドする必要があります。 以下のコマンドを実行することで、独自のイメージをビルドできます: このコマンドは、'k8s-gatekeeper:latest' というローカルイメージを作成します。

docker build --target webhook -t k8s-gatekeeper .

このコマンドは、'k8s-gatekeeper:latest'というローカルイメージを作成します。

メモ

注意: minikubeを使用している場合、'docker build'を実行する前にeval $(minikube -p minikube docker-env)を実行してください。

2.1.2 ステップ2: K8s-gatekeeperのサービスとデプロイメントを設定する

以下のコマンドを実行してください:

kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml

これによりK8s-gatekeeperが起動し、kubectl get podsを実行することで確認できます。

2.1.3 ステップ3: K8s-gatekeeperのCRDリソースをインストールする

以下のコマンドを実行してください:

kubectl apply -f config/auth.casbin.org_casbinmodels.yaml 
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml

2.2 外部ウェブフック

外部ウェブフック方式では、K8s-gatekeeperはKubernetesの外部で実行され、Kubernetesは通常のウェブサイトにアクセスするのと同じようにK8s-gatekeeperにアクセスします。 Kubernetesには、アドミッションウェブフックがHTTPSであることが必須要件となっています。 K8s-gatekeeperを試す目的で、証明書と秘密鍵のセットを提供しています(ただし、これは安全ではありません)。 独自の証明書を使用する場合は、証明書と秘密鍵の調整に関する手順について、第5章 高度な設定を参照してください。 私たちが提供する証明書は、'webhook.domain.local' 用に発行されています。

そのため、ホスト(例:/etc/hosts)を変更し、'webhook.domain.local' を K8s-gatekeeper が実行されている IP アドレスに向けてください。 その後、以下のコマンドを実行します:

2.3 Helm を介して K8s-gatekeeper をインストールする

go mod tidy
go mod vendor
go run cmd/webhook/main.go
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
kubectl apply -f config/webhook_external.yaml

2.3.1 ステップ 1: イメージをビルドする

第2.1.1章を参照してください。

2.3.2 Helm インストール

helm install k8sgatekeeper ./k8sgatekeeper コマンドを実行してください。

コマンド helm install k8sgatekeeper ./k8sgatekeeper を実行してください。

3. K8s-gatekeeperを試してみてください

3.1 Casbinモデルとポリシーの作成

モデルとポリシーを作成するには、kubectlを使用するか、私たちが提供するgo-clientを使用するかの2つの方法があります。

3.1.1 kubectlを介したCasbinモデルとポリシーの作成/更新

K8s-gatekeeperでは、Casbinモデルは「CasbinModel」と呼ばれるCRDリソースに保存されています。 その定義はconfig/auth.casbin.org_casbinmodels.yamlにあります。

example/allowed_repo/model.yamlに例があります。 以下のフィールドに注意してください:

  • metadata.name: モデルの名前 この名前は、このモデルに関連するCasbinPolicyオブジェクトの名前と同じでなければなりません。そうすることで、K8s-gatekeeperはそれらをペアリングし、エンフォーサーを作成できます。
  • spec.enable: このフィールドが "false" に設定されている場合、このモデル(およびこのモデルに関連するCasbinPolicyオブジェクト)は無視されます。
  • spec.modelText: Casbinモデルのモデルテキストを含む文字列です。

Casbinポリシーは、config/auth.casbin.org_casbinpolicies.yaml で定義を見つけることができる 'CasbinPolicy' と呼ばれる別のCRDリソースに保存されています。

example/allowed_repo/policy.yaml に例があります。 以下のフィールドに注意してください:

  • metadata.name: ポリシーの名前。 この名前は、このポリシーに関連するCasbinModelオブジェクトの名前と同じでなければなりません。そうすることで、K8s-gatekeeperはそれらをペアリングし、エンフォーサーを作成できます。
  • spec.policyItem: Casbinモデルのポリシーテキストを含む文字列です。

独自のCasbinModelおよびCasbinPolicyファイルを作成した後、以下のコマンドを使用してそれらを適用します:

kubectl apply -f <filename>

一対のCasbinModelとCasbinPolicyが作成されると、K8s-gatekeeperは5秒以内にそれを検出できるようになります。

3.1.2 go-clientを介してCasbinモデルとポリシーを作成/更新する

K8sクラスタのノード上で直接シェルを使用してコマンドを実行するのが不便な場合があることを理解しています。例えば、企業向けの自動クラウドプラットフォームを構築している場合などです。 そのため、CasbinModelとCasbinPolicyを作成および維持するためのgo-clientを開発しました。

go-clientライブラリはpkg/clientにあります。

client.goで、クライアントを作成するための関数を提供しています。

func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error) 

externalClientパラメータは、K8s-gatekeeperがK8sクラスタ内で実行されているかどうかを決定します。

model.goで、CasbinModelを作成、削除、および変更するためのさまざまな関数を提供しています。 これらのインターフェースの使用方法はmodel_test.goで確認できます。

policy.goで、CasbinPolicyを作成、削除、および変更するためのさまざまな関数を提供しています。 policy_test.goでこれらのインターフェースの使い方を調べることができます。

3.1.2 K8s-gatekeeperが機能するかどうかを試してみてください。

example/allowed_repoに正確なモデルとポリシーをすでに作成していると仮定します。 次のコマンドを試してみてください:

kubectl apply -f example/allowed_repo/testcase/reject_1.yaml

K8sがこのリクエストを拒否し、このリクエストが拒否された理由としてウェブフックが原因であることを指摘するはずです。 しかし、example/allowed_repo/testcase/approve_2.yamlを適用しようとすると、それは受け入れられます。

4. K8s-gatekeeperでモデルとポリシーを書く方法

まず、Casbinモデルとポリシーの基本文法に慣れていることを確認してください。 そうでない場合は、まずはじめにのセクションを読んでください。 この章では、あなたがすでにCasbinのモデルとポリシーについて理解していることを前提としています。

4.1 モデルのリクエスト定義

K8s-gatekeeperがリクエストを承認する際、入力は常にオブジェクトです:Admission RequestのGoオブジェクトです。 これは、エンフォーサーが常に次のように使用されることを意味します:

ok, err := enforcer.Enforce(admission)

ここで、admissionはK8sの公式go api "k8s.io/api/admission/v1"で定義されたAdmissionReviewオブジェクトです。 この構造体の定義は、このリポジトリで見つけることができます:https://github.com/kubernetes/api/blob/master/admission/v1/types.go。 詳細については、https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-responseも参照できます。

したがって、K8s-gatekeeperで使用される任意のモデルについて、request_definitionの定義は常に次のようになります:

    [request_definition]
r = obj

名前 'obj' は必須ではありません。[matchers]部分で使用される名前と一致していればよいです。

4.2 モデルのマッチャー

CasbinのABAC機能を使用してルールを記述することが想定されています。 ただし、Casbinに統合された式評価器は、マップや配列(スライス)でのインデックス付け、または配列の展開をサポートしていません。 そのため、K8s-gatekeeperはこれらの機能を実装するためのさまざまな「Casbin関数」を拡張機能として提供しています。 これらの拡張機能でもお客様のニーズを満たせない場合は、お気軽にイシューを開始するか、プルリクエストを作成してください。

Casbin関数についてご存知ない場合は、詳細についてFunctionを参照してください。

以下は拡張関数です:

4.2.1 拡張関数

4.2.1.1 アクセス

アクセスは、Casbinがマップや配列でのインデックス付けをサポートしていない問題を解決するために使用されます。 example/allowed_repo/model.yamlの例は、この関数の使用方法を示しています:

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj

このマッチャーでは、access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image")r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Image と同等です。ここで、r.obj.Request.Object.Object.Spec.Template.Spec.Containers はスライスです。

アクセスは、パラメータを持たず、単一の値を返す単純な関数も呼び出すことができます。 example/container_resource_limit/model.yaml の例はこれを示しています:

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")) >= parseFloat(p.cpu) && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","memory","Value")) >= parseFloat(p.memory)

このマッチャーでは、access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"].Value() と同等です。ここで、r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits はマップであり、Value() はパラメータを持たず、単一の値を返す単純な関数です。

4.2.1.2 accessWithWildcard

時には、このような要求があるかもしれません:配列内のすべての要素はプレフィックス "aaa" を持つ必要があります。 しかし、Casbin は for ループをサポートしていません。 accessWithWildcard と "map/slice expansion" 機能を使うことで、このような要求を簡単に実装できます。

例えば、a.b.c が配列 [aaa,bbb,ccc,ddd,eee] であるとすると、accessWithWildcard(a,"b","c","*") の結果はスライス [aaa,bbb,ccc,ddd,eee] になります。 ワイルドカード * を使用することで、スライスが展開されます。

同様に、ワイルドカードは複数回使用することができます。 例えば、accessWithWildcard(a,"b","c","*","*") の結果は [a.b.c[0][0], a.b.c[0][1], ..., a.b.c[1][0], a.b.c[1][1], ...] になります。

4.2.1.3 可変長引数をサポートする関数

Casbinの式評価器では、パラメータが配列である場合、自動的に可変長引数として展開されます。 この機能を利用して配列/スライス/マップの展開をサポートするため、配列/スライスをパラメータとして受け取るいくつかの関数も統合しています。

  • contain(): 複数のパラメータを受け取り、最後のパラメータを除くいずれかのパラメータが最後のパラメータと等しいかどうかを返します。
  • split(a,b,c...,sep,index): [splits(a,sep)[index], splits(b,sep)[index], splits(a,sep)[index], ...] を含むスライスを返します。
  • len(): 可変長引数の長さを返します。
  • matchRegex(a,b,c...,regex): 与えられたすべてのパラメータ (a, b, c, ...) が 与えられた正規表現にマッチするかどうかを返します。

これは example/disallowed_tag/model.yaml の例です。

    [matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)

accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image")["a:b", "c:d", "e:f", "g:h"] を返すと仮定します。split は可変長引数をサポートし、各要素に対して分割操作を実行するため、インデックス 1 の要素が選択されて返されます。 したがって、split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1)["b","d","f","h"] を返します。 そして、contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)p.obj["b","d","f","h"] に含まれているかどうかを返します。

4.2.1.2 型変換関数

  • ParseFloat(): 整数を浮動小数点数に解析します(比較に使用される数値は浮動小数点数に変換する必要があるため)。
  • ToString(): オブジェクトを文字列に変換します。 このオブジェクトは文字列の基本型を持っている必要があります(例えば、type XXX string という宣言がある場合の XXX 型のオブジェクト)。
  • IsNil(): パラメータが nil であるかどうかを返します。

5. 高度な設定

5.1 証明書について

Kubernetes(k8s)では、ウェブフックがHTTPSを使用することが必須です。 これを実現するためのアプローチは二つあります:

  • 自己署名証明書を使用する(このリポジトリの例ではこの方法を使用しています)
  • 通常の証明書を使用する

5.1.1 自己署名証明書

自己署名証明書を使用するということは、証明書を発行する認証局(CA)が広く知られたCAではないということです。 そのため、このCAについてk8sに知らせる必要があります。

現在、このリポジトリの例では、自作のCAを使用しており、その秘密鍵と証明書はそれぞれconfig/certificate/ca.crtconfig/certificate/ca.keyに保存されています。 ウェブフックの証明書は config/certificate/server.crt で、自己作成のCAによって発行されています。 この証明書のドメインは "webhook.domain.local"(外部ウェブフック用)と "casbin-webhook-svc.default.svc"(内部ウェブフック用)です。

CAに関する情報は、ウェブフック設定ファイルを介してk8sに渡されます。 config/webhook_external.yamlconfig/webhook_internal.yaml の両方に "CABundle" というフィールドがあり、そこにはCAの証明書のbase64エンコードされた文字列が含まれています。

証明書やドメインを変更する必要がある場合(例えば、内部ウェブフックを使用しながらk8sの別の名前空間にこのウェブフックを配置したい場合、または外部ウェブフックを使用しながらドメインを変更したい場合)、以下の手順に従うべきです:

  1. 新しいCAを生成する:

    • 偽のCAのための秘密鍵を生成する:

      openssl genrsa -des3 -out ca.key 2048
    • 秘密鍵のパスワード保護を解除する:

      openssl rsa -in ca.key -out ca.key
  2. ウェブフックサーバーのための秘密鍵を生成する:

    openssl genrsa -des3 -out server.key 2048
    openssl rsa -in server.key -out server.key
  3. 自己生成のCAを使用してウェブフックの証明書に署名する:

    • システムのopenssl設定ファイルを一時的にコピーします。 openssl version -aを実行することで設定ファイルの場所を確認できます。通常はopenssl.cnfという名前です。

    • 設定ファイル内で:

      • [req]の段落を見つけ、以下の行を追加します: req_extensions = v3_req

      • [v3_req]の段落を見つけ、以下の行を追加します: subjectAltName = @alt_names

      • 以下の行をファイルに追加します:

        [alt_names]
        DNS.2=<The domain you want>

        注意: サービス名を変更する場合は、'casbin-webhook-svc.default.svc'を実際のサービス名に置き換えてください。

    • 変更した設定ファイルを使用して証明書要求ファイルを生成します:

      openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf
    • 自作のCAを使用して要求に応答し、証明書に署名します:

      openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -extensions v3_req -extensions SAN -extfile openssl.cnf
  4. 'CABundle'フィールドを置き換えます: このフィールドを新しい証明書で更新してください。

  5. Helmを使用している場合、同様の変更をHelmチャートに適用する必要があります。

5.1.2 法的証明書

法的証明書を使用する場合、これらすべての手続きを行う必要はありません。 config/webhook_external.yamlconfig/webhook_internal.yamlの"CABundle"フィールドを削除し、これらのファイル内のドメインをあなたが所有するドメインに変更してください。