1. Open Policy Agent (以下OPA)とは
Open Policy Agent(以下OPA)というのは、ポリシーエンジンです。
ポリシーエンジンは、種々のリクエストに対して、ポリシーを守ることを強制します。
OPAは、リクエストが届いたときに、**ポリシー(会社のルール)とデータ(会社の組織構造など)**を読み込み、リクエストの中身を精査し、OK/NGなどの返答を返します。
効果としては、複雑なリクエストやルールを解析して自動で守らせることができます。
OPAを使いこなすことで、会社や部署などの組織の決めたルールからの逸脱を防ぐことができます。
強制の仕方は2つあります。
- OK/NG判定を下し、NG(不正)なリクエストを停止する (Validationという)
- 不足している情報を自動で作成し追加する (Mutationという→詳細は次回)
他にも以下のような特徴があります。
- Kubernetes以外にも、DockerやSSHの制御、Terraform, Envoyなどに適用できます。
- 2020/3現在v0.17.2が公開されています。
- REGO言語という、少しGo言語に似た、でも実行方法が異なる言語を使っています。
2. 今回の目標
以下を目標として、検証を進めます。
- Open Policy AgentをKubernetesに適用します。
- Open Policy Agent向けの最低限のポリシーをチュートリアルに準ずる形で作成します。
- ポリシーが機能しているか検証します。
3. OPAを試す
まずはOPAを実行する以下の環境を用意します。
- Kubernetes (kindで用意する方法はこちら)
- kubectl, helm
Open Policy AgentをKubernetesに組み込むためのパッケージには、以下2つあります。
-
kube-mgmt
- Open Policy Agentの判断をWebhookでKubernetesに伝える。
- ポリシーはConfigMap形式でregoファイルを登録する必要がある。
- Helmでインストール可能。
-
Gatekeeper
- 比較的新しい
- Webhookの仕掛けは上記と同じだが、ポリシーやそのテンプレートをすべて独自のCRDで管理できる。
- 2020年2月現在、まだValidatingにしか対応しておらず、Mutatingは未対応。
今回はHelmで手軽にインストールできてMutatingに対応しているkube-mgmtを採用します。
使用するHelmパッケージは、以下のレポジトリに公開されています。
3-1. OPAをKubernetesにインストールする
まず、OPAを起動させるNamespace
を作ります。名前をopa
とします。
kubectl create namespace opa
次に、OPA起動用のHelm設定ファイルを作ります。
# 今回はMutating型にする
admissionControllerKind: MutatingWebhookConfiguration
# opaには、既存のポリシーのファイルパスなどを書くが、今回は新しく作るのでnull
opa: null
# mgmtには、新しいポリシーをConfigMapから読み込むかどうかを設定する
mgmt:
configmapPolicies:
enabled: true
namespaces: [opa, opa-example]
requireLabel: true
replicate:
cluster:
- v1/namespaces
namespace:
- extensions/v1beta1/ingresses
# rbacにはOPAが必要とするKubernetes上の権限を設定する
rbac:
create: true
rules:
cluster:
- apiGroups:
- "*"
resources:
- configmaps
verbs:
- get
- list
- watch
- patch
- update
- apiGroups:
- "*"
resources:
- namespaces
- ingresses
verbs:
- get
- list
- watch
sar:
enabled: true
authz:
enabled: false
この設定を用いて、OPAを起動します。
helm repo add open-policy-agent https://open-policy-agent.github.io/kube-mgmt/charts
helm repo up
helm install opa open-policy-agent/opa -f helm-values.yaml --namespace opa
3-2. ポリシーを作成する
KubernetesにOPAを適用する構成を図で示すとこうなります。
今回は、Ingressの追加のリクエストをOPAでポリシーチェックすることとします。
この絵の通り、今回のKubernetesとOPAの構成では、
- リクエストはKubernetesに対する
Ingress
リソース追加のリクエストです。 - dat`の部分もKubernetes(etcd)自身が持つ構成情報です。
- policyには、デフォルトのポリシー
main.rego
と、Ingress
に関するリクエストだけに適用するポリシーingress-whitelist.rego
を配置します。
今回配置するポリシーを見ていきます。
1つ目のポリシーmain.rego
は、**「基本的にリクエストを許可する」**よう指定します。
main.rego (クリックで開く)
package system
import data.kubernetes.admission
main = {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": response,
}
default response = {"allowed": true}
response = {
"allowed": false,
"status": {
"reason": reason,
},
} {
reason = concat(", ", admission.deny)
reason != ""
}
次に、2つ目のポリシーingress-whitelist.rego
には、Ingress
の内容がNamespace
のannotations
内で許されたホスト名だけを許可し、それ以外を禁止するポリシーを指定します。
ingress-whitelist.rego (クリックで開く)
package kubernetes.admission
import data.kubernetes.namespaces
operations = {"CREATE", "UPDATE"}
deny[msg] {
input.request.kind.kind == "Ingress"
operations[input.request.operation]
host := input.request.object.spec.rules[_].host
not fqdn_matches_any(host, valid_ingress_hosts)
msg := sprintf("invalid ingress host %q", [host])
}
valid_ingress_hosts = {host |
whitelist := namespaces[input.request.namespace].metadata.annotations["ingress-whitelist"]
hosts := split(whitelist, ",")
host := hosts[_]
}
fqdn_matches_any(str, patterns) {
fqdn_matches(str, patterns[_])
}
fqdn_matches(str, pattern) {
pattern_parts := split(pattern, ".")
pattern_parts[0] == "*"
str_parts := split(str, ".")
n_pattern_parts := count(pattern_parts)
n_str_parts := count(str_parts)
suffix := trim(pattern, "*.")
endswith(str, suffix)
}
fqdn_matches(str, pattern) {
not contains(pattern, "*")
str == pattern
}
上記2つのポリシーファイルを以下のようにConfigMap
としてKubernetesに追加します。
今回は単一のConfigMap
として追加しますが、複数のConfigMap
に分けても構いません。
ポイントは、OPAに認識させるための特別なラベル openpolicyagent.org/policy=rego
を追記することです。
kubectl create -n opa configmap policy --from-file ./main.rego --from-file ./ingress-whitelist.rego
kubectl label -n opa configmap policy openpolicyagent.org/policy=rego
kubectl get configmap -o yaml
で確認します。
kind: ConfigMap
metadata:
labels:
openpolicyagent.org/policy: rego
annotations:
openpolicyagent.org/policy-status: '{"status":"ok"}'
OPAに認識された場合には、専用のアノテーションopenpolicyagent.org/policy-status: '{"status":"ok"}'
がつきます。
これで、ポリシーを有効化できました。
3-3. ポリシーが機能しているかを検証
それでは、これらのポリシーが機能しているかを検証します。
検証用のNamespace
を作ります。
このNamespace
には、特殊なアノテーションingress-whitelist
がついており、許可すべきホスト名が*.ok.com
もしくは*.secondok.com
であることが記載されています。
apiVersion: v1
kind: Namespace
metadata:
annotations:
ingress-whitelist: "*.ok.com,*.secondok.com"
name: opa-example
kubectl apply -f test/namespace.yaml
namespace/opa-example created
正しくポリシーが機能しているかを確認するためのIngress
を追加してみます。
`ingress-ok.yaml` (クリックで開く)
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-ok
namespace: opa-example
spec:
rules:
- host: test.ok.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
`ingress-ok2.yaml` (クリックで開く)
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-ok-2
namespace: opa-example
spec:
rules:
- host: test2.secondok.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
`ingress-bad.yaml` (クリックで開く)
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-ok
namespace: opa-example
spec:
rules:
- host: signin.ng.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
それぞれhost
の部分のホスト名が以下のように違うだけです。
- host: test.ok.com
- host: test2.secondok.com
- host: test.ng.com
Namespace
に、以下のIngress
を追加します。
$ kubectl create -f test/ingress-ok.yaml
ingress.extensions/ingress-ok created
$ kubectl create -f test/ingress-ok2.yaml
ingress.extensions/ingress-ok-2 created
$ kubectl create -f test/ingress-bad.yaml
Error from server (invalid ingress host "test.ng.com"): error when creating "test/ingress-bad.yaml": admission webhook "webhook.openpolicyagent.org" denied the request: invalid ingress host "test.ng.com"
ingress-bad.yaml
のみ、ポリシーの禁止判定に抵触しているため、Ingress
追加が失敗しました。
エラー文章には、ingress-whitelist.repo
で生成したmsg
の文章が表示されていることが分かります。
このことから、ポリシーが有効に機能していることが確認できます。
まとめ
以下を実施し、OPAの基本的な動作を確認しました。
- Kubernetes上でOPAを稼働
- 最低限のポリシーを
ConfigMap
で登録 -
Ingress
追加による検証で、ポリシーが有効であることを検証
Rego言語というすこしだけ特殊なプログラミング言語でポリシーを書くのが、大きな特徴であり、同時に参入障壁を少し高めているとも感じます。
Rego言語を手軽に検証できる環境として以下のPlaygroundがあります。
このRego言語、Prologがベースとなっているらしいのですが、考え方を押さえればスッキリ理解できるようになる気がするので、これについては次回記載したいと思います。
OPAのメリットが何なのか、少しだけ考察します。
Kubernetesからすると、OPAはあくまでポリシーエンジンの一実装であり、OPAの他にもMutatingAdmissionWebhookを自分で実装することでも同様のことができます。
では、OPAの強みは何かというと、Kubernetes以外の、データベースやネットワークなどのさまざまな部分でシステムワイドに「ポリシー」を一括して管理しなければならない、そうしたときに透明性や管理のしやすさを提供すると感じます。
例えば、SQLデータベースがあったとして、「誰々はどのテーブルにアクセスしてはいけません」というポリシーをGRANT
文で書いたりします。しかし「どの列のどの行は機密情報なので誰々はアクセスしてはいけません」、といったポリシーを設定しようとすると、RDBMSごとに書き方が異なったりします。これは非常に煩雑です。
こういう適用レイヤによる実装の差異をなくすという大きな野心をOPAには感じます。これが成功(流行)したらほんとうにすごいことですね。