Help us understand the problem. What is going on with this article?

Open Policy Agent (OPA) でKubernetesにポリシーを強制する

1. Open Policy Agent (以下OPA)とは

Open Policy Agent(以下OPA)というのは、ポリシーエンジンです。
ポリシーエンジンは、種々のリクエストに対して、ポリシーを守ることを強制します。

opa2.png

OPAは、リクエストが届いたときに、ポリシー(会社のルール)データ(会社の組織構造など)を読み込み、リクエストの中身を精査し、OK/NGなどの返答を返します。

効果としては、複雑なリクエストやルールを解析して自動で守らせることができます。
OPAを使いこなすことで、会社や部署などの組織の決めたルールからの逸脱を防ぐことができます。

強制の仕方は2つあります。

  • OK/NG判定を下し、NG(不正)なリクエストを停止する (Validationという)
  • 不足している情報を自動で作成し追加する (Mutationという→詳細は次回)

opa1.png

他にも以下のような特徴があります。

  • Kubernetes以外にも、DockerやSSHの制御、Terraform, Envoyなどに適用できます。
  • 2020/3現在v0.17.2が公開されています。
  • REGO言語という、少しGo言語に似た、でも実行方法が異なる言語を使っています。

2. 今回の目標

e.jpeg

以下を目標として、検証を進めます。

  • Open Policy AgentをKubernetesに適用します。
  • Open Policy Agent向けの最低限のポリシーをチュートリアルに準ずる形で作成します。
  • ポリシーが機能しているか検証します。

3. OPAを試す

まずはOPAを実行する以下の環境を用意します。

  • Kubernetes (kindで用意する方法はこちら)
  • kubectl, helm

Open Policy AgentをKubernetesに組み込むためのパッケージには、以下2つあります。

  1. kube-mgmt
    • Open Policy Agentの判断をWebhookでKubernetesに伝える。
    • ポリシーはConfigMap形式でregoファイルを登録する必要がある。
    • Helmでインストール可能。
  2. Gatekeeper
    • 比較的新しい
    • Webhookの仕掛けは上記と同じだが、ポリシーやそのテンプレートをすべて独自のCRDで管理できる。
    • 2020年2月現在、まだValidatingにしか対応しておらず、Mutatingは未対応。

今回はHelmで手軽にインストールできてMutatingに対応しているkube-mgmtを採用します。

使用するHelmパッケージは、以下のレポジトリに公開されています。

helm/charts

3-1. OPAをKubernetesにインストールする

まず、OPAを起動させるNamespaceを作ります。名前をopaとします。

kubectl create namespace opa

次に、OPA起動用のHelm設定ファイルを作ります。

helm-values.yaml
# 今回は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 stable https://kubernetes-charts.storage.googleapis.com
helm repo up
helm install opa stable/opa -f helm-values.yaml --namespace opa

3-2. ポリシーを作成する

KubernetesにOPAを適用する構成を図で示すとこうなります。

opa3.png

今回は、Ingressの追加のリクエストをOPAでポリシーチェックすることとします。

この絵の通り、今回のKubernetesとOPAの構成では、

  • リクエストはKubernetesに対するIngressリソース追加のリクエストです。
  • dat`の部分もKubernetes(etcd)自身が持つ構成情報です。
  • policyには、デフォルトのポリシーmain.regoと、Ingressに関するリクエストだけに適用するポリシーingress-whitelist.regoを配置します。

今回配置するポリシーを見ていきます。

1つ目のポリシーmain.regoは、「基本的にリクエストを許可する」よう指定します。

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の内容がNamespaceannotations内で許されたホスト名だけを許可し、それ以外を禁止するポリシーを指定します。

ingress-whitelist.rego (クリックで開く)
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であることが記載されています。

namespace.yaml
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 (クリックで開く)
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 (クリックで開く)
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 (クリックで開く)
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があります。

https://play.openpolicyagent.org/

このRego言語、Prologがベースとなっているらしいのですが、考え方を押さえればスッキリ理解できるようになる気がするので、これについては次回記載したいと思います。

OPAのメリットが何なのか、少しだけ考察します。

Kubernetesからすると、OPAはあくまでポリシーエンジンの一実装であり、OPAの他にもMutatingAdmissionWebhookを自分で実装することでも同様のことができます。
では、OPAの強みは何かというと、Kubernetes以外の、データベースやネットワークなどのさまざまな部分でシステムワイドに「ポリシー」を一括して管理しなければならない、そうしたときに透明性や管理のしやすさを提供すると感じます。

例えば、SQLデータベースがあったとして、「誰々はどのテーブルにアクセスしてはいけません」というポリシーをGRANT文で書いたりします。しかし「どの列のどの行は機密情報なので誰々はアクセスしてはいけません」、といったポリシーを設定しようとすると、RDBMSごとに書き方が異なったりします。これは非常に煩雑です。
こういう適用レイヤによる実装の差異をなくすという大きな野心をOPAには感じます。これが成功(流行)したらほんとうにすごいことですね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした