監査ログ設計
1. 概要
業務委託メンバーの操作を適切に記録・監視し、不正アクセスや情報漏洩を早期に検知するための監査ログ設計書。GitLabの標準監査ログに加え、アプリケーション層での補完ログを定義する。
2. 記録対象の操作一覧
2.1 認証・アカウント操作
| カテゴリ | 操作 | 重要度 | 説明 |
|---|---|---|---|
| 認証 | ログイン成功 | INFO | ユーザーログイン |
| 認証 | ログイン失敗 | WARN | パスワード誤り、アカウントロック |
| 認証 | ログアウト | INFO | 明示的ログアウト |
| 認証 | 2FA設定変更 | WARN | 二要素認証の有効化/無効化 |
| 認証 | Personal Access Token作成 | WARN | トークン発行 |
| 認証 | SSH鍵追加/削除 | WARN | 鍵の変更 |
2.2 リポジトリ操作
| カテゴリ | 操作 | 重要度 | 説明 |
|---|---|---|---|
| Git | git clone | INFO | リポジトリクローン |
| Git | git push | INFO | コードプッシュ |
| Git | git pull/fetch | LOW | コード取得 |
| Git | force push | CRITICAL | 強制プッシュ(Protected Branchで禁止) |
| Branch | ブランチ作成 | INFO | 新規ブランチ |
| Branch | ブランチ削除 | WARN | ブランチ削除 |
2.3 Issue・MR操作
| カテゴリ | 操作 | 重要度 | 説明 |
|---|---|---|---|
| Issue | Issue閲覧 | LOW | Issueページアクセス |
| Issue | Issue取得(API) | INFO | API経由でのIssue一覧取得 |
| Issue | Issueコメント追加 | INFO | コメント投稿 |
| Issue | Issue状態変更 | INFO | Open/Close変更 |
| MR | MR作成 | INFO | Merge Request作成 |
| MR | MRコメント追加 | INFO | レビューコメント |
| MR | MRマージ | WARN | MRのマージ実行 |
| MR | MR承認 | INFO | MRのApprove |
2.4 管理操作
| カテゴリ | 操作 | 重要度 | 説明 |
|---|---|---|---|
| アクセス制御 | メンバー追加 | WARN | プロジェクトメンバー追加 |
| アクセス制御 | メンバー削除 | WARN | プロジェクトメンバー削除 |
| アクセス制御 | ロール変更 | CRITICAL | 権限レベルの変更 |
| 設定 | プロジェクト設定変更 | CRITICAL | プロジェクト設定の変更 |
| 設定 | CI/CD Variable変更 | CRITICAL | シークレット変数の追加/変更/削除 |
| 設定 | Protected Branch変更 | CRITICAL | ブランチ保護設定の変更 |
| 設定 | Webhook変更 | CRITICAL | Webhook設定の追加/変更/削除 |
2.5 ファイルアクセス
| カテゴリ | 操作 | 重要度 | 説明 |
|---|---|---|---|
| ファイル | 機密ファイル閲覧 | WARN | CODEOWNERS対象ファイルの閲覧 |
| ファイル | 機密ファイル変更試行 | CRITICAL | CI/CDで検知・ブロックされた変更 |
| ファイル | 大量ファイルダウンロード | WARN | アーカイブダウンロード等 |
3. ログフォーマット
3.1 標準ログフォーマット(JSON)
json
{
"timestamp": "2026-03-28T09:15:30.123Z",
"log_level": "INFO",
"event_type": "repository.push",
"user_id": 42,
"username": "outsource-member-01",
"user_type": "outsource",
"action": "push",
"resource_type": "repository",
"resource_id": "78",
"resource_name": "issueOutsource",
"detail": {
"branch": "feature/issue-12/add-login-validation",
"commits_count": 3,
"files_changed": 5
},
"ip_address": "203.0.113.45",
"user_agent": "git/2.43.0",
"session_id": "sess_abc123def456",
"request_id": "req_789ghi012jkl",
"result": "success",
"correlation_id": "corr_mno345pqr678"
}3.2 フィールド定義
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
timestamp | ISO 8601 | Yes | イベント発生日時(UTC) |
log_level | string | Yes | LOW / INFO / WARN / CRITICAL |
event_type | string | Yes | イベント種別(category.action 形式) |
user_id | integer | Yes | GitLabユーザーID |
username | string | Yes | GitLabユーザー名 |
user_type | string | Yes | internal / outsource / system |
action | string | Yes | 実行された操作 |
resource_type | string | Yes | 対象リソース種別 |
resource_id | string | Yes | 対象リソースID |
resource_name | string | No | 対象リソース名 |
detail | object | No | 操作の詳細情報 |
ip_address | string | Yes | クライアントIPアドレス |
user_agent | string | No | クライアントUser-Agent |
session_id | string | No | セッションID |
request_id | string | No | リクエストID |
result | string | Yes | success / failure / blocked |
correlation_id | string | No | 関連操作の追跡ID |
3.3 GitLab標準監査ログとの対応
GitLab Premium/Ultimateの監査ログ機能を活用し、カスタムログで補完する。
| ログソース | 取得方法 | 内容 |
|---|---|---|
| GitLab Audit Events API | GET /audit_events | メンバー操作、設定変更 |
| GitLab Events API | GET /events | Push、MR、Issue操作 |
| GitLab System Hooks | Webhook受信 | プロジェクト/グループレベルイベント |
| カスタムアプリケーションログ | アプリケーション出力 | Webhook処理、アクセス制御判定 |
4. ログ収集アーキテクチャ
GitLab Webhooks ──┐
├──> Log Collector API ──> Message Queue ──> Log Processor
GitLab API Poll ──┘ │
├──> Log Storage (Primary)
├──> Alert Engine
└──> Log Archive (Cold)4.1 コンポーネント
| コンポーネント | 技術選定 | 役割 |
|---|---|---|
| Log Collector API | FastAPI / Express | Webhook受信、API結果の正規化 |
| Message Queue | Redis Streams | ログの非同期処理バッファ |
| Log Processor | Python Worker | ログの解析、アラート判定 |
| Log Storage (Primary) | PostgreSQL | 構造化ログの保存、検索 |
| Log Archive (Cold) | S3互換ストレージ | 長期保存用アーカイブ |
| Alert Engine | 専用Worker | 不正アクセス検知、通知 |
5. 保存期間・ローテーション
5.1 保存期間
| データ種別 | 保存先 | 保存期間 | 備考 |
|---|---|---|---|
| CRITICAL/WARNログ | Primary DB | 2年 | コンプライアンス要件 |
| INFOログ | Primary DB | 6ヶ月 | 日常監視用 |
| LOWログ | Primary DB | 3ヶ月 | 参照頻度低 |
| 全ログ(アーカイブ) | Cold Storage | 5年 | 法的要件対応 |
| アラート履歴 | Primary DB | 3年 | インシデント追跡 |
5.2 ローテーション処理
yaml
# 日次バッチ: ログローテーション
log_rotation:
schedule: "0 2 * * *" # 毎日AM2:00 UTC
tasks:
- name: archive_expired_logs
description: 保存期間超過ログをCold Storageへ移動
steps:
- query: "SELECT * FROM audit_logs WHERE timestamp < NOW() - INTERVAL '{retention_period}'"
- export: "s3://audit-logs-archive/{year}/{month}/{day}/"
- delete: "DELETE FROM audit_logs WHERE id IN (archived_ids)"
- name: compress_archive
description: アーカイブファイルの圧縮
format: gzip
- name: verify_archive
description: アーカイブ整合性チェック
method: sha256_checksum5.3 データ量見積もり
| 項目 | 見積もり |
|---|---|
| 1委託メンバーあたり/日 | 約200-500レコード |
| 委託メンバー10名/月 | 約10万-15万レコード |
| 1レコードの平均サイズ | 約1KB |
| Primary DB月間増加量 | 約100-150MB |
| 年間アーカイブ量 | 約1-2GB |
6. 不正アクセス検知ルール
6.1 検知ルール一覧
| ルールID | ルール名 | 条件 | 重要度 | アクション |
|---|---|---|---|---|
| R001 | 大量API呼出 | 同一ユーザーが5分間に100回以上API呼出 | HIGH | アラート通知 + 一時ブロック |
| R002 | 業務時間外操作 | 22:00-07:00 (JST) の操作 | MEDIUM | アラート通知 |
| R003 | 未アサインIssueアクセス | 割当外のIssue/リポジトリへのアクセス試行 | HIGH | アラート通知 + ブロック |
| R004 | ログイン失敗連続 | 同一アカウントで10分間に5回以上ログイン失敗 | HIGH | アカウント一時ロック |
| R005 | 大量ファイルダウンロード | 1時間にリポジトリアーカイブ3回以上ダウンロード | HIGH | アラート通知 + ブロック |
| R006 | 機密ファイル変更試行 | CODEOWNERS対象ファイルへの変更Push | CRITICAL | アラート通知 + CI/CDブロック |
| R007 | 異常IPアドレス | 登録外IPからのアクセス | MEDIUM | アラート通知 |
| R008 | 権限昇格試行 | Developer権限でのAdmin操作API呼出 | CRITICAL | アラート通知 + セッション無効化 |
| R009 | 契約期間外アクセス | 契約終了後のアクセス試行 | CRITICAL | アラート通知 + ブロック |
| R010 | 休日・祝日操作 | 土日祝日の操作 | LOW | ログ記録のみ |
6.2 検知ルール実装例
python
# R001: 大量API呼出検知
class ExcessiveApiCallDetector:
WINDOW_SECONDS = 300 # 5分
THRESHOLD = 100
async def check(self, event: AuditLog) -> Optional[Alert]:
count = await self.redis.incr(
f"api_calls:{event.user_id}:{event.timestamp // self.WINDOW_SECONDS}"
)
await self.redis.expire(
f"api_calls:{event.user_id}:{event.timestamp // self.WINDOW_SECONDS}",
self.WINDOW_SECONDS
)
if count >= self.THRESHOLD:
return Alert(
rule_id="R001",
severity="HIGH",
user_id=event.user_id,
message=f"ユーザー {event.username} が{self.WINDOW_SECONDS}秒間に{count}回のAPI呼出を実行",
action="NOTIFY_AND_BLOCK"
)
return None
# R002: 業務時間外操作検知
class OffHoursDetector:
BUSINESS_HOURS_START = 7 # 07:00 JST
BUSINESS_HOURS_END = 22 # 22:00 JST
async def check(self, event: AuditLog) -> Optional[Alert]:
jst_hour = (event.timestamp.hour + 9) % 24 # UTC -> JST
if jst_hour < self.BUSINESS_HOURS_START or jst_hour >= self.BUSINESS_HOURS_END:
return Alert(
rule_id="R002",
severity="MEDIUM",
user_id=event.user_id,
message=f"ユーザー {event.username} が業務時間外に操作を実行 (JST {jst_hour}:00)",
action="NOTIFY"
)
return None6.3 アラート通知先
| 重要度 | 通知先 | 通知方法 | 対応期限 |
|---|---|---|---|
| CRITICAL | セキュリティチーム + プロジェクトOwner | Slack即座通知 + メール | 1時間以内 |
| HIGH | セキュリティチーム | Slack通知 + メール | 4時間以内 |
| MEDIUM | プロジェクトOwner | Slack通知 | 翌営業日 |
| LOW | - | ログ記録のみ | 月次レビュー |
6.4 アラートフォーマット
json
{
"alert_id": "alert_20260328_001",
"rule_id": "R001",
"severity": "HIGH",
"timestamp": "2026-03-28T10:30:00Z",
"user_id": 42,
"username": "outsource-member-01",
"summary": "大量API呼出を検知",
"detail": "5分間に150回のAPI呼出を実行",
"action_taken": "一時ブロック適用",
"requires_ack": true,
"notification_channels": ["slack", "email"]
}7. ダッシュボード
7.1 監視ダッシュボード項目
| パネル | 表示内容 | 更新頻度 |
|---|---|---|
| アクティブセッション | 現在ログイン中の委託メンバー一覧 | リアルタイム |
| 操作ヒートマップ | 時間帯別の操作数 | 5分 |
| アラート一覧 | 未対応アラートの一覧 | リアルタイム |
| API呼出数推移 | ユーザー別API呼出数のグラフ | 1分 |
| ログイン履歴 | 直近のログイン/ログアウト | リアルタイム |
| リソースアクセスTop10 | 最もアクセスされたリポジトリ/Issue | 1時間 |