# 機能設計書 130-Pagesドメイン管理

## 概要

本ドキュメントは、GitLabにおけるPagesドメイン管理機能の設計仕様を記述したものである。本機能は、GitLab Pagesで公開される静的サイトにカスタムドメインを設定し、SSL証明書の管理やドメイン所有権の検証を行う機能である。

### 本機能の処理概要

**業務上の目的・背景**：企業やプロジェクトでは、ブランドイメージの統一や信頼性向上のため、独自ドメインでWebサイトを公開する必要がある。Pagesドメイン管理機能は、GitLab Pagesで公開される静的サイトに対してカスタムドメインを設定し、DNSベースの所有権検証とSSL証明書管理を提供する。Let's Encrypt連携による自動SSL証明書発行も可能で、HTTPS対応を容易にする。

**機能の利用シーン**：プロジェクトのDeploy > Pagesメニューからドメイン設定を行う場面、新規カスタムドメインを追加する場面、SSL証明書をアップロードまたはLet's Encryptで自動取得する場面、ドメイン所有権を検証する場面などで利用される。

**主要な処理内容**：
1. カスタムドメインの登録（CRUD）
2. ドメイン所有権のDNS検証
3. SSL証明書の管理（ユーザー提供/Let's Encrypt自動発行）
4. 証明書の有効期限管理と自動更新
5. ドメイン検証失敗時の無効化・削除

**関連システム・外部連携**：DNS解決によるドメイン検証。Let's Encrypt（ACME）による自動SSL証明書発行。PagesDomainSslRenewalWorkerによる証明書更新。EventStoreへのドメインイベント発行。

**権限による制御**：Pagesの更新権限（authorize_update_pages!）が必要。プロジェクトのPages機能が有効であることが前提。カスタムドメイン数にはプロジェクトごとの上限あり。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 128 | Pagesドメイン設定 | 主画面 | カスタムドメインの設定・管理 |

## 機能種別

設定管理（CRUD操作）/ ドメイン検証

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| domain | String | Yes | カスタムドメイン名 | 有効なホスト名、ユニーク、Pagesルートドメインのサブドメイン不可 |
| user_provided_certificate | Text | No | ユーザー提供SSL証明書 | X.509証明書形式、中間証明書含む |
| user_provided_key | Text | No | ユーザー提供秘密鍵 | 証明書と一致、最大8192バイト（RSA） |
| auto_ssl_enabled | Boolean | No | Let's Encrypt自動SSL有効化 | true/false |

### 入力データソース

- ユーザー入力（フォーム）
- プロジェクト設定（project_settings）
- DNS TXTレコード（検証用）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @domain | PagesDomain | ドメイン情報 |
| domain_presenter | PagesDomainPresenter | 表示用ドメイン情報 |

### 出力先

- HTML画面（ドメイン設定・管理画面）
- JavaScript応答（削除時）

## 処理フロー

### 処理シーケンス

```
1. 前提条件チェック
   └─ require_pages_enabled!
2. 権限チェック
   └─ authorize_update_pages!
3. アクション実行
   └─ show: ドメイン詳細表示（未検証警告あり）
   └─ new: 新規ドメインフォーム表示
   └─ create: ドメイン作成（重複チェック含む）
   └─ update: 証明書更新
   └─ destroy: ドメイン削除
   └─ verify: DNS検証実行
   └─ retry_auto_ssl: Let's Encrypt再試行
   └─ clean_certificate: 証明書削除
4. イベント発行
   └─ PagesDomainCreated/Updated/DeletedEvent
5. レスポンス生成
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{Pages有効?}
    B -->|No| C[エラー表示]
    B -->|Yes| D{権限チェック}
    D -->|NG| E[403 Forbidden]
    D -->|OK| F{アクション判定}
    F -->|show| G{検証必要?}
    G -->|Yes| H[警告メッセージ表示]
    G -->|No| I[ドメイン詳細表示]
    F -->|create| J{重複チェック}
    J -->|重複あり| K[エラー表示]
    J -->|なし| L[ドメイン作成]
    L --> M[イベント発行]
    F -->|verify| N[DNS検証]
    N --> O{検証成功?}
    O -->|Yes| P[verified_at更新]
    O -->|No| Q[unverify/disable]
    F -->|update| R[証明書更新]
    F -->|destroy| S[ドメイン削除]
    S --> T[イベント発行]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-130-01 | ドメインユニーク | ドメイン名は全プロジェクトでユニーク（大文字小文字区別なし） | 作成時 |
| BR-130-02 | ルートドメイン禁止 | Pagesルートドメインのサブドメインは設定不可 | 作成時 |
| BR-130-03 | 検証期間 | 検証は7日間有効（VERIFICATION_PERIOD） | 検証時 |
| BR-130-04 | 削除猶予 | 検証失敗後1週間で自動削除（REMOVAL_DELAY） | 検証失敗時 |
| BR-130-05 | SSL更新閾値 | 有効期限30日前からSSL証明書更新対象（SSL_RENEWAL_THRESHOLD） | 自動SSL時 |
| BR-130-06 | カスタムドメイン上限 | プロジェクトごとのカスタムドメイン数に上限あり | 作成時 |
| BR-130-07 | 証明書鍵一致 | SSL証明書と秘密鍵は一致する必要がある | 証明書設定時 |

### 計算ロジック

**ドメイン検証コード**:
```ruby
verification_code = SecureRandom.hex(16)
verification_domain = "_gitlab-pages-verification-code.#{domain}"
keyed_verification_code = "gitlab-pages-verification-code=#{verification_code}"
```

**検証DNS確認**:
```ruby
# TXTレコードで以下のいずれかを確認
# 1. domain直接のTXTレコード
# 2. _gitlab-pages-verification-code.domain のTXTレコード
dns_record_present? = records.any? { |r| r == keyed_verification_code || r == verification_code }
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ドメイン作成 | pages_domains | INSERT | 新規ドメイン登録 |
| ドメイン更新 | pages_domains | UPDATE | 証明書・設定更新 |
| ドメイン削除 | pages_domains | DELETE | ドメイン削除 |
| 検証状態更新 | pages_domains | UPDATE | verified_at, enabled_until更新 |

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

