Virt 屋のひとりごと : マルチテナンシーと RBAC

※本記事は OpenShift Virtualization アドベントカレンダーの 18 日目の記事です。

qiita.com

皆さんこんにちは、OpenShift Virtualization とストレージを生業にしている Red Hat のうつぼ(宇都宮)です。

今現在、共通基盤で VMware を使っているお客様様はかなり多いです。4桁の VM が動く共通基盤は日本には普通にもありますし、そしてそこには3~4桁の数のユーザーさんがいらっしゃるでしょう。
またVMware ベースのクラウドサービスを提供している企業さんもいらっしゃいます。ユーザーさんも結構にいらっしゃると思います。
こういった複数ユーザーで共有する基盤ではマルチテナンシーの形態がとられるのが一般的ですが、OpenShift Virtualization でも実現できるのでしょうか?

ということで今日のテーマはマルチテナンシーです。

OpenShift Virtualization(以下 Virt)を使ってクラウドサービスのような環境を作る際に重要となるのはマルチテナンシー(複数ユーザーの収容設計)の戦略です。
本稿では、マルチテナンシーの戦略と、それを実現 / 管理するためのプラクティスについて紹介します。

2つのマルチテナンシー戦略

複数のユーザーに VM 環境を提供する場合、それぞれのユーザーが別のユーザーの VM を触れてしまうとリスクが生じます。悪意はなくても間違って別のユーザーの VM を止めてしまうとか、そういったことは普通に起こり得ます。
そのため、ユーザーごとに分離された環境を提供することが求められます。これがマルチテナンシーの基本コンセプトです。

OpenShift に限らず一般的に、複数ユーザーにマルチテナンシーの環境を提供するには、大きく分けて2つのパターンがあります。

  • パターン1 共有型 : 単一の大きなクラスタを論理的に分割して提供する
  • パターン2 専有型 : ユーザーごとに専用のクラスタを払い出す

VMware の世界で言えば、共有型は 1つの vCenter / Cluster を Resource Pool や Folder で区切って権限委譲する形、専有型はユーザーごとに vCenter / Cluster を用意する形、でしょうか。

セキュリティ要件が非常に高いシステムや、特定のハードウェアを占有して提供するサービスなどでは、パターン2 の専有型になります。
しかし、パターン1 の共有型の方がコストメリットが良く、特段厳しい要件がなければできればこちらで進めたいと考える方が多いのかなと考えます。
そこで、共有型のマルチテナンシーを OpenShift ではどのように実現するのかのポイントを超ざっくり説明します。

クラスタ共有型: 単一クラスタ内での論理分割(Project as a Tenant)

OpenShift の Project(≒ Namespace)という論理的な仕切りを「テナント」と見なすパターンです。リソース効率が最も良いというメリットがあります。

しかし、OpenShift はデフォルトでは性善説的に動くところがあり、何も設定しないと隣のテナントと普通に通信ができたりします。よってテナント間をきちんと分割するために、ネットワークの分離ユーザーの権限管理の設定が必要になります。

ネットワークの分離(NetworkPolicy)

OpenShift は OVN-Kubernetes という SDN が標準で使われることが多いですが、デフォルトでは「全 Project 間の通信は許可」されています。例えばテナント A の VM からテナント B の DB にアクセスすることもできてしまいます。
これを防ぐために、NetworkPolicy を使ってファイアウォールのようなルールを適用します。

NetworkPolicy は YAML で設定を行えます。
例えば、以下のような YAML を各テナント(Project)に適用することで、「同じ Project 内の通信は OK、それ以外からの通信は拒否」という状態を作れます。

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: deny-by-default
  namespace: tenant-a  # 対象のテナント名
spec:
  podSelector: {}
  policyTypes:
  - Ingress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-same-namespace
  namespace: tenant-a
spec:
  podSelector: {}
  ingress:
  - from:
    - podSelector: {} # 同じ Project 内の Pod (VM) からの通信のみ許可

これを基本として、後から要件に応じて NetworkPolicy を追加していくというのがよいでしょう。
「基本としてって簡単に言うけど、いちいちテナントごとにこれ書いて適用するのめんどくさい」という場合は、プロジェクトテンプレートを使うのがよいです。Project を作る際に自動的に NetworkPolicy を適用することができます。

参考:新規プロジェクトへのネットワークポリシーの追加 https://docs.redhat.com/ja/documentation/openshift_container_platform/4.20/html-single/network_security/index#nw-networkpolicy-project-defaults_default-network-policy

