門番が寝ている間に:Kubernetes Kubelet未認証アクセスの深淵
エンジニア諸君、お疲れ様。現場でKubernetes(以下K8s)を触っていると、「とりあえず動く」設定のまま放置されたクラスターに出くわすことはないだろうか?
特に危険なのが、Kubeletのアクセス制御だ。Kubeletは各ノードの「現場監督」であり、Podの起動からコンテナのログ収集まで全てを管理している。もしこの門番が認証なしで誰でも中に入れる状態だったら?攻撃者はノードの内部情報、環境変数、さらにはPod内で実行中のサービスにまで、苦もなくアクセスできてしまう。
今日は、なぜKubeletが狙われるのか、そしてどうやって鉄壁の防御を敷くのか、現場のリアルな視点で解説する。
—
1. 狙われる「読み取り専用ポート(10255)」の脆弱性
かつて、Kubeletには 10255 ポートで実行される「読み取り専用ポート」が存在した。これは認証なしでノード上のPod一覧やログを誰でも閲覧できるという、攻撃者にとっては夢のような入り口だ。
最近のバージョンではデフォルトで無効化されているが、レガシーな環境や設定ミスにより、このポートがパブリックに晒されているケースを今でも稀に見る。
攻撃者の視点:何が見えるのか?
攻撃者はまず、ターゲットのIPに対して以下のようなリクエストを送る。
# ノード上の全Podリストを取得する(認証不要)
curl -v http://<NODE_IP>:10255/pods
これだけで、クラスター内の機密情報の所在や、ネットワーク構成、実行されているイメージのバージョンが丸裸になる。ここから、特定のPodへ exec を仕掛けたり、機密情報を奪取するための足がかりを得るわけだ。
—
2. 認証の強制:Kubeletの設定を「ゼロトラスト」へ
Kubeletを保護する鉄則は、「匿名認証の無効化」と「TLSクライアント認証の強制」だ。これらを kubelet-config.yaml で確実に設定する必要がある。
以下に、実務で採用すべきセキュアな設定例を示す。これを適用せずにK8sを本番稼働させるのは、玄関の鍵を開けたまま外出するようなものだ。
Kubelet設定ファイル例 (kubelet-config.yaml)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 1. 匿名認証を無効化する
authentication:
anonymous:
enabled: false
webhook:
enabled: true # APIサーバーによる認証を有効化
cacheTTL: 2m0s
x509:
clientCAFile: "/etc/kubernetes/pki/ca.crt" # クライアント証明書による検証
# 2. 認可もWebHook経由でAPIサーバーに委譲する
authorization:
mode: Webhook
# 3. 読み取り専用ポートを完全に閉じる
readOnlyPort: 0
—
3. WebHookによる認可の実装(Python例)
「認証はしたけれど、誰に何を許可するか?」を細かく制御したい場合、K8sの SubjectAccessReview を活用する。これは外部の認証プロキシやサイドカーで実装することが多い。
以下は、あるリクエストに対してK8s APIサーバーへ「このユーザーは操作を許可されているか?」を問い合わせるためのPythonスクリプトの概念コードだ。
from kubernetes import client, config
def check_permission(user, verb, resource):
"""
K8s APIサーバーに権限を問い合わせる関数
"""
config.load_incluster_config() # Pod内部で実行する場合
api = client.AuthorizationV1Api()
# 認可リクエストの構築
body = client.V1SubjectAccessReview(
spec=client.V1SubjectAccessReviewSpec(
resource_attributes=client.V1ResourceAttributes(
verb=verb,
resource=resource,
namespace="default"
),
user=user
)
)
response = api.create_subject_access_review(body)
return response.status.allowed
# 使用例:特定のユーザーによるPod閲覧が可能かチェック
if check_permission("system:serviceaccount:default:my-app", "get", "pods"):
print("アクセス許可:操作を続行します")
else:
print("アクセス拒否:不正なアクセスを検知しました")
—
4. 最後に:インフラ屋としての矜持
セキュリティは「設定を入れたら終わり」ではない。
1. 定期的なポートスキャン: 外部からKubeletポート(10250/10255)が見えていないか、定期的に外側からテストせよ。
2. 監査ログの監視: system:anonymous ユーザーからのアクセス試行がログに出ていないか、ElasticsearchやCloudWatch Logsでアラートを飛ばせ。
3. ノードの隔離: Kubeletへのアクセスは、管理用VPCやセキュアな踏み台サーバーからのみ許可し、インターネットからは絶対にアクセスできないようにネットワークACLで絞り込め。
「システムは壊れるもの、環境は汚れるもの」という前提で動くのが、プロのエンジニアだ。Kubeletというクラスターの心臓部を守り抜くことこそが、君たちの担当するプロダクトを守る唯一の道だと心に刻んでおいてほしい。
現場からは以上だ。何かあればいつでも相談してくれ。
コメント