kubernetes/GKE 1.12.x でRBACによるアクセス制御を体験してみる
kubernetes 1.7以降、ダッシュボード(kubernetesの管理用Web UI)がフルアクセス権限をデフォルトで持たなくなり、 kubectl proxy
をしたあと http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ にアクセスすると認証画面が表示されるようになった。
また、GKEの場合は1.10以降はデフォルトでダッシュボードがインストールされない(参照: https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster)。
さらにダッシュボードの1.10.1からデフォルトではついに認証画面のSKIPボタンが表示されなくなった(が、デフォルトユーザーは最小限の権限しか持たないので、SKIPしたところで何もできない)。
GKEで新規にクラスタを立ち上げようとすると、デフォルトで「Enable legacy authorization」にチェックが入っておらず、ABACの代わりにRBACを使うことが推奨されている。
これらはすべてセキュリティ上の理由から徐々に引き締められており、これまでだましだましダッシュボードにアクセスしていた自分としてもそろそろ最低限はちゃんとしないとな、と思ったので実際にRBACをいじりつついろいろ試してみる。
クラスタのセットアップ
まずはkubernetesクラスタをGKEで立ち上げる。特に注意点などはないが、執筆時点で最新版のkubernetes 1.12.7-gke.7に変更し、甘えずに「Enable legacy authorization」のチェックは外したままとする。あとから気づいたが下の方に「Enable Kubernetes Dashboard」というオプションがあるので、これを使うとひと手間省けそうである(2019/05/04 追記: このオプションを試したところインストールされたダッシュボードは v1.8.3 だった。古いので自分でインストールした方が良さそう)。「Enable auto-repair」は必須だけど「Enable auto-upgrade」はちょっと怖いので外した。今回はテスト用のクラスタなので変更しなかったが、本番用であればメンテナンスが実施される時間帯も設定する。
ダッシュボードのインストール
前述の通り、デフォルトでダッシュボードがインストールされなくなったので、自分でデプロイする。以下、ダッシュボードのマニュアル通りにセットアップを行う。
“kubectl -n kube-system get pods | grep kubernetes-dashboard” などを実行してPodが立っていたら起動成功。この時点では権限を持ったユーザーがいないのでダッシュボードの認証画面を表示してもその先には進めない。
Service Accountを作成する
Tokenを使ってダッシュボードの認証画面でログインできるよう、管理権限を持ったService Account(ユーザー)を作成する。こちらもマニュアルに従って作成していく。
改めて http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ にアクセスして、得られたTokenを使ってダッシュボードにログインできることを確認する。
権限をしぼった Service Account を作る
先程作成したサンプルユーザーは “cluster-admin” というフルアクセス権限のClusterRoleをClusterRoleBindingで紐付けていたため、何でもできてしまって少々怖い。ということでここからはRBACを使ってもう少し権限を弱めたService Accountを作ってみようと思う。
ClusterRoleとRole、ClusterRoleBindingとRoleBinding
その前にClusterRoleとRoleの違いについて軽く触れておく。ClusterRoleはその名の通り特定のNamespaceに限定しないクラスタ全体に適用されるポリシーの定義に使う。一方でRoleはmetadataのところにNamespace名を指定することで特定のNamespaceに限定したポリシーを定義できる。
・・・と思ったんだけど、紐付け先であるService AccountはNamespace指定なしで作ると自動的にdefaultのNamespace用になるから、どちらかというとNamespace横断的に使いたいポリシーはClusterRoleで定義して、Namespace固有のポリシーはRoleにする、という表現の方が正しそう。
Read-only の Service Account
権限を弱めたアカウントとして「見るだけ」の “read-only” というアカウントを考えてみる。実現にあたって既存のClusterRole “view” が使えそうである。まずは対象となるService Accountを以下のmanifestで作成する。
apiVersion: v1
kind: ServiceAccount
metadata:
name: read-only
namespace: default
上記をYAMLファイルとして保存して “kubectl create -f sa-read-only.yaml” のようにして実行する。
もともと default
というService Accountしかなかったが、 read-only
が追加されていることを “kubectl get serviceaccount” で確かめる。
Service Accountを作成するとアクセス用のTokenがSecretとして自動的に作成される。現時点では何も権限がないので何もアクセスできない状態だが、試しに read-only
アカウントでダッシュボードにアクセスしてみる。
Tokenは “kubectl get secret | grep read-only” で正式な名前を調べて(ここでは “read-only-token-cdwdz” とする)、さらに “kubectl describe secret read-only-token-cdwdz” で表示できる。
試してみると以下のように認証は成功するが認可に失敗する状態となる。
それでは早速 “view” ClusterRole を紐付けてみる。manifestは以下。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-only-view
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: read-only
namespace: default
すると今度は表示できた。”view” にはSecretが対象のリソースに含まれていないためエラーが出ている。
ここで “view” がどのようなポリシーなのか “kubectl get clusterrole view -o yaml” で表示して眺めてみる。
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-view: "true"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2019-04-29T03:35:10Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
rbac.authorization.k8s.io/aggregate-to-edit: "true"
name: view
resourceVersion: "251"
selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/view
uid: ca52b5f5-6a2f-11e9-aa33-42010a800007
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- persistentvolumeclaims
- pods
- replicationcontrollers
- replicationcontrollers/scale
- serviceaccounts
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- bindings
- events
- limitranges
- namespaces/status
- pods/log
- pods/status
- replicationcontrollers/status
- resourcequotas
- resourcequotas/status
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- controllerrevisions
- daemonsets
- deployments
- deployments/scale
- replicasets
- replicasets/scale
- statefulsets
- statefulsets/scale
verbs:
- get
- list
- watch
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- get
- list
- watch
- apiGroups:
- batch
resources:
- cronjobs
- jobs
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- daemonsets
- deployments
- deployments/scale
- ingresses
- networkpolicies
- replicasets
- replicasets/scale
- replicationcontrollers/scale
verbs:
- get
- list
- watch
- apiGroups:
- policy
resources:
- poddisruptionbudgets
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- networkpolicies
verbs:
- get
- list
- watch
Secret以外にも許可されていないリソースがいろいろある。試しにダッシュボードのナビゲーションからRolesをクリックすると予想通りエラーとなる。
Secretをリスティングできるようにする
Secretの中身が見えるのは困るが、どのようなSecretが存在しているかくらいは見えてもよいのでは、と思うので権限を変更する。ここで “view” をコピーしてSecretのlist操作を可能にした新しいClusterRoleを作ることもできるが、せっかくポリシーを複数指定できる仕組みなので、Secretのlist操作を許可するだけのClusterRoleを作成して紐付けることで実現してみる。
manifestは以下。Secretは “core” API Groupに含まれているので “apiGroups” は “” を指定する。
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: secret-listing
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["list"]
次にこのClusterRoleを read-only
アカウントに紐付ける。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-only-secret-listing
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secret-listing
subjects:
- kind: ServiceAccount
name: read-only
namespace: default
ダッシュボードを再読込すると、さきほどまで表示されていたエラーが消えて、さらにナビゲーションからSecretsをクリックするとSecretの名前がリスト表示されるようになった。ただ、Secretの項目をクリックして詳細を表示しようよするとエラーになるのでポリシーはちゃんと働いている。
まとめ
こんな感じでRBACによってやろうと思えば相当に細かくアクセス制御することができるということがわかった。この仕組みを使えば例えばクラスタの構築時はフルアクセス権限のアカウントを使い、クラスタの様子を見守りたいときはread-onlyアカウント、CI/CD用には特定のNamespaceで特定のリソースに対する操作しか許可されていない専用アカウントを使うことができる。最初からすべてやろうとすると大変なので、まずはNamespaceごとのアカウントを作るだけでも、思わぬ事故の発生は防げそうなので手軽に始めることができる。