Kubernetes MCPサーバーの権限設計 — 本番クラスタに読み取り専用で繋ぐ
本番クラスタにKubernetes MCPサーバーを繋ぐ前提で、専用ServiceAccountの作成からTOMLでのアクセス制御までを手順化します。
Kubernetes MCPサーバーを個人の開発クラスタで試したあと、本番クラスタに繋ぐ段になると事情が変わります。既定の接続方法は自分のkubeconfigをそのまま使うため、AIエージェントに管理者権限そのものを渡すことになるからです。専用のServiceAccountを読み取り専用のRBACロールに紐付け、--read-onlyと組み合わせるのが最低ラインです。
なぜkubeconfigをそのまま渡してはいけないか
~/.kube/configには、多くの場合クラスタ管理者権限に近いユーザー認証情報が入っています。MCPサーバーにこのファイルを渡すと、ツール呼び出しの権限はあなたの権限そのものになります。プロンプトインジェクションで意図しない削除コマンドが実行されるリスクや、単純な指示ミスでNamespaceごとPodを消してしまうリスクは、この権限の大きさに比例します。
最小権限の原則に沿うなら、専用のID(ServiceAccount)を用意し、そのIDにだけ必要最小限の権限を与えます。Kubernetes MCPサーバー自体も、公式ドキュメントで本番構成として専用ServiceAccountの作成を推奨しています。
読み取り専用のServiceAccountを作る
まずMCPサーバー専用のNamespaceとServiceAccountを作成します。
kubectl create namespace mcp
kubectl create serviceaccount mcp-viewer -n mcp権限の与え方は2通りあります。クラスタ全体を読み取り専用にするか、特定のNamespaceだけに絞るかです。
# クラスタ全体を読み取り専用にする(組み込みのviewロールを使う)
kubectl create clusterrolebinding mcp-viewer-crb \
--clusterrole=view \
--serviceaccount=mcp:mcp-viewer
# 特定Namespaceだけに絞る場合はこちら
kubectl create rolebinding mcp-viewer-rb \
--role=view \
--serviceaccount=mcp:mcp-viewer \
-n mcp権限が意図通りかはkubectl auth can-iで確認できます。
kubectl auth can-i list pods \
--as=system:serviceaccount:mcp:mcp-viewer --all-namespacesyesが返れば、クラスタ全体でPod一覧の読み取りが許可されている状態です。組み込みのviewClusterRoleはSecretの中身までは読めない設計になっているため、読み取り専用にしてもSecretの値が漏れる心配は基本的にありません。
専用トークンとkubeconfigを組み立てる
次に、このServiceAccount用の期限付きトークンを発行します。kubectl create tokenはKubernetes 1.24以降で使えるTokenRequest API経由のコマンドです。
TOKEN="$(kubectl create token mcp-viewer --duration=2h -n mcp)"このトークンを使い、管理者kubeconfigとは別ファイルの専用kubeconfigを作ります。
API_SERVER="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')"
KUBECONFIG_FILE="$HOME/.kube/mcp-viewer.kubeconfig"
kubectl config --kubeconfig="$KUBECONFIG_FILE" set-cluster mcp-viewer-cluster \
--server="$API_SERVER" --embed-certs=true
kubectl config --kubeconfig="$KUBECONFIG_FILE" set-credentials mcp-viewer \
--token="$TOKEN"
kubectl config --kubeconfig="$KUBECONFIG_FILE" set-context mcp-viewer-context \
--cluster=mcp-viewer-cluster --user=mcp-viewer
kubectl config --kubeconfig="$KUBECONFIG_FILE" use-context mcp-viewer-context
chmod 600 "$KUBECONFIG_FILE"証明書データの埋め込みが必要な場合は、CAデータをbase64デコードして一時ファイルに書き出し、--certificate-authorityに渡す手順が公式ドキュメントに載っています。トークンは2時間で失効するため、長時間の運用にはトークンの自動更新か、有効期限を伸ばした運用ルールが必要です。失効後はkubectl create tokenを再実行し、set-credentialsでトークンだけ差し替えます。
Claude Codeに--read-onlyで接続する
専用kubeconfigができたら、--read-onlyフラグを付けてClaude Codeに追加します。
claude mcp add-json kubernetes-mcp-server \
'{"command":"npx","args":["-y","kubernetes-mcp-server@latest","--read-only"],"env":{"KUBECONFIG":"'${HOME}'/.kube/mcp-viewer.kubeconfig"}}' \
-s user--read-onlyは、readOnlyHint=trueが付いたツールだけを公開し、作成・更新・削除系の操作をサーバー側で遮断します。RBACによる権限制限とこのフラグは独立した2層です。片方だけに頼らず両方を重ねておくと、RBAC設定に穴があってもMCPサーバー側で書き込みを止められます。
クラスタ内にMCPサーバー自体をデプロイする
ローカル端末で専用kubeconfigを保持する構成には、トークンファイルがローカルディスクに残るという弱点があります。MCPサーバーをクラスタ内のPodとして動かせば、この弱点を避けられます。
Podの中で起動したKubernetes MCPサーバーは、in-clusterの実行環境を自動検知し、そのPodに紐づくServiceAccountのトークンを自動的に使います。手元でトークンを発行してファイルに書き出す手順そのものが不要になり、トークンの取り扱いミスによる漏えいリスクも下がります。DeploymentのServiceAccountに、先ほど作ったmcp-viewerを指定するだけです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-mcp-server
namespace: mcp
spec:
template:
spec:
serviceAccountName: mcp-viewer
containers:
- name: kubernetes-mcp-server
args: ["--read-only", "--port", "8080"]この構成が既定で見ているのは同じクラスタのAPIサーバーです。別クラスタを見に行かせたい場合は、kubeconfigとcluster_provider_strategy = "kubeconfig"の両方を明示しないとin-cluster検知が優先されてしまいます。片方だけ指定すると失敗するので、外部クラスタ用のkubeconfigはSecretとしてマウントし、2つの設定を必ずセットで渡します。
TOML設定でさらに絞り込む
CLIフラグより細かい制御が要るなら、TOML設定ファイルを使います。特定リソース種別へのアクセスを丸ごと拒否するdenied_resourcesは、Secretのような機密性の高いリソースを追加でブロックするのに向いています。
read_only = true
toolsets = ["core", "config"]
[[denied_resources]]
group = ""
version = "v1"
kind = "Secret"
[[denied_resources]]
group = "rbac.authorization.k8s.io"
version = "v1"
kind = "ClusterRoleBinding"RBAC自体がviewロールでSecretの中身を隠していても、この設定を重ねておけばRBAC側の設定ミスをもう1段吸収できます。破壊的操作だけを止めたいならdisable_destructive = trueも選べますが、read_onlyが有効なときはこちらは効果を持ちません。
削除のような特定操作だけ確認を挟みたい場合はconfirmation_rulesが使えます。
confirmation_fallback = "deny"
[[confirmation_rules]]
verb = "delete"
namespace = "kube-system"
message = "kube-system内の削除操作です。"対応していないMCPクライアントで確認プロンプトが出せない場合、confirmation_fallbackが挙動を決めます。denyにしておけば、確認できない環境では自動的に操作を止める安全側の動作になります。
ツール単位で許可・禁止を切り替えたいだけなら、enabled_tools(許可リスト)とdisabled_tools(拒否リスト、許可リストの後に適用)も使えます。「Pod関連のツールだけ使わせたい」といった細かい要求には、toolsets単位の粗い制御よりenabled_toolsの方が向いています。
enabled_tools = ["pods_list", "pods_get", "pods_log"]validation_enabled = trueを追加すると、書き込み系のリクエストがKubernetes APIに届く前にスキーマ検証とSelfSubjectAccessReviewによるRBAC事前チェックが走ります。権限不足やマニフェストの不備を、実際にAPIへ送信する前のタイミングでエラーとして返せるため、読み取り専用構成に加えてこの層を有効にしておくと、万一書き込みが通る設定ミスをしても実害の手前で止めやすくなります。
複数人からHTTPで使うときの認証設計
チーム共有のMCPサーバーとしてHTTPモードで公開する場合は、require_oauthでOIDC認証を必須にできます。KeycloakやMicrosoft Entra IDと連携し、接続してきたユーザーのトークンをそのまま検証する構成が一般的です。個人利用のstdio接続と違い、HTTPモードは誰が呼んでいるかを検証する層が別途必要になる点を忘れないでください。
require_oauthだけでは、検証したユーザーの権限をKubernetes側にどう渡すかが決まりません。ここはcluster_auth_modeが担い、passthroughならOIDCトークンをそのままKubernetes APIへ渡してユーザー本人の権限で動かし、kubeconfigならサーバー側の単一のkubeconfig権限で全ユーザーの操作を実行します。検証・開発用に認証自体を省略したい場合はskip_jwt_verificationがありますが、本番のHTTP公開では使わない設定です。
--read-only・disable_destructive・denied_resourcesの効き方の違い
3つの制限はどれも「書き込みを止める」ように見えて、効く階層が異なります。
| 設定 | 止めるもの | 効かないケース |
|---|---|---|
--read-only | 止めるもの作成・更新・削除ツール全般を非公開にする | 効かないケースリソース種別を選んで許可することはできない |
disable_destructive | 止めるもの削除・破壊的更新のみを止める(更新自体は通す) | 効かないケースread_onlyが有効だとこの設定自体が無意味になる |
denied_resources(TOML) | 止めるもの特定のGroup/Version/Kindへのアクセスを丸ごと拒否 | 効かないケース許可した種別への読み書きまでは制限しない |
本番クラスタでは--read-onlyを基本にしつつ、Secretのような機密リソースはdenied_resourcesで二重に塞ぐ構成が堅実です。似た考え方はClaude Codeのpermission・サンドボックス設計にも共通しており、AIエージェントに渡す権限は常にツール単位ではなく操作単位で絞り込みます。
よくあるつまずき
- トークンが2時間で切れて動かなくなる:
--durationを延ばすか、set-credentialsでトークンを再発行する運用を先に決めておきます。手動更新を忘れがちなら、CI経由でのトークン更新ジョブを検討してください viewロールなのに一部リソースが見えない:viewClusterRoleはSecretと一部のCustomResourceを除外する設計です。見えないこと自体が正常な挙動である場合が多く、必要なら別途Roleを組みますkubectl auth can-iがnoを返す: RoleBinding/ClusterRoleBindingのServiceAccount名やNamespaceが実際の値と一致しているか確認します。system:serviceaccount:<namespace>:<name>の形式が正確でないと判定がずれます- TOMLを変更したのに反映されない: 設定ファイルの再読み込みにはプロセスへの
SIGHUP送信が必要ですが、これは--configまたは--config-dirを指定して起動したプロセスに限られます。指定なしで起動した場合、SIGHUPは無視されるためkill -HUP <PID>を送っても何も起きません。まず起動コマンドに設定ファイルの指定があるか確認し、あればプロセスIDを調べてkill -HUP <PID>を実行するか、サーバーを再起動します。なお、kubeconfigやクラスタ接続関連の設定、port・bind_address・metrics_port・tls_cert・tls_keyはSIGHUPでは反映されず、いずれも再起動が必要です
検証や一時利用が終わったら片付ける
検証用に作ったServiceAccountとRBACバインディングは、使い終えたら残さず削除します。放置したServiceAccountは、トークンさえ再発行できれば誰でもクラスタの読み取りに使える踏み台になりかねません。
kubectl delete clusterrolebinding mcp-viewer-crb
kubectl delete serviceaccount mcp-viewer -n mcp
rm "$HOME/.kube/mcp-viewer.kubeconfig"継続利用する場合も、トークンの有効期限を短く保ち、kubectl auth can-iでの棚卸しを定期的に行うと、権限が意図せず広がっていないかを確認できます。
よくある質問
--read-onlyだけで十分ですか、RBACの絞り込みも必要ですか
--read-onlyはMCPサーバー内部のツール公開範囲を絞るだけで、渡したkubeconfigの権限そのものは変えません。kubeconfigが管理者権限のままなら、MCPサーバーの外側(例えば別のクライアントやスクリプト)からは書き込みが通ってしまいます。RBACでServiceAccountの権限自体を絞ることが土台で、--read-onlyはその上に重ねる追加の防御層です。
読み取り専用にしてもクラスタのSecretは安全ですか
組み込みのviewClusterRoleはSecretのdataフィールドを除外していますが、カスタムRoleを自作する場合は同じ配慮が必要です。心配ならdenied_resourcesでSecretそのものへのアクセスを明示的に拒否しておくと、RBAC側の設定ミスをもう1段吸収できます。
まとめ
本番クラスタへの接続は、個人開発クラスタと同じ設定のまま持ち込まないのが原則です。専用ServiceAccountとRBAC、--read-onlyフラグ、TOMLのdenied_resourcesを重ねておけば、どこか1層に穴があっても被害を抑え込めます。まずKubernetes MCPサーバーの基本的な追加手順で挙動を把握したうえで、本番向けにはこの権限設計を先に済ませてください。