Skip to content

契約・NDA管理設計

1. 概要

業務委託メンバーの契約管理、NDA(秘密保持契約)の電子署名、契約期間とアクセス権限の連動、退場時の手続きを体系的に管理するための設計書。

2. 初回ログイン時のNDA同意フロー

2.1 フロー概要

初回ログイン → NDA同意画面表示 → NDA内容確認 → 電子署名 → 同意記録保存 → アクセス権有効化

2.2 詳細フロー

┌─────────────────────────────────────────────┐
│ 1. 委託メンバーがGitLabに初回ログイン       │
│    (SSOまたはGitLabアカウント)               │
└──────────────┬──────────────────────────────┘


┌─────────────────────────────────────────────┐
│ 2. NDA同意状態チェック (API Middleware)      │
│    - DBで同意済みかを確認                    │
│    - 未同意の場合、NDA同意ページへリダイレクト │
└──────────────┬──────────────────────────────┘
               │ 未同意

┌─────────────────────────────────────────────┐
│ 3. NDA同意画面表示                           │
│    - NDA全文表示                              │
│    - スクロール完了必須                       │
│    - 同意チェックボックス                     │
│    - 電子署名入力欄                          │
└──────────────┬──────────────────────────────┘


┌─────────────────────────────────────────────┐
│ 4. 電子署名の実行                            │
│    - 署名者氏名入力                          │
│    - タイムスタンプ記録                       │
│    - IPアドレス記録                          │
└──────────────┬──────────────────────────────┘


┌─────────────────────────────────────────────┐
│ 5. 同意記録の保存                            │
│    - DBに同意情報を保存                      │
│    - 署名付きPDFを生成・保存                 │
│    - 確認メール送信(委託メンバー+管理者)   │
└──────────────┬──────────────────────────────┘


┌─────────────────────────────────────────────┐
│ 6. アクセス権の有効化                        │
│    - GitLab Developer権限の付与              │
│    - Issue単位のアクセス付与開始              │
└─────────────────────────────────────────────┘

2.3 NDA同意画面設計

画面レイアウト

┌──────────────────────────────────────────────────────────┐
│  ┌─────────────────────────────────────────────────────┐ │
│  │           秘密保持契約(NDA)同意                     │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                          │
│  以下のNDA内容をご確認の上、同意してください。              │
│  ※全文をスクロールしてお読みください。                     │
│                                                          │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                                                     │ │
│  │  第1条(目的)                                       │ │
│  │  本契約は、業務委託に伴い開示される秘密情報の...      │ │
│  │                                                     │ │
│  │  第2条(秘密情報の定義)                              │ │
│  │  本契約における秘密情報とは...                        │ │
│  │                                                     │ │
│  │  第3条(秘密保持義務)                                │ │
│  │  ...                                                │ │
│  │                                                     │ │
│  │  [スクロール可能エリア - 全文表示]                    │ │
│  │                                                     │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                          │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  署名者情報                                          │ │
│  │                                                     │ │
│  │  氏名:     [________________________]               │ │
│  │  メール:   outsource@example.com (自動入力)         │ │
│  │  日付:     2026-03-28 (自動入力)                    │ │
│  │                                                     │ │
│  │  ☐ 上記NDA内容を全て確認し、同意します。             │ │
│  │  ☐ 秘密情報の取り扱いについて理解しました。          │ │
│  │  ☐ 契約終了時にローカルデータを削除することに        │ │
│  │    同意します。                                      │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                          │
│           [同意して署名する]    [後で確認する]             │
│                                                          │
│  ※「後で確認する」を選択した場合、リポジトリへの          │
│    アクセスは制限されたままとなります。                    │
│                                                          │
└──────────────────────────────────────────────────────────┘

画面仕様

項目仕様
NDA全文表示エリアスクロール可能、高さ400px
スクロール完了検知最下部到達で同意チェックボックスを有効化
同意チェックボックス3項目全てチェック必須
署名者氏名手動入力(アカウント名と一致確認)
同意ボタン全チェック完了 + 氏名入力で有効化
「後で確認する」アクセスは制限されたまま、次回ログイン時に再表示

2.4 NDA同意のAPI実装

