【実務・中級編】 etcdの保存時暗号化(Encryption at Rest)とKMS統合 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

Kubernetesクラスターを運用する上で、多くのエンジニアが「アプリケーションの動かし方」には熱心になる一方で、「足元(基盤)の守り」を疎かにして痛い目をミドルウェアの現場で見てきた。

特に、Kubernetesの心臓部である etcd のセキュリティ設計は、インフラエンジニアの腕の見せ所であり、同時に最も見落とされがちな盲点だ。

「Secretリクエストを暗号化しているから大丈夫」――そう高を括っている後輩エンジニアによく現実を突きつけるのだが、デフォルト状態のKubernetesにおける etcd は、機密データを平文(Base64エンコードのみ、暗号化にあらず)でディスクに書き込んでいる。

今回は、数々のインシデント現場を潜り抜けてきた私から、etcd の保存時暗号化(Encryption at Rest)と外部KMS(Key Management Service)統合について、攻撃者の視点を交えながら実戦的な要塞化手法を伝授しよう。

—

1. なぜデフォルトの etcd は危険なのか?(攻撃者の視点)

ペネトレーションテストやインシデントレスポンスの現場で、Kubernetesクラスターへの初期侵入(コンテナ逃げ出しやRCEなど)に成功した攻撃者が真っ先に狙うのはどこか?
答えは、APIサーバーの背後で静かに息をしている etcd のデータストアだ。

アプリケーションの接続文字列、データベースのパスワード、外部APIの秘密鍵――これらはすべてKubernetesの Secret オブジェクトとして保存される。しかし、前述の通り、Secret はBase64でエンコードされているだけであり、誰でも簡単にデコード可能だ。

もし攻撃者がコントロールプレーンへのアクセス権、あるいは何らかの脆弱性を突いて etcd のスナップショットやデータディレクトリ(通常 /var/lib/etcd)への読み取り権限を手に入れた場合、クラスター内の全機密情報が1秒で露出する。

「うちはTLS通信させているから安全だ」という言い訳は通用棒に値しない。「通信中の暗号化(In-Transit)」と「保存時の暗号化(At-Rest)」は全く別問題なのだ。ディスクに落ちた瞬間、ゲームオーバーになるリスクを断ち切らなければならない。

—

2. etcdの保存時暗号化(Encryption at Rest)の仕組み

このリスクを防ぐ唯一の正解が、Kubernetes APIサーバーにおける EncryptionConfiguration の有効化だ。

APIサーバーは、クライアントからのリクエストを受けて etcd にデータを書き込む際、設定された暗号化プロバイダー(AES-CBC、AES-GCM、SecretBox、あるいは外部KMS)を使用してデータを暗号化し、読み出すときは復号する。これにより、たとえ etcd のストレージが丸ごとダークウェブに流出したとしても、鍵がなければ中身を読み取ることは不可能になる。

しかし、ここでローカルの固定鍵(Static Key)だけに頼るのも、セキュリティの観点からは片落ちだ。鍵のローテーション(定期的な変更)やアクセス監査ログの欠如という運用上のリスクが残る。
そこで本番環境では、AWS KMSやGCP KMSといった外部KMSとの統合が必須要件となる。

—

3. 【実践】AWS KMS / GCP KMS を用いたetcd暗号化の設定手順

ここからは、実際にコントロールプレーンのマスターノード(またはマネージドではないセルフホスト環境)でどのように設定を行うのか、具体的な設定ファイルを交えて解説する。

ステップ1: 暗号化設定ファイル(EncryptionConfiguration)の作成

まずは、APIサーバーがどの暗号化プロバイダーを使用し、どのリソースを対象にするかを定義するYAMLファイルを作成する。通常、/etc/kubernetes/etcd/encryption-config.yaml などのパスに配置する。

以下は、AWS KMSのプラットフォームを利用しつつ、フォールバックとしてAES-CBCも併用する堅牢な設定サンプルだ。

