Pod / コンテナへCA証明書をまとめて配信する
アプリケーションPodが起動する前に、KMSが管理する共通CA証明書セットをPod内へ配置する仕組みです。
目的が「すべてのアプリケーションに共通でCA証明書をインストールする」ことであるため、チーム内ではプリインジェクションと呼んでいます。
注意
Certificate Deliveryは、CA証明書をターゲットにインストールする仕組みを提供するプロジェクトです。
別プロジェクトであるため詳細な説明をしていません。
CD経由のCDはCertificate Deliveryを指しています。
CI/CDとは別ですのでご注意ください。
なぜプリインジェクションが必要か
Kubernetes上のアプリケーションでは、社内CA、サービスメッシュ用CA、外部接続先のCAなど、様々なCAをPod内に配置したい場面があります。
アプリケーションごとに証明書ファイルを個別設定すると、次の課題が出ます。
- どのPodがどのCAを使っているか追跡しにくい
- CA追加時に、多数のマニフェストを変更する必要がある
- CA更新時に、古いCAと新しいCAの併用期間を安全に扱いにくい
- 証明書取得のための強い権限を、アプリケーションPod側へ割り当てる設計になりやすい
- CA証明書の正本がnamespaceごとに分散し、どれが最新か判断しにくくなる
- CA更新を反映するためのPod再作成や再起動の判断まで、証明書配布手順に含まれやすい
プリインジェクションの目的は、これらの課題を「KMS側で管理するCAグループ」と「Pod Templateの利用宣言」に分けて解決できるようにすることです。
基本コンセプト
インジェクショングループという考え方が存在します。
インジェクショングループは、KMSが管理する複数のCA証明書を用途単位で束ねたものです。
アプリケーションは個別のCA名をすべて列挙するのではなく、「このインジェクショングループを使う」と宣言します。
この構造により、アプリケーション側の設定は小さく保ちつつ、KMS側で標準CAセットを管理できるようになります。
ドメイン
プリインジェクションを理解するためのドメインは次のとおりです。
Managed Certificate(管理対象証明書)
Managed Certificateは、KMSが管理する証明書の論理名です。
証明書の実体は鍵管理DBに保存されます。
Kubernetes上のKMSリソースは、証明書の実体そのものではなく、証明書名、リビジョン、フィンガープリント、鍵管理DBへの参照などのメタデータを持ちます。
プリインジェクションは、Managed Certificateの証明書の実体をKubernetes APIで直接読みません。
証明書を使う直前にKMS APIへ利用宣言を作成し、許可された証明書の実体だけを取得します。
インジェクショングループ
インジェクショングループは、複数のManaged Certificateを束ねるKMS管理リソースです。
例えば、platform-ca というインジェクショングループに、社内ルートCA、サービス間通信用CA、外部接続先CAを含めることができます。
アプリケーションPodは platform-ca を宣言するだけで、そのグループに含まれるCAセットを受け取れます。
インジェクショングループは、KMS namespaceにだけ集約するよう設計されています。
KMS namespaceに集約することで、定義の分散を防ぐ、RBACの単純化、アプリケーションnamespace側の学習負荷や設定負荷を下げる、などのメリットが得られます。
Usage Declaration(利用宣言)
Usage Declarationは、あるPodまたはKMSコンポーネントが証明書を利用するという宣言です。
プリインジェクションでは、Gatewayが証明書の実体を取得する前に、KMS APIへ利用宣言を作ります。
これにより、KMSは「どのインジェクショングループがどの証明書を利用しているか」を追跡可能になります。
※ Gatewayとは、CA証明書をPodにインストールするためのアプリケーションです。
※ KMS APIへの利用宣言は、利用中のCA証明書が誤って削除されないようにするための宣言を指します。別プロジェクトのため、詳細な説明は省いています。
Injected Runtime(注入される実行部品)
プリインジェクションでは、アプリケーションイメージ自体は変更しない設計です。
代わりに、Podが作成されるタイミングでAdmission WebhookがPod定義へ証明書のインストールに必要な部品を追加します。この追加される実行部品を、ここではInjected Runtimeと呼びます。
追加される主な部品は次のとおりです。
- initContainer: アプリケーションコンテナより先に起動し、GatewayからCA証明書を取得する
-
emptyDirvolume: 取得したCA証明書ファイルをPod内で一時的に置く場所 - 読み取り専用のvolumeMount:
emptyDirに置かれたCA証明書を、アプリケーションコンテナから読み取り専用で見えるようにする設定 - ServiceAccountの認証トークン: Gatewayが「どのPodからの要求か」を確認するためのトークン
- Gateway TLS 検証用のCA情報: initContainerがGatewayへHTTPS接続するときに、接続先を検証するためのCA情報
Injected Runtimeによって、アプリケーションはKMSを意識することなくCA証明書を利用できます。
アプリケーションコンテナが行うことは、インストールされたCA証明書ファイルを読むことだけです。
重要なのは、アプリケーションnamespaceに証明書の実体の正本を複製しないことです。
Kubernetes 上の全体像
アプリケーション側の宣言
アプリケーションは、Pod Templateにアノテーション kms.example.io/injection: preinjection を宣言してデプロイするだけです。
以下は例です。
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-app
spec:
template:
metadata:
annotations:
# ▼ 追加するアノテーション ====================
kms.example.io/injection: preinjection
# ▲ =======================================
spec:
containers:
- name: app
image: example/app:1.0.0
例のようにアプリケーションは、鍵管理DBのURL、認証情報、証明書の識別情報を知る必要がありません。
インジェクショングループの例
インジェクショングループは、KMS側で管理します。
apiVersion: kms.example.io/v1alpha1
kind: InjectionGroup
metadata:
name: preinjection
namespace: kms-system
spec:
certificates:
- secretRef: internal-root-ca
fileName: internal-root-ca.crt
mode: "0444"
- secretRef: service-mesh-ca
fileName: service-mesh-ca.crt
mode: "0444"
secretRefは、鍵管理DBの直接IDではなく、KMSが管理する証明書の論理名です。
fileNameは、Pod内に配置するファイル名です。
modeはファイル権限です。
Pod内では、これらのCAが決められたディレクトリに配置されます。
配置先ディレクトリは実装ごとに決めますが、プリインジェクションでは「グループに含まれるCAを同じディレクトリにまとめてインストールする」方針になっています。
Pod 作成時の流れ
プリインジェクションのKubernetes上の動きは、Pod作成時に始まります。
Admission Webhookは、Podのアノテーションを見てプリインジェクション対象かどうかを判断します。
対象であれば、KMS namespaceにあるインジェクショングループを読み、Podに必要な構成を追加します。
アプリケーション側にできるだけ干渉しない設計になっており、KMSが管理するmountPathや予約済みvolume名と衝突する場合は、Pod作成を停止するようになっています。
initContainerの流れ
Podが作成された後、アプリケーションコンテナより先にinitContainerが実行されます。
initContainerは、Podに注入されたServiceAccountの認証トークンを使ってGatewayにアクセスします。
Gatewayは、TokenReviewなどを使って認証トークンを検証し、要求元Podのnamespace、ServiceAccount、宣言されたインジェクショングループを確認します。
Gatewayは、KMS APIを通してCA証明書データを取得します。
アプリケーションPodは、鍵管理DBに直接アクセスしません。
initContainerは、受け取ったCA証明書データをemptyDirにファイルとして書き込みます。
その後、アプリケーションコンテナが起動し、同じボリュームを読み取り専用で参照します。
ボリュームとインストールの考え方
プリインジェクションでは、CAセットを1つのディレクトリとして扱います。
/var/run/kms/certs/
internal-root-ca.crt
service-mesh-ca.crt
実際のインストールパスは設定で決まります。
重要なのは、グループに含まれるCAをひとつのディレクトリにまとめ、アプリケーションコンテナから読み取り専用で参照させることです。
この方式には次の利点があります。
- 複数CAをまとめて扱いやすくなる
- アプリケーションコンテナが証明書を書き換えられない
- Kubernetes Secretをアプリケーションnamespaceに複製しなくてよくなる
- initContainerが完了してからアプリケーションを起動できる
既に同じmountPathをアプリケーションが使っている場合は衝突と判定し、上書きせずAdmissionでPod作成を停止します。
Gatewayの認可
Gatewayは、プリインジェクションの安全性を支える境界です。
そのため、証明書をインストールする前に次の確認を行います。
- 要求元PodのServiceAccountトークンが有効であるか
- トークンのnamespaceとServiceAccountが要求内容と一致するか
- Podがインジェクショングループを宣言しているか
- 宣言されたインジェクショングループに対象CAが含まれているか
- KMS API上で証明書の利用宣言を作れるか
これにより、Podが関係ない証明書名を指定して証明書の実体を取得することを防ぎます。
CA更新の考え方
CA証明書の更新では、古いCAと新しいCAを一時的に併用できるよう設計しました。
KMSでは、現行の証明書と次に切り替える候補の証明書を分けて管理します。
更新期間中は、現在利用中のCA証明書と次に切り替えるCA証明書の両方をPodにインストールします。
これにより、古いCAを信頼する通信と、新しいCAを信頼する通信を一時的に両立できます。
移行が完了したら、最終的に新しいCA証明書だけを配布する状態へ切り替えます。
KMSは、アプリケーションのDeploymentやStatefulSetを勝手に書き換えて、Podの再作成を行いません。
理由は、CA証明書の更新をきっかけにPodを再作成すると、起動時にアンシールが必要なアプリケーションなどで機能を提供できなくなる可能性があるためです。そのため、更新内容は次回Podが作成されるタイミングで反映されるようにしています。
グループ変更時の挙動
インジェクショングループにCAを追加した場合、以降に作成される対象Podには追加CAが含まれます。
インジェクショングループからCAを外した場合、以降に作成される対象Podには除外後のCAセットだけが入ります。
対象外Podへの影響
プリインジェクション対象でないPodには、initContainer、volume、volumeMountを追加しません。
アノテーションを持たないPodは通常どおり作成されます。
インジェクショングループに含まれていないCAをKMSへ登録、更新、削除しても、プリインジェクション対象Podには影響しません。
他証明書インストール機能との共存
プリインジェクションは、KMS内の他の証明書インストール機能と共存できるように設計されています。
- プリインジェクションのCAセットと個別証明書は別ボリュームとして扱う
- mountPathが衝突する場合はAdmissionで拒否する
- プリインジェクションのCA追加、更新、削除は他証明書インストール機能に影響させない
- 他証明書インストール機能の追加、更新、削除はインジェクショングループに影響させない
責務境界
プリインジェクションが責務を持つもの:
- インジェクショングループの解決
- Podがプリインジェクション対象かどうかの判定
- Pod作成時のinitContainer、volume、volumeMountの注入
- Gateway経由での証明書の実体取得
- 証明書利用宣言の作成
- グループ所属CAの結合
プリインジェクションが責務を持たないもの:
- アプリケーションWorkloadのPod入れ替え判断
- Deployment、StatefulSet、DaemonSet、Jobの更新
- アプリケーションPodの再起動、再作成
- アプリケーション固有の信頼ストア設計
- 鍵管理DBの認証情報をアプリケーションへ渡すこと
この境界を守ることで、KMSは「CAセットを安全に届ける」責務に集中できます。
セキュリティ設計の要点
プリインジェクションのセキュリティでは、次が重要です。
- 証明書の実体をKubernetes Secretとして各namespaceに複製しない
- 鍵管理DBの認証情報をアプリケーションPodに渡さない
- PodのServiceAccountトークンを、短い有効期限と専用の利用先を指定して投影する
- GatewayでトークンとPod情報を検証する
- Podが宣言したインジェクショングループの範囲外の証明書を返さない
- KMS管理リソースへの書き込み権限をKMSコンポーネントに限定する
- アプリケーションnamespaceの既存リソースを上書きしない
Admission WebhookによるPod定義の変更でも、既存定義を壊さないことが重要です。
既存volumeMountと衝突する場合は、上書きではなく拒否を選びます。
まとめ
プリインジェクションは、KMSが管理する共通CAセットを、Pod起動前に安全に配置するためのアーキテクチャです。
インジェクショングループによってCAセットの正本をKMSに集約し、Pod Templateのアノテーションによって利用を宣言し、Admission WebhookとinitContainerによってPod内へ配置します。
プリインジェクションでは、下記のような方針で課題の解消や管理の安定を実現するようにしています。
- アプリケーション側のCA設定を簡潔にする
- CAセットの変更をKMS側で管理する
- 鍵管理DBの詳細をアプリケーションから隠蔽する
- Kubernetes Secretのnamespace間複製を避ける
- Podの身元を検証したうえで証明書の実体を返す
- KMSがアプリケーションWorkloadを勝手に入れ替えない
プリインジェクションは、証明書ファイルを置くことだけではありません。
どのPodが、どのCAセットを、どの経路で、どの権限境界のもとで利用するのかをKMSのドメインとして扱える点にあります。