# 通知設計書 133-CertificateExpired

## 概要

本ドキュメントは、KubernetesのPod証明書（PodCertificate）が期限切れとなった場合に発行されるWarningイベント「CertificateExpired」の通知設計について記載する。

### 本通知の処理概要

本通知は、kubeletのPodCertificateManagerが管理するPod証明書の有効期限（notAfter）を過ぎた際に発行されるWarningイベントである。証明書の期限切れはTLS通信やサービス認証に直接影響するため、最も深刻なセキュリティ警告の一つである。

**業務上の目的・背景**：Pod証明書はPodCertificateProjection機能を通じて、Podに自動配布されるX.509証明書である。証明書には有効期限（notAfter）があり、通常はbeginRefreshAtの時点でリフレッシュが試みられる。しかし、Signerの障害やPCRの拒否などにより証明書が更新できない場合、最終的に期限切れとなる。期限切れ証明書を使用するサービスはTLSハンドシェイクに失敗し、通信不能となるため、即座に対応が求められる。

**通知の送信タイミング**：PodCertificateManagerのhandleProjection処理において、credStateFreshまたはcredStateWaitRefresh状態で、現在時刻が証明書のnotAfterを超過した場合にイベントが発行される。1つのPodCertificateProjectionにつき1回のみ発行される（eventEmittedForExpirationフラグで制御）。

**通知の受信者**：対象Podに紐付くKubernetesイベントとして記録されるため、kubectl describe pod等でPodを参照するクラスタ管理者やアプリケーション開発者が受信者となる。

**通知内容の概要**：Pod証明書が期限切れである旨を示す警告メッセージ「PodCertificate expired」。

**期待されるアクション**：受信者は即座にPodCertificateRequestの状態を確認し、証明書署名者（Signer）の障害を調査する。Podの再起動による新規証明書発行の促進、Signerの修復、または代替の認証手段の確保が必要である。

## 通知種別

Kubernetesイベント（Event API）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（kubelet内のPodCertificateManager ワークキュー経由） |
| 優先度 | 高（緊急） |
| リトライ | 有・projectionQueueのRateLimitingQueue による自動リトライ |

### 送信先決定ロジック

イベントは対象PodのObjectReferenceに紐付けて記録される。PodCertificateProjectionのprojectionKeyから対応するPodを特定し、そのPodに対してイベントが発行される。

## 通知テンプレート

### Kubernetesイベント

| 項目 | 内容 |
|-----|------|
| EventType | Warning |
| Reason | CertificateExpired |
| Component | kubelet podcertificate manager |

### 本文テンプレート

