# 機能設計書 80-SSHキー管理

## 概要

本ドキュメントは、GitLabのSSHキー管理機能について、その処理概要、入出力仕様、処理フロー、データベース操作仕様を定義する。

### 本機能の処理概要

SSHキー管理機能は、ユーザーがGitリポジトリへのSSHアクセスおよびコミット署名に使用するSSH公開鍵を管理する機能である。SSHキーの登録、表示、削除、失効を行う。

**業務上の目的・背景**：GitLabでは、HTTPSに加えてSSHプロトコルでのGitリポジトリアクセスをサポートしている。SSHキーを登録することで、パスワードなしでの安全なリポジトリアクセスが可能となる。また、SSHキーをコミット署名に使用することで、コミットの真正性を証明できる。

**機能の利用シーン**：
- ユーザーがSSHでのリポジトリアクセスを設定する場合
- 複数のデバイスから異なるSSHキーでアクセスする場合
- コミット署名用のSSHキーを登録する場合
- 不要になったSSHキーを削除または失効させる場合
- SSHキーの有効期限を設定する場合

**主要な処理内容**：
1. SSHキーの登録（タイトル、公開鍵、用途、有効期限）
2. SSHキー一覧の表示
3. SSHキーの詳細表示
4. SSHキーの削除
5. SSHキーの失効（署名無効化を伴う）

**関連システム・外部連携**：
- authorized_keys（Gitシェルアクセス）
- SSH署名検証（コミット署名）
- システムフック（作成・削除時）
- 通知サービス（キー追加時）

**権限による制御**：
- SSHキー管理: 本人のみ

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 204 | SSHキー一覧 | 主画面 | SSHキーの一覧表示・登録 |
| 205 | SSHキー詳細 | 詳細画面 | SSHキーの詳細表示・削除 |

## 機能種別

CRUD操作 / セキュリティ機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | String | Yes | キーのタイトル | 最大255文字 |
| key | String | Yes | SSH公開鍵 | 最大5000文字、有効なSSH公開鍵形式 |
| usage_type | Integer | No | キーの用途 | 0:認証&署名, 1:認証のみ, 2:署名のみ |
| expires_at | Date | No | 有効期限 | 未来の日付 |

### 入力データソース

- 画面入力（SSHキー登録フォーム）
- ユーザーのローカル環境から生成されたSSH公開鍵

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | キーID |
| title | String | キーのタイトル |
| key | String | SSH公開鍵（コメント部分はマスク） |
| fingerprint_sha256 | String | SHA256フィンガープリント |
| usage_type | String | キーの用途 |
| expires_at | Date | 有効期限 |
| created_at | DateTime | 作成日時 |
| last_used_at | DateTime | 最終使用日時 |

### 出力先

- 画面表示（HTML）
- authorized_keys（非同期更新）

## 処理フロー

### 処理シーケンス

