Kubernetesの心臓部を抉る:etcd非暗号化とTLS不備が生む「クラスタ全権掌握」の悪夢
数々のインシデントレスポンスやペネトレーションテストを経験してきた私にとって、Kubernetesクラスタの監査で最も絶望的な瞬間は、API Serverやコンテナのセキュリティ設定ではなく、その足元であるetcdが丸裸になっているのを発見したときだ。
多くの開発者やインフラエンジニアは、Kubernetesを「コンテナオーケストレーションプラットフォーム」という抽象的なレイヤで捉えている。しかし、セキュリティアーキテクトの視点から見れば、Kubernetesの本質は「etcdという分散KVS(Key-Value Store)の周りに構築された巨大な認可・制御機構」に過ぎない。
etcdが落ちればクラスタは機能不全に陥り、etcdが奪われれば、それはクラスタ全体の完全な敗北(Full Cluster Compromise)を意味する。シークレット、サービスアカウントのトークン、環境変数、そして暗号化されていない限り平文で保存される機密データ――すべてが攻撃者の手に渡る。
今回は、このクラスタの心臓部であるetcdの「静止データ暗号化(Encryption at Rest)」と「TLSによる通信経路の保護」について、攻撃者の手口とそれを完全に封じ込めるための要塞化のロジックを、現場の泥臭い知見を交えて徹底的に解説する。
—
1. 攻撃者の視点:なぜetcdが狙われるのか?
ペネトレーションテストにおいて、攻撃者がアプリケーション層の脆弱性(RCEやSQLiなど)を通じてクラスタ内のワーカーノードやPodの足がかり(Initial Access)を得た後、次に見据えるのは「コントロールプレーンへの横展開(Lateral Movement)」である。
通常、KubernetesのSecretリソースはBase64エンコードされて保存されている。これはセキュリティ素人に対する気休めにもならない。「暗号化」ではなく単なる「エンコーディング」であるため、kubectl get secrets -o yamlを実行できれば、誰でも即座にデコードして平文のパスワードやAPIキーを手に入れられる。
しかし、RBAC(Role-Based Access Control)が適切に機能しており、API Server経由でのSecret取得が制限されている場合、攻撃者はどこを狙うか?
そう、etcdへの直接アクセスだ。
低レイヤの脅威:メモリダンプとスナップショットの強奪
etcdはデータをディスク上に永続化する際、デフォルトでは平文で書き込む。
もし攻撃者がコントロールプレーンノードへのSSHアクセス権、あるいはホストのファイルシステムを読み取る権限(例えば、特権コンテナの脱獄や脆弱なボリュームマウント)を得た場合、/var/lib/etcdディレクトリにあるデータファイルやスナップショット(.dbファイル)をそのまま持ち出すことが可能になる。
さらに恐ろしいのは、メモリ上の挙動だ。etcdプロセス(etcdバイナリ)のメモリ空間には、復号されたキーやTLSのプライベートキーが常時キャッシュされている。カーネル空間の脆弱性やデバッグインターフェースの悪用、あるいはコールドブート攻撃に近いアプローチにより、プロセスが保持するメモリダンプから直接機密情報を抽出する高度な攻撃も現実に行われている。
—
2. 対策①:etcdの「静止データ暗号化(Encryption at Rest)」の鉄則
このリスクを断ち切る唯一の方法が、API Server層での静止データ暗号化だ。ここで重要なのは、etcd自体がストレージ層で暗号化機能を持つだけでなく、KubernetesのAPI Serverがetcdへ書き込む段階で暗号化を強制するというアーキテクチャである点だ。
API Serverに対して「このリソース(例: secrets)はetcdに保存する前に暗号化せよ」と指示する設定ファイルを投入する必要がある。
暗号化設定ファイル(EncryptionConfiguration)の実装例
以下は、AES-CBC方式を用いてSecretを暗号化するための本番グレードの設定ファイル(/etc/kubernetes/enc/encryption-config.yaml)の例だ。
apiVersion: apiserver.config.k8s.v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
# AES-CBCによる暗号化。より強固なAES-GCMを使用することも可能だが、
# クラスタ移行や鍵ローテーションの互換性を考慮してAES-CBCが選択されることが多い。
- aescbc:
keys:
- name: key1
# 32バイト(256bit)のランダムな秘密鍵をBase64エンコードして指定する
# 生成コマンド例: head -c 32 /dev/urandom | base64
secret: "Z3Vyc3VjdXJpdHl3aGl0ZWhhY2tlcnNlY3JldGtleTEyMzQ1Njc="
# 復号専用プロバイダ。鍵のローテーション時に古い鍵で暗号化されたデータを読めるようにする
- identity: {}
この設定ファイルをAPI Serverの起動オプションに組み込む必要がある。Kubeadm等で構築された環境であれば、API Serverの静的ポッドマニフェスト(/etc/kubernetes/manifests/kube-apiserver.yaml)に以下のフラグを追加する。
spec:
containers:
- command:
- kube-apiserver
# 以下のフラグを追加して暗号化設定ファイルを読み込ませる
- --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml
volumeMounts:
- mountPath: /etc/kubernetes/enc
name: enc-conf
readOnly: true
volumes:
- hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
name: enc-conf
⚠️ 現場の罠:既存データの再暗号化(Migration)を忘れるな
ここで多くのエンジニアが陥る致命的なミスがある。上記の設定を投入しても、すでにetcd上に存在する既存のSecretは平文のまま放置されるという事実だ。
新しく作成されるSecretのみが暗号化の対象となるため、攻撃者は過去の平文Secretを依然として読み取ることができる。設定変更後は、必ず以下のコマンドを実行して既存データのマイグレーション(強制上書き保存による暗号化)を実施しなければならない。
# 全てのシークレットを一度取得し、API Server経由で再書き込みさせることで暗号化を強制する
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
—
3. 対策②:通信経路の要塞化:相互TLS(mTLS)の厳格な実装
データをディスク上で守るだけでは不十分だ。API Serverとetcdの間、そしてetcdクラスタ内のノード間通信(Peer通信)が平文(HTTP)であれば、同一ネットワークセグメントに侵入した攻撃者にパケットをスニッフィング(盗聴)されるか、不正なクライアントからのリクエストインジェクション(中間者攻撃:MitM)を許してしまう。
etcdとの通信は、すべての経路で相互TLS(mTLS)を強制しなければならない。
証明書トポロジの設計
セキュアなetcdクラスタを構築するには、最低限以下の3種類の証明書(および鍵)を適切に発行・配置する必要がある。
1. etcdCA(認証局): すべてのetcd関連証明書に署名するルートCA。
2. etcd Server証明書: etcdインスタンス自身がクライアント(API Server等)からの接続を受け付ける際に提示する証明書。
3. etcd Peer証明書: etcdクラスタ内のメンバ同士が同期通信を行う際に使用する証明書。
4. API Server Client証明書: API Serverがetcdのクライアントとして認証を受ける際に使用する証明書。
etcdの起動パラメータ(mTLSの強制)
etcdの設定ファイル、またはsystemd / 静的ポッドのパラメータにおいて、以下のフラグが確実に設定されていることを監査せよ。
# etcdマニフェスト(または設定ファイル)の抜粋
command:
- etcd
# クライアント通信におけるTLSの有効化
- --client-cert-auth=true
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
# Peer(クラスタ間)通信におけるTLSの有効化とmTLSの強制
- --peer-client-cert-auth=true
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
# リッスンアドレスの限定(ループバックや内部セキュアネットワークのみ)
- --listen-client-urls=https://192.168.1.10:2379
- --listen-peer-urls=https://192.168.1.10:2380
ここで --client-cert-auth=true と --peer-client-cert-auth=true を明示的にtrueにすることが極めて重要だ。これが抜けていると、暗号化(TLS)はされていてもクライアントの身元確認(認証)が行われないため、証明書を持っていなくてもTLSのハンドシェイクさえ通れば接続できてしまうという致命的な設定不備(Broken Authentication)に繋がる。
—
4. チーフホワイトハッカーの監査チェックリスト
実務の現場で、Kubernetesクラスタのセキュリティレビューやペネトレーションテストを行う際、私は必ず以下のコマンドと手順でetcd周りの防衛力を検証している。あなたも今すぐ自身のクラスタで確認してほしい。
1. etcdポートの外部露出チェック
- インターネット側、あるいは不要なワークロードから
2379(Client)や2380(Peer)ポートへ直接アクセスできないか?(Nmapやnetcatによるスキャン)
2. API Serverからのetcd暗号化ステータス確認
- API Serverのプロセス引数に
--encryption-provider-configが正しく指定されているか? - etcd内の実データを直接
etcdctlで覗き、平文でデータが保存されていないか確認する。
# 脆弱な環境の検証例(TLSや認証なし、または平文保存の場合)
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret
- 正しく暗号化されていれば、出力に
k8s:enc:aescbc:v1:...といったプレフィックスが付与され、バイナリデータとして保護されていることが確認できる。
3. 証明書の有効期限とアルゴリズムの監査
- 自己署名CAの有効期限が長すぎていないか、また使用されているアルゴリズムがレガシー(SHA-1など)になっていないか。現代の基準では最低でも
SHA-256以上、できれば将来の耐量子暗号(PQC)への移行を見据えたモダンな暗号スイートの選定を視野に入れておくべきだ。
—
結びに代えて
セキュリティは「点」ではなく「面」であり、Kubernetesにおいてはコントロールプレーンの要塞化が防御の最前線である。どれほど厳格なNetworkPolicyを書き、どれほど優秀なIDS/IPSをコンテナ層に導入しようとも、その足元にあるetcdが平文で保存され、mTLSの網が破られていれば、それは「鍵をかけた立派な玄関の横に、合鍵を置きっぱなしにしている家」となんら変わらない。
クラスタの心臓部を守り抜くこと。それこそが、真のインフラストラクチャ・セキュリティの第一歩なのである。
コメント