# 機能設計書 33-CSRF保護

## 概要

本ドキュメントは、JenkinsにおけるCSRF（Cross-Site Request Forgery）保護機能の設計を記載したものである。CSRF保護機能は、クロスサイトリクエストフォージェリ攻撃を防止するためのcrumb（トークン）発行と検証を担当する。

### 本機能の処理概要

本機能は、Jenkinsに対する不正なリクエストを防止するため、各リクエストにcrumb（ワンタイムトークン）を付与し、サーバー側で検証を行う。

**業務上の目的・背景**：Webアプリケーションは、ユーザーが意図しない操作を悪意のあるサイトから実行させられるCSRF攻撃に脆弱である。Jenkinsでは、ジョブの実行・設定変更・ユーザー管理など重要な操作が多く、CSRF攻撃による被害が深刻になり得る。本機能により、正規のJenkinsページからのリクエストのみを受け付け、攻撃を防止する。

**機能の利用シーン**：
- Jenkinsの全てのPOSTリクエスト（フォーム送信、API呼び出し）
- ジョブのビルド開始、設定変更
- ユーザー・権限の管理操作
- プラグインのインストール・更新
- 外部からのAPI呼び出し（crumbヘッダー付与が必要）

**主要な処理内容**：
1. crumb（トークン）の発行（ユーザー情報とセッションIDから生成）
2. リクエストに含まれるcrumbの検証
3. crumb情報のAPIエクスポート（外部クライアント用）
4. Staplerとの連携によるフォーム保護

**関連システム・外部連携**：CrumbFilterがServletFilterとして全リクエストを監視。Staplerのorg.kohsuke.stapler.CrumbIssuerと連携してフォームを保護。

**権限による制御**：crumb自体は認証済みユーザーに対して発行される。crumbの取得はREST APIで可能だが、そのAPIも認証が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | ログイン | 補助機能 | CSRF保護によるログインフォーム保護 |

## 機能種別

セキュリティ機能 / リクエスト検証 / トークン管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| Jenkins-Crumb | String | Yes（POST時） | リクエストに含まれるcrumb値 | 空不可、サーバー生成値と一致 |
| salt | String | No | crumb生成用のソルト | Descriptorで設定 |

### 入力データソース

- HttpServletRequest: リクエストパラメータまたはヘッダーからcrumbを取得
- SecurityContext: 現在の認証情報を取得
- HttpSession: セッションIDを取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| crumb | String | 生成されたcrumb値（16進文字列） |
| crumbRequestField | String | crumbパラメータ名（デフォルト: "Jenkins-Crumb"） |

### 出力先

- HTTPレスポンスヘッダー: APIレスポンスでcrumb情報を返却
- HTMLフォーム: 隠しフィールドとしてcrumbを埋め込み
- リクエスト属性: キャッシュ用にcrumbを保存

## 処理フロー

### 処理シーケンス

```
1. POSTリクエスト受信
   └─ CrumbFilterがリクエストをインターセプト
2. crumb抽出
   └─ リクエストパラメータまたはヘッダーからcrumbを取得
3. crumb再計算
   └─ 現在の認証情報とセッションIDからcrumbを再計算
4. crumb検証
   └─ MessageDigest.isEqual()で定時間比較
5. 結果判定
   └─ 一致: リクエスト続行、不一致: SecurityException
```

### フローチャート