ユーザーの権限管理(RBAC: Role/RoleBindings)

次に、「誰が何をしてよいか」の制御です。OpenShift の権限管理は RBAC(Role-Based Access Control)ベースで行われ、RoleRoleBindings という仕組みを組み合わせて使うことで実現します。

  • Role : 「何をやっていいか」という記述(例: VMを作れる、消せる)
  • RoleBinding : Role を「誰(User / Group)」に渡すかの紐付け
標準の ClusterRole

実は、Virt をインストールすると、VM 操作に特化した 5つの ClusterRole が自動的に追加されています。まずはこれらを使うのがよいでしょう。

ClusterRole名 権限の内容 想定ユーザー
kubevirt.io:admin VM に関するフルコントロール権限。作成、削除、設定変更、コンソール接続、マイグレーションなど全てが可能。 テナント管理者
kubevirt.io:edit VM の作成や設定変更は可能。ただし、Role 自体の変更など権限管理に関わる操作は不可。 VMを作って壊す作業が多い開発担当者や運用担当者
kubevirt.io:view VM の設定情報やステータスの閲覧のみ可能。VM へのコンソール接続はできない。 監査担当や監視システムユーザ
kubevirt.io:migrate VM の Live migration を実行するための特定権限。 メンテナンス担当者(Live migration のみ許可)
kubevirt.io:default 基本的なアクセス権のみを提供するベースロールで、単体だとほぼ何もできず一般的には使われることはない。 n/a

※ちなみに Role と ClusterRole の違いは、

  • Role : 単一の Namespace でのみ有効なもので、テナントごとに設定して利用する。
  • ClusterRole : クラスタ全体で有効なもので、どのテナントでも共通して利用できる。

という違いがあります。ゆえに、OpenShift の管理者としては ClusterRole の方が便利っちゃ便利なのです。

RoleBindings

さて、これらの ClusterRole をユーザーに割り当てるためには、RoleBindings を作成します。
例えば tenant-a というテナントの管理者ユーザーに kubevirt.io:admin を割り当てるには、次のように定義します。

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: vm-admin-binding
  namespace: tenant-a
subjects:
  - kind: User
    name: "user-admin"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: kubevirt.io:admin
  apiGroup: rbac.authorization.k8s.io
カスタム Role

しかし、「一般ユーザーにコンソールは操作させたいが、VM を勝手に削除されたくない」という要件はよくあります。
上記の標準 ClusterRole だと、kubevirt.io:view ではコンソールが見れず、かといって kubevirt.io:edit だと VM を消せてしまいます。
その場合は、カスタマイズした Role を作成することで実現します。
Role は次のように、操作対象の resourceapiGroup、そして許可する操作 verbs をリストして作ります。

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: tenant-a
  name: vm-user-role
rules:
  # 閲覧のみ許可(create, delete, patch 等は持たせない)
  - apiGroups: ["kubevirt.io"]
    resources: ["virtualmachines", "virtualmachineinstances"]
    verbs: ["get", "list", "watch"]
  # コンソール接続と電源操作(起動・停止・再起動)のみ実行許可
  - apiGroups: ["subresources.kubevirt.io"]
    resources: ["virtualmachineinstances/console", "virtualmachines/start", "virtualmachines/stop", "virtualmachines/restart"]
    verbs: ["update"]

この Role を先の例のようにユーザーやグループに対して RoleBindings を作れば、「コンソールは操作できるが VM は消せない」という権限を割り当てられます。

あ、もちろん ClusterRole も同じように作ることができるので、複数のテナントで共通して与える権限を作るには ClusterRole を作るほうが効率的です。

まとめ

今回はクラスタ共有型に特化して Virt でのマルチテナンシーの実装要素を紹介しました。
特に RBAC については、基本的にはまず標準の kubevirt.io:* ClusterRole の利用を検討し、ちょうどいいのがなければカスタムする、というアプローチをおすすめします。

NetworkPolicy や Role/RoleBindings は結構奥が深く、私が紹介したのはほんの一部分です。
これを突き詰めていくと OpenShift/Kubernetes はどうやって安全性を担保しているかの動作原理に到達し、目からウロコがポロポロと落ちること請け合いです。
私はこの辺は正直まだ勉強が足りず、フナとか鯉みたいにびっしりウロコが敷き詰められているので、精進したいと思います。

ということで今日はここまで。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。