```
PodCertificate expired
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | Kubernetesイベントのため添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| なし | メッセージは固定文字列 | - | - |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 定期リフレッシュ | runRefreshPass（1分間隔） | credStateFresh状態でnow > notAfter | 証明書期限切れ検出（Fresh状態） |
| PCR更新 | PodCertificateRequest Informerイベント | credStateWaitRefresh状態でnow > notAfter | 証明書期限切れ検出（WaitRefresh状態） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| イベント発行済み | eventEmittedForExpirationフラグがtrueの場合、同一Projectionに対する重複発行を抑止 |
| 証明書未発行 | credStateInitialまたはcredStateWaitの場合は対象外 |
| 証明書拒否/失敗 | credStateDeniedまたはcredStateFailedの場合は対象外 |
| notAfter未到達 | 現在時刻がnotAfterより前の場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[runRefreshPass / PCR Informerイベント] --> B[projectionQueueにProjectionを追加]
    B --> C[handleProjection]
    C --> D{credState判定}
    D -->|credStateFresh| E{now > beginRefreshAt?}
    E -->|Yes| F{now > notAfter?}
    F -->|Yes| G{eventEmittedForExpiration?}
    G -->|No| H[CertificateExpiredイベント発行]
    H --> I[eventEmittedForExpirationをtrueに設定]
    G -->|Yes| J[新PCR作成へ進む]
    I --> J
    D -->|credStateWaitRefresh| K{PCR Terminal状態?}
    K -->|Pending| L{now > notAfter?}
    L -->|Yes| M{eventEmittedForExpiration?}
    M -->|No| N[CertificateExpiredイベント発行]
    N --> O[eventEmittedForExpirationをtrueに設定]
    M -->|Yes| P[終了]
    O --> P
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| Pod | 対象Pod情報の取得 | PodManager経由 |
| PodCertificateRequest | PCRの状態・notAfter確認 | Informerキャッシュ（pcrLister）経由 |

### テーブル別参照項目詳細

#### PodCertificateRequest

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| Status.NotAfter | 証明書有効期限の確認 | PCR名で取得 |
| Status.BeginRefreshAt | リフレッシュ開始予定時刻 | PCR名で取得 |
| Status.Conditions | PCRの最終状態確認 | PCR名で取得 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Event | INSERT | 警告イベントの記録 |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | Type | Warning | イベントタイプ |
| INSERT | Reason | CertificateExpired | イベント理由 |
| INSERT | Message | PodCertificate expired | 固定メッセージ |
| INSERT | Source.Component | kubelet | イベントソース |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| Pod未検出 | PodManagerでPodが見つからない | credStoreをクリーンアップ |
| PCR取得失敗 | pcrListerからPCRが取得できない | ワークキューでリトライ |
| PCR削除検知 | PCRがabandon時間を超過して見つからない | credStateFreshに戻り新PCRを作成 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 無制限（RateLimitingQueue） |
| リトライ間隔 | 指数バックオフ（DefaultTypedControllerRateLimiter） |
| リトライ対象エラー | handleProjection関数が非nilエラーを返した場合 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Kubernetes Event APIのデフォルトレート制限に従う |
| 1日あたり上限 | 制限なし（ただしProjectionあたり1回のみ） |

### 配信時間帯

時間帯制限なし。kubeletが稼働中かつ証明書期限切れを検出した時点で発行される。

## セキュリティ考慮事項

- 期限切れ証明書を使用したTLS通信はハンドシェイク失敗となるため、サービスの可用性に直接影響する
- 証明書期限切れは攻撃者による証明書差し替えのリスク指標にもなりうる
- イベントメッセージ自体には証明書の詳細情報（CN、SAN等）は含まれないため、情報漏洩リスクは低い
- 速やかなPod再起動または証明書更新を推奨

## 備考

- CertificateExpiredイベントはCertificateOverdueForRefreshイベントの後に発行される（時系列順：beginRefreshAt → beginRefreshAt + 10分 → notAfter）
- credStateFresh→credStateWaitRefresh遷移時にeventEmittedForExpirationフラグは引き継がれる（597-598行目）
- 証明書が期限切れとなっても、credStoreからは削除されず、古い証明書チェーンが引き続きgetCredBundleで返される
- metricsState関数では、notAfter超過時に「expired」状態が報告される（230-231行目、259-260行目）

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

証明書の有効期限管理に関連するデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | credStateFresh構造体（216-223行目）のnotAfterフィールドとeventEmittedForExpirationフラグ |
| 1-2 | podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | credStateWaitRefresh構造体（239-252行目）のnotAfterフィールドとeventEmittedForExpirationフラグ |

**読解のコツ**: notAfterはPCRのStatus.NotAfterから取得される証明書の有効期限。eventEmittedForExpirationはbooleフラグで、イベント重複発行を防止する。

#### Step 2: エントリーポイントを理解する

リフレッシュパスとProjection処理の起動を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | Run関数（339-353行目）。runRefreshPass（1分間隔）とrunProjectionProcessorの起動 |

#### Step 3: 期限切れ検出とイベント発行を理解する

handleProjection内の期限切れ判定ロジックを追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | credStateFreshケース内の561-564行目。now > notAfterの判定とイベント発行 |
| 3-2 | podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | credStateWaitRefreshケース内の684-688行目。now > notAfterの判定とイベント発行 |

**主要処理フロー**:
- **561行目**: `m.clock.Now().After(state.notAfter)` - 現在時刻がnotAfterを超過しているか判定
- **562行目**: `!state.eventEmittedForExpiration` - まだイベントが発行されていないか確認
- **563行目**: `m.recorder.Eventf(pod, corev1.EventTypeWarning, "CertificateExpired", "PodCertificate expired")` - イベント発行
- **564行目**: `state.eventEmittedForExpiration = true` - フラグ更新

### プログラム呼び出し階層図

```
IssuingManager.Run (339行目)
    |
    +-- runRefreshPass (704行目) [1分間隔]
    |      |
    |      +-- queueAllProjectionsForPod (311行目) [全Pod]
    |
    +-- runProjectionProcessor (355行目) [常時]
           |
           +-- processNextProjection (360行目)
                  |
                  +-- handleProjection (378行目)
                         |
                         +-- [credStateFresh] (542行目)
                         |      |
                         |      +-- clock.Now().After(state.notAfter) (561行目)
                         |      +-- recorder.Eventf "CertificateExpired" (563行目)
                         |
                         +-- [credStateWaitRefresh] (611行目)
                                |
                                +-- clock.Now().After(state.notAfter) (684行目)
                                +-- recorder.Eventf "CertificateExpired" (686行目)
```

### データフロー図

```
[入力]                          [処理]                              [出力]

runRefreshPass --------+
(1分間隔タイマー)       |
                       |
PCR Informer ----------+----> handleProjection ---+
(Add/Update/Delete)    |                          |
                       |                          +---> Kubernetes Event
TrackPod (kubelet) ----+                          |     (CertificateExpired)
                       |                          |
                       v                          +---> credStore更新
                  projectionQueue                       (eventEmittedフラグ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| podcertificatemanager.go | `pkg/kubelet/podcertificate/podcertificatemanager.go` | ソース | PodCertificateManager本体。状態管理、イベント発行 |
| podcertificatemanager_test.go | `pkg/kubelet/podcertificate/podcertificatemanager_test.go` | テスト | PodCertificateManagerのユニットテスト |
| podcertificate_metrics.go | `pkg/kubelet/metrics/collectors/podcertificate_metrics.go` | ソース | PodCertificateのPrometheusメトリクス収集 |
| kubelet.go | `pkg/kubelet/kubelet.go` | ソース | kubeletからのPodCertificateManager初期化 |
| types.go | `staging/src/k8s.io/api/certificates/v1beta1/types.go` | ソース | PodCertificateRequest API型定義（NotAfterフィールド） |