#### pages_domains

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | domain, verified_at, enabled_until, certificate, key | project_id = @project.id | ドメイン取得 |
| INSERT | domain, verification_code, auto_ssl_enabled, certificate, key | project.pages_domains.create | 新規作成 |
| UPDATE | verified_at, enabled_until, remove_at | 検証結果に応じて | 検証状態更新 |
| UPDATE | certificate, key, certificate_source, certificate_valid_not_before, certificate_valid_not_after | ユーザー/Let's Encrypt証明書 | 証明書更新 |
| DELETE | - | domain.destroy | ドメイン削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| 403 | Forbidden | 権限不足 | アクセス拒否 |
| 404 | Not Found | Pages未有効/ドメイン不存在 | エラー画面表示 |
| 422 | Unprocessable | バリデーションエラー | フォーム再表示 |
| - | 重複エラー | ドメイン既存 | エラーメッセージ表示 |
| - | 検証失敗 | DNS検証不可 | 警告メッセージ表示 |

### リトライ仕様

- DNS検証：手動でverifyアクションを再実行
- Let's Encrypt：retry_auto_sslアクションで再試行

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

- ドメイン作成: CreateService内でcreate実行、イベント発行
- ドメイン更新: UpdateService内でupdate実行、イベント発行
- ドメイン削除: DeleteService内でdestroy実行、イベント発行
- 検証状態更新: VerifyPagesDomainService内でsave!(validate: false)

## パフォーマンス要件

- feature_category: pages
- DNS検証タイムアウト: 15秒（RESOLVER_TIMEOUT_SECONDS）
- SSL証明書鍵最大長: 8192バイト（MAX_CERTIFICATE_KEY_LENGTH）

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

- 秘密鍵は暗号化保存（attr_encrypted、AES-256-CBC）
- ドメイン所有権のDNS検証（TXTレコード確認）
- 証明書と鍵の一致検証
- 中間証明書の有効性検証
- HTTPS設定時の証明書必須チェック

## 備考

- Let's Encrypt連携で自動SSL証明書発行対応（auto_ssl_enabled）
- PagesDomainSslRenewalWorkerで証明書の自動更新
- EventStoreでドメインイベント（Created/Updated/Deleted）を発行
- EE版で追加機能あり（prepend_mod_with）

---

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

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

### 推奨読解順序

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

Pagesドメインモデルの構造と検証ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PagesDomain | `app/models/pages_domain.rb` | ドメインモデル、バリデーション、SSL管理 |

**読解のコツ**:
- **9-11行目**: VERIFICATION_KEY、VERIFICATION_THRESHOLD、SSL_RENEWAL_THRESHOLDの定数定義
- **17-19行目**: certificate_source、scope、usageのenum定義
- **27-44行目**: バリデーションルール（ドメイン、証明書、鍵）
- **49-53行目**: 秘密鍵の暗号化設定（attr_encrypted）
- **92-106行目**: verified?、enabled?、https?メソッド
- **177-193行目**: verification_domain、keyed_verification_code、verification_recordメソッド
- **235-243行目**: カスタムドメイン数上限チェック

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

コントローラーの各アクションを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Projects::PagesDomainsController | `app/controllers/projects/pages_domains_controller.rb` | 各アクションの処理フロー |

**主要処理フロー**:
1. **6-8行目**: before_actionでのPages有効化・権限・ドメイン取得
2. **14-18行目**: showアクション - 未検証時の警告表示
3. **24-34行目**: verifyアクション - VerifyPagesDomainService呼び出し
4. **36-40行目**: retry_auto_sslアクション - RetryAcmeOrderService呼び出し
5. **46-54行目**: createアクション - CreateService呼び出し
6. **56-66行目**: updateアクション - UpdateService呼び出し
7. **68-79行目**: destroyアクション - DeleteService呼び出し
8. **81-88行目**: clean_certificateアクション - 証明書クリア

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