python
# NDA同意チェックミドルウェア
class NDACheckMiddleware:
    EXCLUDED_PATHS = ["/api/nda/", "/api/auth/", "/static/"]

    async def __call__(self, request, call_next):
        if any(request.url.path.startswith(p) for p in self.EXCLUDED_PATHS):
            return await call_next(request)

        user = get_current_user(request)
        if user and user.user_type == "outsource":
            nda_status = await get_nda_status(user.id)
            if not nda_status or not nda_status.is_agreed:
                return RedirectResponse("/nda/agree")

        return await call_next(request)

# NDA同意API
@router.post("/api/nda/agree")
async def agree_nda(request: NDAAgreeRequest, current_user: User):
    # バリデーション
    if not request.all_checkboxes_checked:
        raise HTTPException(400, "全ての同意項目にチェックしてください")
    if not request.signer_name:
        raise HTTPException(400, "署名者氏名を入力してください")

    # 同意記録の保存
    nda_record = NDARecord(
        user_id=current_user.id,
        signer_name=request.signer_name,
        agreed_at=datetime.utcnow(),
        ip_address=request.client.host,
        user_agent=request.headers.get("user-agent"),
        nda_version=CURRENT_NDA_VERSION,
    )
    await save_nda_record(nda_record)

    # 署名付きPDF生成
    pdf_path = await generate_signed_nda_pdf(nda_record)

    # 確認メール送信
    await send_nda_confirmation_email(current_user, pdf_path)

    # GitLabアクセス権の有効化
    await activate_gitlab_access(current_user.gitlab_user_id)

    # 監査ログ記録
    await log_audit("NDA_AGREED", current_user.id, detail={
        "nda_version": CURRENT_NDA_VERSION,
        "signer_name": request.signer_name,
    })

    return {"status": "success", "message": "NDA同意が完了しました"}

3. 電子署名の管理方法

3.1 署名データモデル

sql
CREATE TABLE nda_agreements (
    id              SERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id),
    gitlab_user_id  INTEGER NOT NULL,
    signer_name     VARCHAR(255) NOT NULL,
    email           VARCHAR(255) NOT NULL,
    nda_version     VARCHAR(20) NOT NULL,
    agreed_at       TIMESTAMP WITH TIME ZONE NOT NULL,
    ip_address      INET NOT NULL,
    user_agent      TEXT,
    pdf_storage_key VARCHAR(500),
    signature_hash  VARCHAR(64) NOT NULL,
    is_active       BOOLEAN DEFAULT TRUE,
    revoked_at      TIMESTAMP WITH TIME ZONE,
    revoked_reason  VARCHAR(500),
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_nda_user_id ON nda_agreements(user_id);
CREATE INDEX idx_nda_gitlab_user_id ON nda_agreements(gitlab_user_id);
CREATE INDEX idx_nda_active ON nda_agreements(is_active);

3.2 署名ハッシュの生成

署名の真正性を確認するため、以下の情報からSHA-256ハッシュを生成する:

python
import hashlib
import json

def generate_signature_hash(record: NDARecord) -> str:
    payload = json.dumps({
        "user_id": record.user_id,
        "signer_name": record.signer_name,
        "email": record.email,
        "nda_version": record.nda_version,
        "agreed_at": record.agreed_at.isoformat(),
        "ip_address": str(record.ip_address),
    }, sort_keys=True)

    return hashlib.sha256(payload.encode()).hexdigest()

3.3 署名付きPDFの生成・保存

項目仕様
PDF内容NDA全文 + 署名情報(氏名、日時、IP)
保存先S3互換ストレージ(暗号化有効)
暗号化AES-256 サーバーサイド暗号化
アクセス権管理者のみダウンロード可
保存パスnda-documents/{year}/{user_id}/{timestamp}.pdf
保存期間契約終了後5年

3.4 NDAバージョン管理

NDA内容が更新された場合、未同意の新バージョンに対して再同意を求める。

python
CURRENT_NDA_VERSION = "1.0.0"

async def check_nda_version(user_id: int) -> bool:
    """現行バージョンのNDAに同意済みかチェック"""
    latest = await get_latest_nda_agreement(user_id)
    if not latest:
        return False
    return latest.nda_version == CURRENT_NDA_VERSION and latest.is_active

4. 契約期間とアクセス権限の連動

4.1 契約データモデル