# /etc/kubernetes/etcd/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps # 機密性が高い場合はConfigMapも含めることを推奨
    providers:
      # AWS KMSを用いた暗号化(推奨)
      - awsKMS:
          name: my-k8s-aws-kms-provider
          # AWS KMSで作成したCMK(Customer Master Key)のARNを指定
          arn: arn:aws:kms:ap-northeast-1:123456789012:key/a1b2c3d4-e5f6-7890-abcd-ef0123456789
          # KMSプラグインとの通信用ソケットパス
          endpoint: unix:///var/run/kms-plugin.sock
          # キャッシュの有効期間(パフォーマンス改善のため)
          cacheSize: 100
          timeout: 3s
      # フォールバック用プロバイダー(KMS障害時に備えるが、本番では慎重に)
      - identity: {}

ステップ2: クラウドIAMとKMSプラグインの連携

マネージドなAWS(EKSなど)ではなく、EC2上に自前でKubernetesを構築している場合、APIサーバーからAWS KMSを叩くために AWS KMS Plugin for Kubernetes をサイドカーやデーモンとして常駐させ、適切なIAMロール(またはインスタンスプロファイル)を付与する必要がある。

必要な最小限のIAMポリシー(Least Privilegeの原則)は以下の通りだ。KMSの暗号化(Encrypt)、復号(Decrypt)、鍵情報の取得(DescribeKey)のみを許可する。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/a1b2c3d4-e5f6-7890-abcd-ef0123456789"
    }
  ]
}

ステップ3: APIサーバーの起動引数(Manifest)の変更

作成した EncryptionConfiguration をKubernetesのAPIサーバーに認識させる。static podとして動作しているkube-apiserverのマニフェストファイル(通常 /etc/kubernetes/manifests/kube-apiserver.yaml)を編集し、引数を追加する。

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    image: k8s.gcr.io/kube-apiserver:v1.28.0
    command:
    - kube-apiserver
    # --- ここから追加・修正 ---
    - --encryption-provider-config=/etc/kubernetes/etcd/encryption-config.yaml
    # --- ここまで ---
    volumeMounts:
    - mountPath: /etc/kubernetes/etcd
      name: etcd-encryption-config
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/etcd
      type: DirectoryOrCreate
    name: etcd-encryption-config

ファイルを保存すると、kube-apiserverが自動的に再起動する。

—

4. 導入後の「罠」:既存Secretの再暗号化と運用の注意点

ここでエンジニアが最もやりがちな致命的ミスを指摘しておこう。

「設定ファイルを置いてAPIサーバーを再起動しても、すでにetcd内に存在する既存のSecretは自動的には暗号化されない」

新規に作成されるSecretのみが暗号化の対象となるため、過去に作成された平文のSecretはそのまま残されてしまう。これではセキュリティ対策としては不十分だ。
必ず、以下のコマンドを実行してクラスター内の既存Secretを強制的に書き換え、暗号化を適用させる必要がある。

# クラスター内のすべてのシークレットを取得し、強制的に上書き保存(パッチ)することでKMS経由での再暗号化を走らせる
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

このコマンドを実行する際は、APIサーバーに一時的な高負荷がかかるため、トラフィックの少ないメンテナンスウィンドウを狙って実施すること。

—

5. シニアセキュリティチーフからの提言

セキュリティは「一度設定したら終わり」という静的なものではない。今回導入したKMSの鍵(CMK)についても、以下の運用ルールをチーム全体で徹底してほしい。

1. 鍵の自動ローテーションの有効化: AWS KMSやGCP KMSでは、年1回の鍵の自動ローテーションを有効化する。過去に暗号化されたデータも古い鍵で復号できるため、運用の手を止めることなく安全性を高められる。
2. 監査ログ(Audit Logs)の監視: KMSへのアクセスログ(AWS CloudTrailなど)を監視し、予期せぬタイミングや見覚えのないIPからの復号リクエストがないかをSIEMツールでアラート検知できるようにする。

「面倒くさい」を言い訳に後回しにしたインフラストラクチャの隙こそが、サイバー攻撃者にとっての最高の扉となる。今日の作業が終わったら、直ちに自社のKubernetesクラスターの etcd がどのように保護されているか、確認してみてほしい。

コメント

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