Slide 1

Slide 1 text

Open Policy Agent Gatekeeper 勉強会 2019/08/26 (2019/11/24改訂) 池添 明宏

Slide 2

Slide 2 text

目次 • OpenPolicyAgent とは • Document • Rego • Gatekeeper とは • ポリシーの作り方 • 機能紹介 2

Slide 3

Slide 3 text

Open Policy Agent とは • 軽量な汎用ポリシーエンジン • ポリシーに基づいて入力データの検証をおこなう • 特徴 • 宣言的な記述でポリシーの読み書きがしやすい • コンパイルし直すことなくポリシーの変更が可能 3

Slide 4

Slide 4 text

何に使えるの? • 例えば Kubernetes において、RBAC よりも柔軟なルールを適用でき る。 • 例: • Pod に resource フィールドの指定を強制する • 利用可能なコンテナレジストリを制限する • 特定のユーザーに一部のリソースへのアクセスを制限する 4

Slide 5

Slide 5 text

Data & Policy 5 • Data (Document) • サービスやユーザーが用意するデータ • JSON 形式 • Policy (Rule) • データに適用する検証ルール • Rego という独自言語で定義 出展: https://www.openpolicyagent.org/docs/latest/how-does-opa-work/

Slide 6

Slide 6 text

Document の種別 • Base Documents • サービスやユーザーが用意した データ • Virtual Documents • Rule を適用した結果データ • Virtual Documents にさらに Rule を適用することも可能 6 出展: https://www.openpolicyagent.org/docs/latest/how-does-opa-work/

Slide 7

Slide 7 text

Documents の構造 • data • 検証に利用する静的なデータ • Rule を適用した結果データ • input • 検証対象の入力データ 7 出展: https://www.openpolicyagent.org/docs/latest/how-does-opa-work/

Slide 8

Slide 8 text

Rule から Document へのアクセス • import で Document にアクセス できる • 例えば opa.examples.allow とい うルールを適用した結果の Virtual Document は、data.opa.example s.allow に入っている。 8 import data.opa.examples.allow import data.servers rule { server := servers[_] input.server == server allow[server] }

Slide 9

Slide 9 text

Rules • Rego という独自言語でデータの検証ルールを記述する • Rego • Datalog という Prolog 系言語をインスパイア • 宣言的な記述が可能 • The Rego Playground で試してみよう • https://play.openpolicyagent.org/ 9

Slide 10

Slide 10 text

Rego による Rule の定義 10 # BODYの条件を満たした場合 # HEADの値を返す rule_name { } # 条件を満たさなかった場合 # undefinedを返す # 配列の場合は空配列を返す # HEADの省略は =trueと同じ rule1 { input.x < input.limit } # 固定値を返す rule2 = "abc" { input.x < input.limit } # 変数を返すことも可能 rule3 = x { x := input.x x < input.limit }

Slide 11

Slide 11 text

AND 条件と OR 条件 11 # 羅列するとAND条件 rule_name { } # OR条件は同一名のルールを # 複数個用意する rule_name { } rule_name { }

Slide 12

Slide 12 text

Go 言語と比較してみる 12 # Rego default allow = false allow = true { input.size != 0 input.size < data.limit } # Go func allow() bool { if input.size == 0 { return false } if input.size >= data.limit { return false } return true }

Slide 13

Slide 13 text

else • 同一名の複数ルールは適用順序が考 慮されない。矛盾したルールは記述 することができない。 • 順序を考慮する場合は else キーワー ドを利用する。 13 rule_name { } else { }

Slide 14

Slide 14 text

繰り返し 14 containers = { "ap": { "image": "ubuntu:18.04" }, "db": { "image": "mysql:latest" }, } # latestイメージを抽出してkeyを返す rule[key] = image { image := containers[key].image endswith(image, ":latest") } numbers = [1, 2, 3, 4, 5] # 偶数だけを抽出 rule[x] { x := numbers[_] x % 2 == 0 }

Slide 15

Slide 15 text

For Any, For All 15 # For All # 条件を逆にして否定する positive { not isNegative } isNegative { x := numbers[_] x < 0 } # 内包表記 positives { negative := [x | x := numbers[_]; x < 0] count(negatives) == 0 } # For Any numbers = [1, 2, 3, 4, 5] # 1つでも条件に一致すればtrue rule { x := numbers[_] x >= 0 }

Slide 16

Slide 16 text