```mermaid
flowchart TD
    A[POSTリクエスト受信] --> B{CrumbIssuer設定あり?}
    B -->|No| C[リクエスト続行]
    B -->|Yes| D[crumb抽出]
    D --> E{crumb存在?}
    E -->|No| F[SecurityException]
    E -->|Yes| G[crumb再計算]
    G --> H[issueCrumb実行]
    H --> I{crumb一致?}
    I -->|Yes| C
    I -->|No| F
    F --> J[リクエスト拒否]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-33-01 | POST必須 | 状態変更を伴うリクエストはPOSTで行い、crumb検証必須 | POSTリクエスト時 |
| BR-33-02 | セッション依存 | crumbはセッションIDを含めて計算（デフォルト） | EXCLUDE_SESSION_ID=false時 |
| BR-33-03 | 定時間比較 | タイミング攻撃防止のため定時間比較を使用 | crumb検証時 |
| BR-33-04 | キャッシュ | 同一リクエスト内でcrumbをキャッシュ | リクエスト属性に保存 |

### 計算ロジック

crumb生成（DefaultCrumbIssuer）:
```
buffer = 認証ユーザー名 + ";" + セッションID
crumb = toHexString(SHA-256(buffer + salt))
```

## データベース操作仕様

### 操作別データベース影響一覧

CSRF保護機能自体はデータベースを操作しない。crumbはリクエストごとに動的に生成・検証される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | SecurityException | crumbが不一致または欠落 | フォームを再取得して再送信、またはAPIでcrumbを取得してヘッダーに付与 |
| - | NullPointerException | MessageDigest初期化失敗（SHA-256未サポート） | JVMの設定を確認 |

### リトライ仕様

crumb検証失敗時は、新しいcrumbを取得して再試行可能。セッションが変わるとcrumbも変わるため、セッション管理に注意。

## トランザクション仕様

CSRF保護機能はステートレスであり、トランザクション管理は不要。

## パフォーマンス要件

- crumb検証は毎POSTリクエストで実行されるため、高速な応答が必要
- SHA-256ハッシュ計算は十分高速（数マイクロ秒）
- リクエスト属性でキャッシュすることで同一リクエスト内の重複計算を回避

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

- MessageDigest.isEqual()による定時間比較でタイミング攻撃を防止
- ソルトはHexStringConfidentialKeyで安全に管理
- セッションIDを含めることでセッション固定攻撃への耐性向上
- EXCLUDE_SESSION_IDオプションは非推奨（セキュリティ低下のため）

## 備考

- CrumbExclusionを使用して特定のURLパスをcrumb検証から除外可能（Webhook等）
- GlobalCrumbIssuerConfigurationでJenkins全体のcrumb設定を管理
- レガシーなjavax.servlet対応のためのラッパーメソッドが存在

---

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

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

### 推奨読解順序

#### Step 1: 基底クラスを理解する

CrumbIssuerの抽象構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CrumbIssuer.java | `core/src/main/java/hudson/security/csrf/CrumbIssuer.java` | crumb発行・検証の基本構造 |

**主要処理フロー**:
- **50行目**: CrumbIssuerクラス定義（Describable, ExtensionPoint実装）
- **61-64行目**: getCrumbRequestField() - crumbパラメータ名の取得
- **70-95行目**: getCrumb() - crumb発行とリクエスト属性へのキャッシュ
- **144-149行目**: validateCrumb(ServletRequest) - crumb検証のエントリーポイント
- **186-198行目**: validateCrumb(request, salt, crumb) - 実際の検証ロジック（抽象メソッド）

**読解のコツ**: CrumbIssuerはExtensionPointであり、複数の実装が存在し得る。DefaultCrumbIssuerがデフォルト実装。

#### Step 2: デフォルト実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DefaultCrumbIssuer.java | `core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java` | 実際のcrumb生成・検証アルゴリズム |

**主要処理フロー**:
- **44-56行目**: クラス定義とEXCLUDE_SESSION_IDオプション
- **92-99行目**: initializeMessageDigest() - SHA-256の初期化
- **101-120行目**: issueCrumb() - ユーザー名+セッションIDからcrumb生成
- **122-133行目**: validateCrumb() - MessageDigest.isEqual()による定時間比較

#### Step 3: Staplerとの統合を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CrumbIssuer.java | `core/src/main/java/hudson/security/csrf/CrumbIssuer.java` | initStaplerCrumbIssuer()によるStapler連携 |

**主要処理フロー**:
- **245-265行目**: initStaplerCrumbIssuer() - @InitializerでStaplerにcrumb発行者を設定

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

```
CrumbFilter.doFilter() [Servletフィルター]
    │
    ├─ [GETリクエスト] → 通過
    │
    └─ [POSTリクエスト]
           │
           ├─ CrumbExclusion.process() [除外パスチェック]
           │      └─ 除外対象なら通過
           │
           └─ CrumbIssuer.validateCrumb()
                  │
                  ├─ request.getParameter(crumbField)
                  │
                  └─ DefaultCrumbIssuer.validateCrumb()
                         │
                         ├─ issueCrumb() [crumb再計算]
                         │      ├─ Jenkins.getAuthentication2()
                         │      └─ request.getSession().getId()
                         │
                         └─ MessageDigest.isEqual() [定時間比較]
```

### データフロー図

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

HttpServletRequest ─────▶ CrumbFilter ────────────────────▶ FilterChain.doFilter()
      │                        │                              （成功時）
      │                        ▼
      │               CrumbIssuer.validateCrumb()
      │                        │
      │                        ▼
      │               DefaultCrumbIssuer.issueCrumb()
      │                        │
      │                 ┌──────┴──────┐
      │                 ▼             ▼
      │         Authentication   SessionID
      │                 │             │
      │                 └──────┬──────┘
      │                        ▼
      │                  SHA-256 hash
      │                        │
      │                        ▼
      └────────────────▶ MessageDigest.isEqual() ──────────▶ SecurityException
                                                            （失敗時）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| CrumbIssuer.java | `core/src/main/java/hudson/security/csrf/CrumbIssuer.java` | ソース | crumb発行の基底クラス |
| DefaultCrumbIssuer.java | `core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java` | ソース | デフォルトのcrumb発行実装 |
| CrumbFilter.java | `core/src/main/java/hudson/security/csrf/CrumbFilter.java` | ソース | Servletフィルターによるcrumb検証 |
| CrumbExclusion.java | `core/src/main/java/hudson/security/csrf/CrumbExclusion.java` | ソース | crumb検証除外パスの定義 |
| CrumbIssuerDescriptor.java | `core/src/main/java/hudson/security/csrf/CrumbIssuerDescriptor.java` | ソース | CrumbIssuerのDescriptor |
| GlobalCrumbIssuerConfiguration.java | `core/src/main/java/hudson/security/csrf/GlobalCrumbIssuerConfiguration.java` | ソース | グローバルcrumb設定 |
