TT Lab
はじめる
学ぶ 学習パス コース

CKA — Kubernetes管理者

認証と認可は別の層だ

TT Labで続きを見る

一言でいうと

apiserverに入ってきたリクエストは、認証(あなたは誰か) → 認可(その人はこれをしてよいか) → アドミッション(その内容はルールに合っているか)の順に通過します。RBACは2番目の層1つであり、1番目の層は、証明書とトークンの世界です。この2つを混ぜると、診断がもつれます。

なぜ必要なのか

Kubernetesにはユーザーオブジェクトがありません。kubectl get usersはありません。なぜなら、apiserverは身元の発行者ではなく、検証者だからです。身元の出どころは外にあります。

そのため、「そのユーザーを削除してください」というリクエストは成り立ちません。証明書を失効させるか、バインディングを削除するしかありません。

認可側は、4種類のオブジェクトだけです。

ネームスペースの範囲 クラスターの範囲
権限の定義 Role ClusterRole
権限の付与 RoleBinding ClusterRoleBinding

ここに、紛らわしい組み合わせがちょうど1つあります。RoleBindingがClusterRoleを参照することは許可されており、その場合、ClusterRoleのルールは、そのRoleBindingがあるネームスペースの中でのみ適用されます。view、editのような組み込みのClusterRoleを、ネームスペースごとに再利用する標準的な方法です。逆に、ClusterRoleBindingを使うとクラスター全体に広がるので、ネームスペースの境界を守ろうとして、ここで間違えることが多くあります。

RBACは累積するだけです。拒否ルールがありません。どのバインディングであれ、一度許可すれば許可です。そのため、「なぜこのアカウントがこれをできるのか」を追跡するときは、すべてのバインディングを洗い出す必要があり、kubectl auth can-i --as=が、その近道です。このコマンドは、SubjectAccessReviewを作成してapiserverに尋ねるだけで、実際のリクエストは送りません。副作用なしに、安全に確認できます。

どう動くのか

Aggregated ClusterRoleは、ルールを直接書かないClusterRoleです。aggregationRuleにラベルセレクターを書くと、コントローラーが、そのラベルの付いたほかのClusterRoleたちを見つけて、rulesを埋めます。CRDで新しいリソースを追加したときに、既存のview/editのロールに、ラベル1つで載せられる拡張ポイントです。ここに手でrulesを書くと、コントローラーが上書きします。

automountServiceAccountTokenは、PodにAPIトークンを入れるかどうかを決めます。デフォルトでは入れます。そうすると、APIをまったく使わないnginxのPodにもトークンが入り、コンテナが侵害されると、そのトークンがそのままクラスターへのアクセス権になります。ServiceAccountやPodのスペックでfalseにすると、kube-api-access-*の投影ボリュームそのものが注入されません。

現場での姿

事例1: CA秘密鍵の露出期間を2時間に絞ります。ホームラボを3ノードから7ノードに増やすとき、ワーカーの参加とコントロールプレーンの参加の違いが、認証の構造をそのまま見せてくれました。ワーカーは、トークン1つで終わりです。

kubeadm token create --print-join-command

コントロールプレーンは違います。新しいノードも証明書を発行する主体になる必要があるので、CA秘密鍵が必要です。kubeadmは、この機微なファイルをscpで運ばせず、kubeadm init phase upload-certs --upload-certsで、CA鍵の束を暗号化してクラスター内のSecretとしてアップロードします。参加コマンドに渡す64桁のcertificate-keyが、その暗号文を解く対称鍵です。

そしてこのSecretは、2時間後に自動削除されます。暗号化されているとはいえ、クラスターの信頼の根が中に入っているので、露出期間を最小化しようとする設計です。期限が切れたら、upload-certsを再実行すればよく、既存のクラスターには影響がありません。認証の核心は、このように「信頼の根を、どれだけ短く露出するか」です。

事例2: 証明書のSANがそのままアクセス権になります。同じクラスターで、コントロールプレーンノードを3つに増やしたのに、最初のノードが落ちると、誰もつなげませんでした。apiserverの証明書のSANに、残りの2ノードのIPがなかったからです。RBACをどれだけうまく組んでも、TLS検証を通過できなければ、認可の層にすら到達できません。逆に、この性質を逆手に取り、落ちたノードのIPを生きているノードのインターフェースに載せると、証明書の再発行もkubeconfigの修正もなく、すべてのクライアントがそのまま再接続します。クライアントが検証するのはノードではなく、接続したアドレスがSANにあるかだからです。

事例3: 権限ではなく設定が問題のときもあります。GPU Operatorを導入しているときに、Podがすべて、Initで止まったことがあります。kubectl get runtimeclassにはnvidiaが問題なくありましたが、ノードのcontainerdの設定には、nvidiaが1文字もありませんでした。ラベルはKubernetesのオブジェクトとして存在し、実体はノードになければならないのに、後者がなかったのです。オブジェクトがあることと、実際に動作することは別の命題です。認可の問題を診断するときにも、同じ習慣が必要です。can-iがyesを返したなら、その次は、証明書、ネットワーク、アドミッションWebhookの順に、下っていく必要があります。

次のラボですること

ServiceAccountを作成し、Role/RoleBindingで読み取り権限だけを与え、kubectl auth can-i --as=で、できることとできないことの両方を確認します。そのあと、ClusterRole/ClusterRoleBindingでクラスタースコープのリソースを開き、Aggregated ClusterRoleを構成し、最後に、トークンがそもそも注入されないPodを作成します。