```
【SSHキー登録】
1. パラメータ受け取り
   └─ title, key, usage_type, expires_at
2. 公開鍵サニタイズ
   └─ Gitlab::SSHPublicKey.sanitize
3. フィンガープリント生成
   └─ SHA256フィンガープリント計算
4. バリデーション
   └─ 形式、一意性、禁止キーチェック
5. キー保存
   └─ keysテーブルにINSERT
6. authorized_keys更新
   └─ AuthorizedKeysWorkerで非同期追加
7. システムフック実行
   └─ SystemHooksService.execute_hooks_for
8. 通知送信
   └─ notification_service.new_key

【SSHキー削除】
1. キー取得
   └─ current_user.keys.find
2. authorized_keys更新
   └─ AuthorizedKeysWorkerで非同期削除
3. キー削除
   └─ key.destroy
4. システムフック実行
   └─ SystemHooksService.execute_hooks_for
5. キャッシュ更新
   └─ Users::KeysCountService.refresh_cache

【SSHキー失効】
1. キー取得
   └─ current_user.keys.find
2. 署名無効化
   └─ ssh_signaturesをrevoked_keyに更新
3. キー削除
   └─ destroy処理
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|登録| C[パラメータ受け取り]
    C --> D[公開鍵サニタイズ]
    D --> E[フィンガープリント生成]
    E --> F{バリデーション}
    F -->|失敗| G[エラー表示]
    F -->|成功| H[キー保存]
    H --> I[authorized_keys更新]
    I --> J[システムフック実行]
    J --> K[通知送信]
    K --> L[詳細画面へ]
    B -->|削除| M[キー取得]
    M --> N{削除可能?}
    N -->|No| O[エラー表示]
    N -->|Yes| P[authorized_keys更新]
    P --> Q[キー削除]
    Q --> R[システムフック実行]
    R --> S[一覧画面へ]
    B -->|失効| T[キー取得]
    T --> U[署名無効化]
    U --> V[削除処理]
    V --> S
    G --> W[終了]
    L --> W
    O --> W
    S --> W
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-80-01 | タイトル必須 | タイトルは必須、最大255文字 | 登録時 |
| BR-80-02 | キー形式 | サポートされるアルゴリズムの公開鍵のみ | 登録時 |
| BR-80-03 | フィンガープリント一意 | SHA256フィンガープリントは一意 | 登録時 |
| BR-80-04 | 禁止キー | 侵害されたキーは登録不可 | 登録時 |
| BR-80-05 | 有効期限 | 期限切れのキーは登録不可 | 登録時 |
| BR-80-06 | 用途指定 | auth_and_signing, auth, signingから選択 | 登録時 |
| BR-80-07 | 失効時署名無効化 | キー失効時、関連署名はrevoked_keyに更新 | 失効時 |
| BR-80-08 | FIPSモード | FIPSモードではMD5フィンガープリント非生成 | 登録時 |

### 計算ロジック

- **フィンガープリント計算**: `Gitlab::SSHPublicKey.new(key).fingerprint_sha256`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キー登録 | keys | INSERT | SSHキー情報 |
| キー削除 | keys | DELETE | SSHキー削除 |
| キー失効 | commit_signatures_ssh_signatures | UPDATE | verification_statusをrevoked_keyに更新 |
| 最終使用日時更新 | keys | UPDATE | last_used_at |

### テーブル別操作詳細

#### keys

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | ユーザー入力値 | 必須 |
| INSERT | key | サニタイズ済み公開鍵 | 必須 |
| INSERT | fingerprint | MD5フィンガープリント | FIPSモードでは null |
| INSERT | fingerprint_sha256 | SHA256フィンガープリント | 必須 |
| INSERT | usage_type | 0, 1, または 2 | デフォルト 0 |
| INSERT | expires_at | ユーザー入力値 | 任意 |
| INSERT | user_id | current_user.id | 必須 |
| INSERT | organization_id | Current.organization.id | 任意 |
| UPDATE | last_used_at | 現在日時 | 使用時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | バリデーションエラー | キー形式不正 | エラーメッセージ表示 |
| 422 | 重複エラー | フィンガープリント重複 | エラーメッセージ表示 |
| 422 | 禁止キー | 侵害されたキー | エラーメッセージ表示、ヘルプページへのリンク |
| 422 | 期限切れ | 期限切れのキー | エラーメッセージ表示 |
| 404 | NotFound | 存在しないキーID | 404ページ表示 |

### リトライ仕様

- authorized_keys更新はSidekiqワーカーで非同期実行、失敗時はリトライ

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

- キー失効時、署名無効化と削除はトランザクション内で実行
- `each_batch`でバッチ処理

## パフォーマンス要件

- キー登録: 2秒以内
- キー一覧取得: 1秒以内
- authorized_keys更新: 非同期（バックグラウンド）

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

- 公開鍵のコメント部分はマスク（メールアドレス漏洩防止）
- 禁止キー（侵害されたキー）のブロック
- SHA256フィンガープリントによる一意性確保
- FIPSモード対応（MD5フィンガープリント非生成）
- キー失効時の署名無効化

## 備考

- UserSettings::SshKeysControllerで処理
- Keys::CreateService、Keys::DestroyService、Keys::RevokeServiceを使用
- EE版では追加の制限機能あり

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | key.rb | `app/models/key.rb` | Keyモデルの属性とバリデーションを確認 |

**読解のコツ**:
- **28-36行目**: title, keyのバリデーションを確認
- **38-40行目**: fingerprint_sha256の一意性バリデーション
- **42-43行目**: expiration, banned_keyのカスタムバリデーション
- **47-52行目**: usage_typeの列挙型定義（auth_and_signing, auth, signing）
- **54-59行目**: after_create, after_destroyフック
- **177-185行目**: generate_fingerprintメソッド

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ssh_keys_controller.rb | `app/controllers/user_settings/ssh_keys_controller.rb` | SSHキーコントローラーの構造を確認 |

**主要処理フロー**:
1. **8-11行目**: `index`アクションでキー一覧取得
2. **13-16行目**: `show`アクションで詳細表示
3. **18-28行目**: `create`アクションでキー登録
4. **30-38行目**: `destroy`アクションでキー削除
5. **40-48行目**: `revoke`アクションでキー失効
6. **52-54行目**: `key_params`で許可パラメータ定義

#### Step 3: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_service.rb | `app/services/keys/create_service.rb` | キー作成処理 |
| 3-2 | destroy_service.rb | `app/services/keys/destroy_service.rb` | キー削除処理 |
| 3-3 | revoke_service.rb | `app/services/keys/revoke_service.rb` | キー失効処理 |

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

```
UserSettings::SshKeysController
    │
    ├─ index（一覧表示）
    │      └─ current_user.keys.order_id_desc
    │
    ├─ show（詳細表示）
    │      └─ current_user.keys.find
    │             └─ public_key.weak_key_warning
    │
    ├─ create（キー登録）
    │      └─ Keys::CreateService
    │             ├─ user.keys.create(params)
    │             │      ├─ generate_fingerprint（before_validation）
    │             │      ├─ post_create_hook（after_create）
    │             │      │      └─ SystemHooksService.execute_hooks_for
    │             │      ├─ refresh_user_cache（after_create）
    │             │      │      └─ Users::KeysCountService.refresh_cache
    │             │      └─ add_to_authorized_keys（after_commit）
    │             │             └─ AuthorizedKeysWorker.perform_async
    │             └─ notification_service.new_key
    │
    ├─ destroy（キー削除）
    │      └─ Keys::DestroyService
    │             └─ key.destroy
    │                    ├─ post_destroy_hook（after_destroy）
    │                    │      └─ SystemHooksService.execute_hooks_for
    │                    ├─ refresh_user_cache（after_destroy）
    │                    └─ remove_from_authorized_keys（after_commit）
    │                           └─ AuthorizedKeysWorker.perform_async
    │
    └─ revoke（キー失効）
           └─ Keys::RevokeService
                  ├─ unverify_associated_signatures
                  │      └─ ssh_signatures.update_all(verification_status: :revoked_key)
                  └─ super（DestroyService#execute）
