おい、ちょっと手を止めてくれ。Kubernetesクラスターの構築、最近サクッと終わらせて満足してないか? 「とりあえず動いたから良し」なんて言って、デフォルト設定のまま本番稼働させているとしたら、それはもう自らハッカーに合鍵を渡しているようなものだ。
数々のインシデント現場を見てきたが、攻撃者はきれいなフロントエンドの脆弱性なんて狙っちゃいない。彼らが真っ先に探すのは、設定のミスマッチや認証の抜け穴だ。そして、その最たる標的が Kubelet なんだよ。
今回は、Kubeletの認証・認可の仕組み、そして絶対に外してはいけない --anonymous-auth=false について、現場のリアルな目線で徹底的に叩き込んでやる。
—
1. なぜKubeletの匿名アクセスが「死活問題」なのか
Kubeletは、Kubernetesの各ワーカーノード上で常駐し、コントロールプレーンからの指示を受けてコンテナを起動・管理するエージェントだ。こいつは自身のAPIをデフォルトで 10250 ポートあたりに公開している。
昔のKubernetesや、いくつかのディストリビューションのデフォルト設定では、このKubeletのエンドポイントが 「認証なし(匿名)」 でアクセスを許容しているケースがあった。これが何を意味するか分かるか?
攻撃者がポートスキャンで 10250 を見つけたら、APIサーバーを通さずに直接Kubeletと会話できてしまう。つまり、クラスターの制御権をバイパスして、ノード上で任意のコンテナ(Pod)を勝手に立ち上げたり、他のコンテナのログや環境変数を覗き見たりすることが可能になるのだ。
実際に狙われる攻撃シナリオ(PoCの概念)
攻撃者は、認証のないKubelet APIに対して直接リクエストを投げる。例えば、Pythonを使って以下のようなリクエストを送るだけで、ノード上で任意のコマンド実行に近い特権Podをデプロイできてしまう。
import requests
import urllib3
# 警告を非表示にする(検証用スクリプト)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ターゲットのワーカーノードのKubeletポート
target_node = "https://192.168.1.50:10250"
# 認証情報を一切持たずに、稼働中のポッド一覧を取得を試みる
try:
response = requests.get(f"{target_node}/pods", verify=False)
if response.status_code == 200:
print("[!] 脆弱性検知: Kubeletへの匿名アクセスが許可されています!")
pods = response.json()
for pod in pods.get("items", []):
print(f" - Running Pod: {pod['metadata']['name']}")
else:
print(f"[-] 安全です。ステータスコード: {response.status_code}")
except Exception as e:
print(f"[x] エラーが発生しました: {e}")
もしこのスクリプトがポッドの一覧を返してきたら、そのクラスターはもう「抜かれている」と同然だ。環境変数に埋め込まれたAWSのシークレットキーやデータベースのパスワードなど、機密情報が丸見えになる。
—
2. 鉄壁のハーデニング:Kubeletの認証・認可を強制する
この悪夢を防ぐためのアプローチは明確だ。やるべきことは以下の2つ。
1. 匿名アクセスを完全に遮断する(--anonymous-auth=false)
2. Kubelet自身のWebhook認証とRBAC認可を有効化する
これらを確実に行うための設定ファイルをみていこう。
Kubelet設定ファイル(kubelet-config.yaml)の模範解答
モダンなKubernetes環境では、コマンドライン引数ではなく、Kubeletのコンフィグファイル(YAML)で管理するのがベストプラクティスだ。以下の設定をすべてのワーカーノードに適用してほしい。
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 0.0.0.0
port: 10250
# 【最重要】匿名アクセスを完全に拒否する
authentication:
anonymous:
enabled: false
webhook:
enabled: true
cacheTTL: 2m
x509:
clientCAFile: "/etc/kubernetes/pki/ca.crt"
authorization:
mode: Webhook
# プレーンなHTTPトラフィックを遮断し、HTTPSを強制
readOnlyPort: 0
ここで注目してほしいのは、readOnlyPort: 0 という設定だ。昔のバージョンでは 10255 ポートなどで認証なしの読み取り専用APIが開いていたが、これもセキュリティリスクの温床になるため、明示的に 0 を指定して無効化する。
—
3. マネージドKubernetes(EKS / GKE / AKS)での注意点
「うちはAWSのEKSを使っているから大丈夫だろ」なんて油断していないか? マネージドサービスであっても、マネージドなのはコントロールプレーン側であって、自分でプロビジョニングしたワーカーノード(NodeGroup)のAMIやブートストラップスクリプトの不備によって、Kubeletがガバガバな状態で立ち上がることがある。
特にカスタムAMIを使っている場合は、kubeletの起動フラグや設定ファイルに --anonymous-auth=false が確実に含まれているかを、以下のコマンドで全ノードを回って監査(Audit)しなきゃいけない。
# 各ノードにSSH、またはSSM等でログインし、Kubeletのプロセス引数を確認するコマンド
ps aux | grep kubelet | grep -- "--anonymous-auth=false"
もし、このコマンドで何もヒットしなかったり、--anonymous-auth=true になっていたりしたら、即座に修正してノードを再構築しろ。
—
4. セキュリティチーフからの現場の教訓
いいか、セキュリティというのは「穴を塞いだつもり」が一番危ない。システムをデプロイしたら、必ず外部から(あるいはクラスター内の別権限のコンテナから)ペネトレーションテストを行え。
簡易的に、手元から curl でKubeletが正しく弾いてくるか確認するワンライナーも教えておく。
# 認証トークンなしでKubeletにアクセスし、401 Unauthorizedが返ってくることを確認する
curl -sk https://<node-ip>:10250/pods
これで 200 OK が返ってきたら赤信号だ。すぐに先の kubelet-config.yaml の設定を見直し、APIサーバーとKubelet間の通信(TLSクライアント証明書認証とWebhook認可)が正しく機能しているか確認してくれ。
インフラストラクチャの強度は、一番弱いパーツ(今回の場合は個々のノードのKubelet設定)で決まる。面倒くさい設定こそ、最初に自動化スクリプトに組み込んでおくこと。それがプロの仕事というものだ。頼んだぞ。
コメント