sql
CREATE TABLE outsource_contracts (
    id              SERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id),
    gitlab_user_id  INTEGER NOT NULL,
    contract_start  DATE NOT NULL,
    contract_end    DATE NOT NULL,
    status          VARCHAR(20) NOT NULL DEFAULT 'active',
    -- status: 'pending', 'active', 'expiring_soon', 'expired', 'terminated'
    auto_revoke     BOOLEAN DEFAULT TRUE,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_contract_status ON outsource_contracts(status);
CREATE INDEX idx_contract_end ON outsource_contracts(contract_end);

4.2 契約状態遷移

pending ──(NDA同意)──> active ──(期限30日前)──> expiring_soon ──(期限到来)──> expired

                         └──(契約解除)──> terminated

4.3 自動アクセス剥奪の実装

日次バッチ処理

python
# 日次実行: 契約期限チェック & アクセス剥奪
async def daily_contract_check():
    today = date.today()

    # 1. 期限切れ契約の処理
    expired_contracts = await get_contracts_by_status_and_date(
        status="active",
        contract_end_before=today
    )
    for contract in expired_contracts:
        await revoke_gitlab_access(contract.gitlab_user_id)
        await update_contract_status(contract.id, "expired")
        await log_audit("CONTRACT_EXPIRED", contract.user_id)
        await send_notification(
            to=["admin", contract.user_id],
            subject="契約期間終了に伴うアクセス権剥奪",
            body=f"契約期間が終了したため、GitLabへのアクセス権を剥奪しました。"
        )

    # 2. 期限間近の通知(30日前)
    expiring_contracts = await get_contracts_expiring_within(days=30)
    for contract in expiring_contracts:
        if contract.status == "active":
            await update_contract_status(contract.id, "expiring_soon")
            await send_notification(
                to=["admin"],
                subject=f"契約期限30日前通知: {contract.user_id}",
                body=f"契約終了日: {contract.contract_end}"
            )

    # 3. 期限間近の最終通知(7日前)
    final_warning = await get_contracts_expiring_within(days=7)
    for contract in final_warning:
        await send_notification(
            to=["admin", contract.user_id],
            subject="契約終了7日前の最終通知",
            body=f"契約終了日: {contract.contract_end}。終了後はアクセスが自動的に無効化されます。"
        )

4.4 通知スケジュール

タイミング通知先内容
契約終了30日前管理者契約更新検討の依頼
契約終了14日前管理者 + 委託メンバー契約更新有無の確認
契約終了7日前管理者 + 委託メンバー最終警告、退場準備依頼
契約終了3日前管理者 + 委託メンバー退場チェックリスト送付
契約終了当日管理者アクセス自動剥奪完了通知

4.5 契約更新フロー

更新判断 → 新規契約レコード作成 → expires_at更新 → NDA再確認(バージョン変更時のみ)

5. 退場時のチェックリスト

5.1 退場チェックリストデータモデル

