Kubernetesの心臓部を守れ:API Serverを「公開」するなという警鐘
現場でインシデント対応をしていると、笑えない現実に直面することがある。「KubernetesのAPI Serverがインターネットに全開放されていた」というケースだ。これは、家の玄関の鍵を開けたまま、リビングに金庫を置いて放置しているようなものだ。
今日は、Kubernetesのコントロールプレーンをどう鉄壁にするか、その「現場の勘所」を共有する。教科書的な設定だけでは、百戦錬磨の攻撃者は防げない。
1. なぜAPI Serverが狙われるのか?(攻撃者の視点)
攻撃者が最初に狙うのは、kube-apiserverの公開ポート(通常は6443)だ。ここさえ突破すれば、クラスター内の全リソースを操作し、コンテナを乗っ取り、そこを足掛かりにクラウドのIAM権限を奪取できる。
彼らが使う手法は単純だ。
- 匿名認証の悪用: 設定ミスで
system:anonymousがクラスタ管理者権限を持っている場合、認証なしでkubectlが叩けてしまう。 - RBACの不備: 「とりあえず
cluster-adminを付与しておく」という怠惰な権限管理が、特権昇格の踏み台になる。 - トークンの漏洩: CI/CDパイプラインのログや、誤ってコミットされた
kubeconfigから静的トークンを盗み出す。
2. 防御の要:ネットワーク層での封鎖
まず大前提として、kube-apiserverをインターネットに公開してはならない。
クラウド環境(AWS/GCP/Azure)での鉄則
必ず「プライベートエンドポイント」を使用すること。パブリックIPを付与せず、VPN(OpenVPN, WireGuard)や、AWSなら AWS Systems Manager Session Manager を経由した踏み台サーバーからのみアクセスできるようにする。
Nginxでのアクセス制限(WAF/リバースプロキシを挟む場合)
もし何らかの事情でAPIサーバーの前にプロキシを置くなら、ngx_http_auth_request_module を使い、認証を通ったクライアントのみを通す構成にせよ。
# Nginxの設定例:API Serverへのアクセス制御
location /api/ {
# 特定のIP範囲のみ許可(ただしIP制限は偽装可能なので過信しない)
allow 10.0.0.0/24;
deny all;
# 証明書ベースの相互認証(mTLS)を強制する
ssl_verify_client on;
ssl_client_certificate /etc/nginx/certs/ca.crt;
proxy_pass https://kubernetes-internal-ip:6443;
}
3. RBACの「最小権限」を徹底する(IAMとの連携)
Kubernetesの認証において、RSAやECDSAを用いた公開鍵暗号は「証明書の正当性」を保証するために使われる。しかし、認証(Who are you?)が通っても、認可(What can you do?)が甘ければ意味がない。
悪い例:開発者に cluster-admin を渡す
これをやると、開発者が誤ってクラスター全体を削除したり、機密情報にアクセスしたりするリスクがある。
良い例:Namespace単位の制限
特定のNamespace(例:web-app-production)のみ操作可能な Role と RoleBinding を作成する。
# RoleBindingの定義(特定のユーザーに権限を付与)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-read-only-binding
namespace: web-app-production
subjects:
- kind: User
name: "engineer@company.com" # OIDC等と連携したユーザーID
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader # 読み取り専用の権限
apiGroup: rbac.authorization.k8s.io
4. プログラムからのAPI利用:サービスアカウントのセキュアな運用
アプリケーションからAPIを叩く際、ServiceAccount のトークンをコードに埋め込むのは最悪のプラクティスだ。必ず「OIDC連携」を使うこと。
以下は、PythonでKubernetesクライアントを使う際のセキュアな実装パターンだ。
from kubernetes import client, config
def get_k8s_client():
# 実行環境がPod内の場合、自動的にServiceAccountトークンが読み込まれる
# ローカル開発時は kubeconfig をロードするが、本番では環境変数で制御する
try:
config.load_incluster_config()
except config.ConfigException:
config.load_kube_config()
# 最小権限で実行される ServiceAccount に紐付いたクライアントのみを利用する
v1 = client.CoreV1Api()
return v1
# 安全にPod一覧を取得する例
def list_pods():
api = get_k8s_client()
pods = api.list_namespaced_pod(namespace="web-app-production")
for pod in pods.items:
print(f"Found pod: {pod.metadata.name}")
結論:セキュリティは「設定」ではなく「哲学」
Kubernetesのセキュリティは、一度設定して終わりではない。
- mTLSによる暗号化: 通信経路は必ずTLSで保護し、証明書は短命に保つ。
- ログの監査:
audit-logを有効にし、誰がいつ何をしたかを必ず外部ストレージに転送する。 - 防御的姿勢: 「API Serverは常に攻撃されている」という前提で、異常なAPIコールを検知するアラートを仕込むこと。
君たちが書くコードの一行、設定するYAMLの一つが、会社のインフラを守る砦になる。面倒な作業こそ、自動化し、堅牢に積み上げていこう。それが、プロのエンジニアの流儀だ。
コメント