各CRUD操作とドメイン検証のサービスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Pages::Domains::CreateService | `app/services/pages/domains/create_service.rb` | 作成ロジック、重複チェック |
| 3-2 | Pages::Domains::UpdateService | `app/services/pages/domains/update_service.rb` | 更新ロジック |
| 3-3 | Pages::Domains::DeleteService | `app/services/pages/domains/delete_service.rb` | 削除ロジック |
| 3-4 | VerifyPagesDomainService | `app/services/verify_pages_domain_service.rb` | DNS検証ロジック |
| 3-5 | Pages::Domains::RetryAcmeOrderService | `app/services/pages/domains/retry_acme_order_service.rb` | Let's Encrypt再試行 |

**CreateService（6-28行目）**:
- 権限チェック後、既存ドメインの重複確認
- 重複時は適切なエラーメッセージを設定
- 作成成功時はPagesDomainCreatedEventを発行

**VerifyPagesDomainService（19-31行目）**:
- verification_enabledとdns_record_presentをチェック
- 成功時: verify_domain!（verified_at、enabled_until更新）
- 失敗時: unverify_domain!またはdisable_domain!

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

```
Projects::PagesDomainsController
    │
    ├─ show (action)
    │      └─ domain_presenter.needs_verification? → 警告表示
    │
    ├─ new (action)
    │      └─ @project.pages_domains.new
    │
    ├─ create (action)
    │      └─ Pages::Domains::CreateService.new(@project, current_user, params).execute
    │             ├─ PagesDomain.find_by_domain_case_insensitive（重複チェック）
    │             ├─ project.pages_domains.create
    │             └─ Gitlab::EventStore.publish(PagesDomainCreatedEvent)
    │
    ├─ update (action)
    │      └─ Pages::Domains::UpdateService.new(@project, current_user, params).execute(@domain)
    │             ├─ domain.update(params)
    │             └─ Gitlab::EventStore.publish(PagesDomainUpdatedEvent)
    │
    ├─ destroy (action)
    │      └─ Pages::Domains::DeleteService.new(@project, current_user).execute(@domain)
    │             ├─ domain.destroy
    │             └─ Gitlab::EventStore.publish(PagesDomainDeletedEvent)
    │
    ├─ verify (action)
    │      └─ VerifyPagesDomainService.new(@domain).execute
    │             ├─ dns_record_present?（DNS TXTレコード確認）
    │             ├─ verify_domain!（検証成功）
    │             ├─ unverify_domain!（検証失敗）
    │             └─ disable_domain!（期限切れ）
    │
    ├─ retry_auto_ssl (action)
    │      └─ Pages::Domains::RetryAcmeOrderService.new(@domain).execute
    │             ├─ domain.update!(auto_ssl_failed: false)
    │             ├─ PagesDomainSslRenewalWorker.perform_async
    │             └─ Gitlab::EventStore.publish(PagesDomainUpdatedEvent)
    │
    └─ clean_certificate (action)
           └─ Pages::Domains::UpdateService（証明書をnilに更新）
```

### データフロー図

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

params ─────────────────▶ PagesDomainsController ───────────▶ HTML View
(domain, certificate)           │
                          ┌─────┴─────┐
                          │           │
                     CreateService  VerifyService
                          │           │
                    pages_domains  DNS Resolver
                          │           │
                   ┌──────┴──────┐    │
                   │             │    │
              EventStore     DB Update │
                   │             │    │
        PagesDomainCreatedEvent  │  verified_at
        PagesDomainUpdatedEvent  │  enabled_until
        PagesDomainDeletedEvent  │    │
                   │             │    │
                   └─────────────┴────┘
                               │
                               ▼
                        Response/Redirect
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Projects::PagesDomainsController | `app/controllers/projects/pages_domains_controller.rb` | コントローラー | エントリーポイント |
| PagesDomain | `app/models/pages_domain.rb` | モデル | ドメイン管理、バリデーション |
| Pages::Domains::CreateService | `app/services/pages/domains/create_service.rb` | サービス | ドメイン作成 |
| Pages::Domains::UpdateService | `app/services/pages/domains/update_service.rb` | サービス | ドメイン更新 |
| Pages::Domains::DeleteService | `app/services/pages/domains/delete_service.rb` | サービス | ドメイン削除 |
| VerifyPagesDomainService | `app/services/verify_pages_domain_service.rb` | サービス | DNS検証 |
| Pages::Domains::RetryAcmeOrderService | `app/services/pages/domains/retry_acme_order_service.rb` | サービス | Let's Encrypt再試行 |
| PagesDomainSslRenewalWorker | `app/workers/pages_domain_ssl_renewal_worker.rb` | ワーカー | SSL証明書更新 |
| PagesDomainPresenter | `app/presenters/pages_domain_presenter.rb` | プレゼンター | 表示用データ整形 |
| PagesDomainCreatedEvent | `app/events/pages_domain_created_event.rb` | イベント | 作成イベント |
| PagesDomainUpdatedEvent | `app/events/pages_domain_updated_event.rb` | イベント | 更新イベント |
| PagesDomainDeletedEvent | `app/events/pages_domain_deleted_event.rb` | イベント | 削除イベント |