Go 言語と比較してみる 16 # Rego images = [ "quay.io/cybozu/ubuntu", "docker.io/nginx", "localhost/nginx" ] repos = [ "quay.io", "docker.io" ] rule[img] { img := images[_] rep := repos[_] startswith(img, rep) } # Go func rule() []string { var result []string for _, img := range images { for _, rep := range repos { if strings.HasPrefix(img, rep) { result = append(result, img) } } } return result }

Slide 17

Slide 17 text

関数定義 • ルールと似たような記法で関数 を定義することができる。 • 関数は引数を受け取ることがで きる。 • ルールのように Virtual Document は生成されない 17 starts_with_foo(s) { startswith(s, "foo") } remove_prefix_foo(s) = r { starts_with_foo(s) r := substring(s, 3, -1) }

Slide 18

Slide 18 text

組み込み関数 • 集計: count(), sum(), sort(), all(), any() • 文字列: contains(), startswidt(), endswith(), replace(), split(), sprintf(), substring() • 正規表現: re_match(), regex.split() • 型判定: is_number(), is_string(), is_set() • エンコーディング: json.marshal()/unmarshal(), yaml.marshal()/unmarshal() • そのほか Token Signing/Verification, 時間, HTTP通信 などなど 18

Slide 19

Slide 19 text