sql
CREATE TABLE offboarding_checklists (
    id              SERIAL PRIMARY KEY,
    contract_id     INTEGER NOT NULL REFERENCES outsource_contracts(id),
    user_id         INTEGER NOT NULL REFERENCES users(id),
    status          VARCHAR(20) DEFAULT 'pending',
    -- status: 'pending', 'in_progress', 'completed'
    initiated_at    TIMESTAMP WITH TIME ZONE,
    completed_at    TIMESTAMP WITH TIME ZONE,
    completed_by    INTEGER REFERENCES users(id),
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE offboarding_checklist_items (
    id              SERIAL PRIMARY KEY,
    checklist_id    INTEGER NOT NULL REFERENCES offboarding_checklists(id),
    item_key        VARCHAR(100) NOT NULL,
    item_label      TEXT NOT NULL,
    category        VARCHAR(50) NOT NULL,
    is_completed    BOOLEAN DEFAULT FALSE,
    completed_at    TIMESTAMP WITH TIME ZONE,
    completed_by    VARCHAR(50),
    -- completed_by: 'system' (自動) or ユーザー名 (手動確認)
    evidence        TEXT,
    notes           TEXT
);

5.2 チェックリスト項目

カテゴリ: アクセス権削除(自動実行)

#項目実行者自動/手動確認方法
1GitLabプロジェクトメンバーから削除System自動API応答確認
2GitLab Personal Access Token無効化System自動API応答確認
3GitLab SSH鍵削除System自動API応答確認
4CI/CD関連の一時トークン無効化System自動API応答確認
5Webhook連携の無効化System自動API応答確認

カテゴリ: データ削除確認(手動確認)

#項目確認者自動/手動確認方法
6ローカルリポジトリの削除委託メンバー手動自己申告 + スクリーンショット
7ローカル環境の設定ファイル削除委託メンバー手動自己申告
8開発用ダミーデータの削除委託メンバー手動自己申告
9ブラウザ保存パスワード・Cookieの削除委託メンバー手動自己申告
10メール・チャットの業務関連データ削除委託メンバー手動自己申告

カテゴリ: 管理者確認

#項目確認者自動/手動確認方法
11未完了Issue/MRの引継ぎ確認管理者手動GitLab上で確認
12最終監査ログレビュー管理者手動監査ダッシュボード
13NDA継続義務の通知管理者手動メール送信確認
14退場完了報告管理者手動チェックリスト完了

5.3 自動退場処理の実装

python
async def execute_offboarding(contract_id: int):
    contract = await get_contract(contract_id)
    checklist = await create_offboarding_checklist(contract)

    # 自動実行項目の処理
    auto_items = {
        "remove_project_member": remove_gitlab_member,
        "revoke_pat": revoke_personal_access_tokens,
        "remove_ssh_keys": remove_ssh_keys,
        "revoke_ci_tokens": revoke_ci_cd_tokens,
        "disable_webhooks": disable_user_webhooks,
    }

    for item_key, handler in auto_items.items():
        try:
            await handler(contract.gitlab_user_id)
            await mark_checklist_item_completed(
                checklist.id, item_key, completed_by="system"
            )
            await log_audit(f"OFFBOARDING_{item_key.upper()}", contract.user_id)
        except Exception as e:
            await log_audit(
                f"OFFBOARDING_{item_key.upper()}_FAILED",
                contract.user_id,
                detail={"error": str(e)}
            )
            await send_alert(
                severity="HIGH",
                message=f"退場処理の自動実行に失敗: {item_key} - {e}"
            )

    # 手動確認項目のリマインド送信
    await send_offboarding_reminder(contract.user_id, checklist.id)

    return checklist

5.4 退場時の通知テンプレート

委託メンバー向け

件名: 【重要】業務委託終了に伴う退場手続きのお願い

{signer_name} 様

契約期間の終了({contract_end})に伴い、退場手続きをお願いいたします。

以下の作業を実施し、完了をご報告ください:

1. ローカルリポジトリの削除
   - クローンした全リポジトリを完全削除してください
   - ゴミ箱も空にしてください

2. 設定ファイル・データの削除
   - .env ファイル等の設定ファイル
   - 開発用のダミーデータ
   - 業務関連のメモ・ドキュメント

3. ブラウザデータの削除
   - GitLab関連の保存パスワード
   - Cookie・セッション情報

4. 未完了タスクの引継ぎ
   - 未完了のIssue/MRがある場合は管理者にご連絡ください

確認用URL: {checklist_url}

※NDAに基づく秘密保持義務は契約終了後も継続します。

ご不明点がございましたらお問い合わせください。

5.5 退場完了の確認フロー

退場処理開始

  ├─ 自動処理(即時実行)
  │   ├─ GitLabアクセス権削除
  │   ├─ トークン無効化
  │   └─ SSH鍵削除

  ├─ 委託メンバー確認(3日以内)
  │   ├─ ローカルデータ削除の自己申告
  │   └─ チェックリストの各項目に確認チェック

  ├─ 管理者確認(5日以内)
  │   ├─ 未完了タスクの引継ぎ確認
  │   ├─ 最終監査ログレビュー
  │   └─ NDA継続義務通知の送信

  └─ 退場完了
      ├─ 全項目完了の確認
      ├─ 退場完了記録の保存
      └─ 契約ステータスを 'expired' に更新

6. 全体タイムライン

[契約開始]

  ├─ アカウント作成
  ├─ 初回ログイン → NDA同意
  ├─ アクセス権有効化

  │  ... 業務遂行期間 ...

  ├─ 契約終了30日前: 管理者通知
  ├─ 契約終了14日前: 双方通知
  ├─ 契約終了7日前: 最終警告
  ├─ 契約終了3日前: 退場チェックリスト送付

[契約終了]

  ├─ 自動アクセス剥奪
  ├─ 退場チェックリスト実施
  ├─ 管理者最終確認

[退場完了]

  └─ NDA秘密保持義務継続(契約終了後も有効)