【実務・中級編】 KubernetesにおけるSecretsの平文保存リスクと暗号化 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

KubernetesのSecretsは「金庫」ではない:etcdを覗き見られる前に知るべき現実

「KubernetesのSecretsは暗号化されているから大丈夫」。もしチームの誰かがそう言っていたら、即座に修正が必要です。

こんにちは。セキュリティチームのチーフエンジニアです。今日は、多くのエンジニアが陥る「K8s Secretsの幻想」と、それを現実的な脅威から守るための防御策について、現場の視点から解説します。

1. なぜ「デフォルト」のSecretsは無防備なのか

Kubernetesの Secret オブジェクトは、APIサーバー経由でアクセスするとBase64デコードされた状態で取得できます。しかし、これ自体は「転送時の保護」に過ぎません。

真の問題は、バックエンドである etcd にあります。
Kubernetesのクラスターデータストアである etcd は、デフォルト設定ではデータを暗号化せずにディスクへ保存します。つまり、何らかの理由でクラスターのノードに侵入を許したり、etcdのバックアップファイルがクラウドストレージ(S3バケットなど)に漏洩したりした瞬間、すべての機密情報は「平文」として攻撃者の手に渡ります。

攻撃者の視点:PoC(概念実証)

攻撃者がクラスター内で一時的なPod(kubectl exec等が可能)を確保できた場合、あるいは etcd への読み取り権限(あるいはバックアップへのアクセス権)を得た場合、以下のように情報を抽出します。

# etcdctlを使って直接データをダンプする(認証情報が漏れた想定)
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  get /registry/secrets/default/db-secret --print-value-only

これだけで、Base64エンコードされた文字列が手に入ります。echo "BASE64文字列" | base64 -d と叩けば、DBのパスワードやAPIキーが丸見えです。

2. Encryption at Rest:KMSによる暗号化

この脅威を防ぐ唯一の正攻法が、Encryption at Rest(保存時の暗号化) です。これを有効にすると、Kubernetesは etcd に書き込む直前に、指定したKMS(AWS KMS, GCP KMS, Azure Key Vaultなど)を用いてデータを暗号化します。

実装:EncryptionConfiguration の設定

Kubernetes APIサーバーに以下の設定ファイル(encryption-config.yaml)を読み込ませる必要があります。

# encryption-config.yaml
kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
  - resources:
      - secrets
    providers:
      # AWS KMSを使用する場合の例
      - kms:
          name: my-kms-provider
          # AWS KMSのARNを指定
          endpoint: unix:///var/run/kmsplugin/socket.sock
          cachesize: 100
          timeout: 3s
      # 失敗時のフォールバック(推奨されませんが移行用)
      - identity: {}

3. アプリケーションコード側での防御:Secretを「環境変数」にしない

インフラ側で暗号化しても、Pod内で環境変数として DB_PASSWORD を読み込む設計だと、プロセスの環境変数を覗き見られたり、ログに出力されたりするリスクが残ります。

最も堅牢なのは、「ボリュームマウント」し、権限を最小化したファイルとして読み込むことです。

Pythonでのセキュアな読み込みサンプル

環境変数に頼らず、マウントされたファイルから直接読み込む実装例です。

import os

def get_db_password():
    # Kubernetesでマウントされたシークレットファイルのパス
    secret_path = "/etc/secrets/db-password"
    
    try:
        with open(secret_path, "r") as f:
            # 読み込み、末尾の改行を除去
            return f.read().strip()
    except FileNotFoundError:
        # デバッグ用ログに機密情報を出さないよう注意
        raise Exception("シークレットファイルが見つかりません")

# 利用時はメモリ上で扱う(ログ出力は厳禁)
db_password = get_db_password()

4. 現場で意識すべきセキュリティの鉄則

最後に、インシデントハンドリングの経験から、必ず守ってほしい「3つのルール」を伝えます。

1. etcdバックアップの暗号化を疑え: クラスターのバックアップをS3に置いている場合、そのバケット自体に暗号化設定(SSE-KMSなど)が適用されているか、今すぐ確認してください。
2. RBACの監査: get secrets 権限を持つサービスアカウントが多すぎませんか?「最小権限の原則」を適用し、本当にシークレットが必要なPodにのみ権限を与えてください。
3. Secret Store CSI Driverの導入: クラウドネイティブな運用なら、Secret Store CSI Driver を検討してください。これを使えば、K8sのSecretオブジェクトを介さず、直接クラウドのKMSから値をPodにマウントできるため、etcdのリスクを根本から回避できます。

セキュリティは「設定して終わり」ではなく、常に「どこが突破口になるか」を疑い続けるプロセスです。今日紹介した設定を、今週末の運用でぜひ適用してみてください。何かあれば、いつでもコードレビューを依頼してください。一緒に堅牢なシステムを築きましょう。

コメント

タイトルとURLをコピーしました