集合演算 • 集合を扱うことができる • 和集合、差集合、積集合などの 計算が可能 • 空集合は {} ではなく set() 19 resources = { "pod", "service", "deployment", "node" } apps = { "pod", "deployment" } hello[x] { # 差集合の計算 x := resources - apps }

Slide 20

Slide 20 text

コマンドラインツール • バイナリダウンロード • https://github.com/open-policy-agent/opa/releases • 文法チェック • opa check hoge.rego • 実行 • opa eval --input input.json --data hoge.rego –format pretty "data.query" • フォーマット • opa fmt -w hoge.rego 20

Slide 21

Slide 21 text

テスト • with xxx as yyy 構文を利用して、 テスト対象のルールに任意の入力 を与えることができる。 • 下記のコマンドでテスト実行 • opa test -v . 21 package k8s.test_admission import data.k8s.admission test_deny { input1 := { "review": { "namespace": "kube-system" }, "parameters": { "systemNamespaces": [ "kube-system", "kube-public" ] } } # k8s.admission.violation というルールをテスト res := admission.violation with input as input1 count(res) > 0 }

Slide 22

Slide 22 text

Gatekeeper • Open Policy Agent を Kubernetes で使えるようにしたもの • Kubernetes の Admission Controller Webhook による呼び 出し • ポリシーを Custom Resource と して定義可能 • 共通処理をライブラリとして利用 可能 22 出展: vhttps://kubernetes.io/blog/2019/08/06/opa- gatekeeper-policy-and-governance-for-kubernetes/

Slide 23

Slide 23 text

Admission Controller Webhook • Kubernetes の拡張機能のひとつ • Kubernetes のリソースを作成、更新、削除するタイミング で外部の Webhook API を呼び出す • Validating: リソースが条件を満たしているかチェック • Mutating: リソースの一部を書き換える • Gatekeeper v3 では未対応 (対応中) 23

Slide 24

Slide 24 text

Gatekeeper の歴史 • Gatekeeper v1 • OpenPolicyAgent + kube-mgmt • Gatekeeper v2 • OpenPolicyAgent + kube-mgmt + kube-policy-controller • Gatekeeper v3 • kubebuilder を利用して全面刷新 24

Slide 25

Slide 25 text

動かしてみよう • https://github.com/zoetrope/gatekee per-demo • kind をセットアップ • kind create cluster --name gatekeeper --config cluster.yaml • kubectl apply -f https://raw.githubuse rcontent.com/open-policy-agent/gate keeper/master/deploy/gatekeeper.ya ml 25 # cluster.yaml kind: Cluster apiVersion: kind.sigs.k8s.io/v1alpha3 kubeadmConfigPatches: - | apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration metadata: name: config apiServer: extraArgs: "enable-admission-plugins": "ValidatingAdmissionWebhook" nodes: - role: control-plane - role: worker

Slide 26

Slide 26 text

動かしてみよう • 特定のレジストリ以外のコンテナイメージの利用を禁止するポリ シーを用意する • ポリシーを記述した ConstraintTemplate カスタムリソースを作成 • ポリシーの適用対象とパラメータを記述した Constraint カスタムリ ソースを作成 26

Slide 27

Slide 27 text

カスタムリソース • ConstraintTemplate • Gatekeeper が Custom Resource Definition を用意している • Constraint の名前とスキーマを定義 • ポリシーを Rego で記述 • Constraint • ConstraintTemplate から動的に作られる Custom Resource Definition • ポリシー適用対象リソースとパラメータを指定 27

Slide 28

Slide 28 text

ConstraintTemplate 28 apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: k8sallowedrepos spec: crd: spec: names: kind: K8sAllowedRepos # Constraint の Custom Resource 名 validation: # `parameters` フィールドのスキーマ openAPIV3Schema: properties: repos: type: array items: type: string targets: # 後述

Slide 29

Slide 29 text

ConstraintTemplate 29 apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: k8sallowedrepos spec: crd: # 省略 targets: - target: admission.k8s.gatekeeper.sh # 固定 rego: | package k8sallowedrepos violation[{"msg": msg}] { container := input.review.object.spec.containers[_] satisfied := [good | repo = input.parameters.repos[_]; good = startswith(container.image, repo)] not any(satisfied) msg := sprintf("container <%v> has an invalid image repo <%v>", [container.name, container.image]) }

Slide 30

Slide 30 text

Constraint 30 apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sAllowedRepos metadata: name: repo-is-cybozu spec: match: # 対象リソースを指定 kinds: - apiGroups: [""] kinds: ["Pod"] parameters: # パラメータの指定。rego から input.parameters としてアクセス可能 repos: - "quay.io/cybozu/"

Slide 31

Slide 31 text

入力データ 31 { "review": { # AdmissionReview "uid": "705ab4f5-6393-11e8-b7cc-42010a800002", "kind": {"group":"apps","version":"v1","kind":"Pod"}, "resource": {"group":"apps","version":"v1","resource":"pods"}, "operation": "CREATE", "userInfo": { "username": "admin", "groups": ["system:authenticated","system:masters"], }, "object": { "apiVersion":"v1","kind":"Pod",... }, }, # Constraintで指定したパラメータ "parameters": {"repos": ["quay.io/cybozu",...]} }

Slide 32

Slide 32 text

検証してみよう $ kubectl apply -f nginx.yaml Error from server ([denied by repo-is-cybozu] container has an invalid image repo ): error when creating "nginx.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by repo-is-cybozu] container has an invalid image repo 上記のようなメッセージが表示されたら成功 32 # nginx.yaml apiVersion: v1 kind: Pod metadata: labels: run: nginx name: nginx namespace: default spec: containers: - name: nginx image: nginx:latest

Slide 33

Slide 33 text

データの同期 • Kubernetes クラスタの特定リ ソースを Rego から利用できるよ うにする。 • client-go/informer により結果を キャッシュ。 • Audit 機能や namespaceSelector を利用するときにも必要。 33 apiVersion: config.gatekeeper.sh/v1alpha1 kind: Config metadata: name: config namespace: "gatekeeper-system" spec: sync: syncOnly: - group: "" version: "v1" kind: "Namespace" - group: "" version: "v1" kind: "Pod"

Slide 34

Slide 34 text

同期データの利用方法 • Cluster-scoped Resource • data.inventory.cluster[][][] • Namespaced Resource • data.inventory.namespace[][][][] 34 allPods[name] = pod { # 全namespaceのPodを取得 pod := data.inventory .namespace[_][_]["Pod"][name] }

Slide 35

Slide 35 text

Audit • ValidatingAdmissionWebhook はリソースの作成、更新、削除など のタイミングで実行される • Audit 機能を利用すると、定期的にポリシーのチェックが実施される • 検証対象のリソースは同期設定しておく必要ある • Constraint リソースの Status に検証結果が保存される 35

Slide 36

Slide 36 text

トレース • トレース機能を利用すると、検証に 利用した入力データや検証結果がロ グに出力される • 大量にログが出力されるので、デ バッグ用途での利用を推奨 • 正直トレースの読み方がわからない 36 apiVersion: config.gatekeeper.sh/v1alpha1 kind: Config metadata: name: config namespace: "gatekeeper-system" spec: validation: traces: - user: "kubernetes-admin" kind: group: "" version: "v1" kind: "Pod" dump: "All"

Slide 37

Slide 37 text

Rego のテスト • ConstraintTemplate の中に Rego が埋め込まれてるのでテストしにくい。 • ConstraintTemplate のベースファイルに Rego を埋め込む Kustomize プラグイ ンをつくってみた。 • https://github.com/zoetrope/ConstraintTemplateGenerator 37

Slide 38

Slide 38 text

おしまい(所感) • 最初はとっつきにくいかもしれないけど、慣れればそれなりに書き やすい。 • デバッグしにくいので、トレース周りは改善してほしい。 • argo-cd と相性悪いのをなんとかしたい。 • パフォーマンスがよくないかも? • https://github.com/open-policy-agent/gatekeeper/issues/266 38