```

### データフロー図

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

SSHキー登録 ────────▶ SshKeysController ────────────▶ 詳細画面
    │                      │
    │                      └─▶ Keys::CreateService
    │                               │
    │                               ├─▶ Key.create
    │                               │      │
    │                               │      ├─▶ keysテーブル
    │                               │      │
    │                               │      └─▶ AuthorizedKeysWorker
    │                               │               │
    │                               │               └─▶ authorized_keys
    │                               │
    │                               └─▶ notification_service.new_key
    │
    └── params[:key]
           title, key, usage_type, expires_at

SSHキー失効 ────────▶ SshKeysController ────────────▶ 一覧画面
    │                      │
    │                      └─▶ Keys::RevokeService
    │                               │
    │                               ├─▶ unverify_associated_signatures
    │                               │      │
    │                               │      └─▶ ssh_signatures（UPDATE）
    │                               │
    │                               └─▶ key.destroy
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ssh_keys_controller.rb | `app/controllers/user_settings/ssh_keys_controller.rb` | コントローラー | SSHキー管理処理 |
| key.rb | `app/models/key.rb` | モデル | SSHキーデータ構造 |
| create_service.rb | `app/services/keys/create_service.rb` | サービス | キー作成処理 |
| destroy_service.rb | `app/services/keys/destroy_service.rb` | サービス | キー削除処理 |
| revoke_service.rb | `app/services/keys/revoke_service.rb` | サービス | キー失効処理 |
| last_used_service.rb | `app/services/keys/last_used_service.rb` | サービス | 最終使用日時更新 |
| ssh_public_key.rb | `lib/gitlab/ssh_public_key.rb` | ライブラリ | SSH公開鍵処理 |
| authorized_keys_worker.rb | `app/workers/authorized_keys_worker.rb` | ワーカー | authorized_keys更新 |
