セキュリティ要件定義書とは

セキュリティ要件定義書は、システムが満たすべきセキュリティ対策を具体的な要件として定義した設計書です。OWASP Top 10(Webアプリケーションの重大なセキュリティリスク)を基準に、認証・認可・暗号化・入力検証・ログ・セキュリティテストの各領域で対策要件を定義します。

💡 OWASP Top 10(2021年版)への対応

A01:アクセス制御の失敗 / A02:暗号化の失敗 / A03:インジェクション / A04:安全でない設計 / A05:セキュリティの設定ミス / A06:脆弱で古いコンポーネント / A07:識別と認証の失敗 / A08:ソフトウェアとデータの整合性の失敗 / A09:セキュリティログとモニタリングの失敗 / A10:サーバーサイドリクエストフォージェリ

① 認証・アカウント管理要件

ユーザー認証の仕組みとアカウント管理ルールを定義します。

項目要件定義
認証方式メールアドレス+パスワード認証(社内システムはSSO / SAML2.0 連携を推奨)
多要素認証(MFA)管理者ロールは必須。一般ユーザーは任意(TOTP / SMS OTP)
パスワードポリシー最小12文字以上、英大文字・小文字・数字・記号をそれぞれ1文字以上含む
パスワードハッシュbcryptまたはArgon2id を使用。MD5/SHA-1は禁止
セッション管理JWT(RS256署名)。アクセストークン有効期限: 30分。リフレッシュトークン: 7日
ログイン失敗制御同一IPから5回連続失敗で15分ロックアウト。30回でIPブロック
パスワードリセットメールにワンタイムURL(有効期限30分)を送信。URL使用後即無効化
同時ログイン制御同一アカウントの同時ログイン数上限: 3セッション(超過時は古い順に無効化)

② 認可・アクセス制御要件

ロールベースアクセス制御(RBAC)の設計と、機能・データへのアクセス権限マトリクスを定義します。

ロール商品管理受注管理マスタ管理ユーザー管理システム設定
システム管理者CRUDCRUDCRUDCRUDCRUD
業務管理者CRUDCRUDCRU参照×
一般ユーザーCRCRUR××
閲覧専用RRR××

⚠️ 水平的アクセス制御(IDORへの対策)

同じロールのユーザーAとユーザーBが存在する場合、AがBのデータにアクセスできないよう「テナント制御・オーナーシップチェック」を実装します。APIレベルでのチェック(「このリソースのオーナーはリクエスト者か」)が必須です。フロントエンドの表示制御だけでは不十分です。

③ 通信暗号化・データ保護要件

対象要件
通信経路暗号化全通信をTLS 1.2以上で暗号化。TLS 1.0/1.1は無効化。HTTP通信はHTTPSにリダイレクト
SSL証明書有効期限90日以内の証明書を使用(Let's Encrypt自動更新 or 商用証明書)。有効期限30日前にアラート
HSTSStrict-Transport-Security: max-age=31536000; includeSubDomains; preload
機密データの保護個人情報・決済情報はDBで暗号化(AES-256-GCM)。バックアップファイルも暗号化必須
パスワード以外の機密項目APIキー・接続文字列は環境変数または Vault で管理。ソースコードへのハードコード禁止
セキュリティヘッダーContent-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin を設定

④ 入力検証・出力エスケープ要件

OWASP Top 10の「A03: インジェクション」「A03: XSS」に対する対策要件を定義します。

脅威対策要件
SQLインジェクションORMのプレースホルダー(パラメータバインディング)を必ず使用。動的SQL生成は禁止。特殊文字(' ; -- /* など)はエスケープ処理
XSS(クロスサイトスクリプティング)テンプレートエンジンの自動エスケープを有効化。innerHTML への直接代入禁止。ユーザー入力値は必ず出力時エスケープ
CSRF(クロスサイトリクエストフォージェリ)すべての状態変更リクエスト(POST/PUT/PATCH/DELETE)にCSRFトークンを検証。SameSite=Strict Cookie属性を付与
コマンドインジェクションOSコマンド実行は原則禁止。必要な場合はサニタイズ後に引数として渡す(シェル展開なし)
ファイルアップロードMIMEタイプ検証・拡張子ホワイトリスト制御・ウイルススキャン(ClamAV等)を実施。アップロード先はWebルート外
APIレートリミットログインAPI: 5回/分、その他API: 60回/分をIP単位で制限。超過時は429を返却

⑤ 監査ログ・証跡要件

インシデント発生時の追跡性を担保するため、操作ログ・アクセスログ・エラーログの要件を定義します。

ログ種別記録すべき項目保持期間
認証ログ日時・ユーザーID・IPアドレス・操作(ログイン成功/失敗/ロックアウト/ログアウト)1年
操作ログ(監査ログ)日時・ユーザーID・テーブル名・操作(INSERT/UPDATE/DELETE)・変更前後の値5年(法的要件)
アクセスログ日時・IPアドレス・URL・HTTPメソッド・ステータスコード・レスポンスタイム1年
エラーログ日時・エラーレベル・スタックトレース・リクエストID6ヶ月

⚠️ ログへのパスワード・個人情報の混入禁止

ログファイルにパスワード・クレジットカード番号・マイナンバーなどの機密情報が記録されないよう、ログ出力箇所でマスキング処理を実装します。また、ログファイル自体へのアクセスは管理者のみに制限します。

⑥ セキュリティテスト計画

テスト種別実施タイミングツール・手法
静的アプリケーションセキュリティテスト(SAST)CI/CDパイプライン(コミット時)bandit(Python)/ semgrep / SonarQube
依存ライブラリ脆弱性スキャンCI/CDパイプライン(日次)pip-audit / OWASP Dependency Check / Snyk
動的アプリケーションセキュリティテスト(DAST)ステージング環境(リリース前)OWASP ZAP / Burp Suite
ペネトレーションテスト年1回・大規模機能追加時外部セキュリティ会社に委託
SSL/TLS設定検証リリース前・証明書更新時SSL Labs(ssllabs.com)で A評価以上を目標

Python Tips — SSL証明書有効期限チェックを自動化する

Python — SSL証明書有効期限チェック
"""
SSL証明書の有効期限を複数ホストに対して一括確認し、期限切れ直前のホストを検出する。
pip install pandas openpyxl

活用例: 社内・外部連携先サーバーの証明書を定期監視するCron Job
"""
import ssl
import socket
from datetime import datetime, timezone
from dataclasses import dataclass
from typing import Optional
import pandas as pd


@dataclass
class SSLCheckResult:
    host: str
    port: int
    status: str           # "OK" | "WARNING" | "EXPIRED" | "ERROR"
    expiry_date: Optional[datetime]
    days_remaining: Optional[int]
    issuer: str
    subject: str
    error_message: str


def check_ssl_certificate(host: str, port: int = 443, timeout: float = 10.0) -> SSLCheckResult:
    """SSL証明書の有効期限と発行者情報を取得する"""
    context = ssl.create_default_context()
    try:
        with socket.create_connection((host, port), timeout=timeout) as sock:
            with context.wrap_socket(sock, server_hostname=host) as ssock:
                cert = ssock.getpeercert()

        # 有効期限の解析
        expiry_str = cert.get("notAfter", "")
        expiry_date = datetime.strptime(expiry_str, "%b %d %H:%M:%S %Y %Z")
        expiry_date = expiry_date.replace(tzinfo=timezone.utc)
        now = datetime.now(timezone.utc)
        days_remaining = (expiry_date - now).days

        # 発行者・サブジェクト
        issuer = dict(x[0] for x in cert.get("issuer", []))
        subject = dict(x[0] for x in cert.get("subject", []))
        issuer_str = issuer.get("organizationName", "Unknown")
        subject_str = subject.get("commonName", host)

        # ステータス判定
        if days_remaining < 0:
            status = "EXPIRED"
        elif days_remaining <= 30:
            status = "WARNING"   # 30日以内: 警告
        else:
            status = "OK"

        return SSLCheckResult(
            host=host, port=port, status=status,
            expiry_date=expiry_date, days_remaining=days_remaining,
            issuer=issuer_str, subject=subject_str, error_message=""
        )

    except ssl.SSLCertVerificationError as e:
        return SSLCheckResult(
            host=host, port=port, status="ERROR",
            expiry_date=None, days_remaining=None,
            issuer="", subject="",
            error_message=f"証明書検証エラー: {e}"
        )
    except (socket.timeout, ConnectionRefusedError) as e:
        return SSLCheckResult(
            host=host, port=port, status="ERROR",
            expiry_date=None, days_remaining=None,
            issuer="", subject="",
            error_message=f"接続エラー: {e}"
        )


def check_all_hosts(hosts: list[tuple[str, int]]) -> pd.DataFrame:
    """複数ホストのSSL証明書を一括チェックしてExcel出力する"""
    results = []
    for host, port in hosts:
        print(f"チェック中: {host}:{port}")
        r = check_ssl_certificate(host, port)
        results.append({
            "ホスト": r.host,
            "ポート": r.port,
            "ステータス": r.status,
            "有効期限": r.expiry_date.strftime("%Y-%m-%d") if r.expiry_date else "—",
            "残日数": r.days_remaining if r.days_remaining is not None else "—",
            "発行者": r.issuer,
            "サブジェクト": r.subject,
            "エラー": r.error_message,
        })

    df = pd.DataFrame(results)

    # Excel出力(条件付き書式: ステータス列に色付け)
    output_path = f"ssl_check_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
    with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
        df.to_excel(writer, sheet_name="SSL証明書チェック結果", index=False)
        ws = writer.sheets["SSL証明書チェック結果"]
        from openpyxl.styles import PatternFill, Font
        colors = {"OK": "C6EFCE", "WARNING": "FFEB9C", "EXPIRED": "FFC7CE", "ERROR": "D3D3D3"}
        for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
            status_cell = row[2]  # ステータス列(C列)
            color = colors.get(status_cell.value, "FFFFFF")
            status_cell.fill = PatternFill(fill_type="solid", fgColor=color)

    print(f"\n✅ チェック完了 → {output_path}")
    # 期限切れ・警告ホストのみ表示
    warn = df[df["ステータス"].isin(["WARNING", "EXPIRED", "ERROR"])]
    if not warn.empty:
        print(f"\n⚠️ 要対応ホスト ({len(warn)}件):")
        print(warn[["ホスト", "ステータス", "有効期限", "残日数"]].to_string(index=False))
    return df


if __name__ == "__main__":
    # チェック対象ホスト一覧
    target_hosts = [
        ("example.com", 443),
        ("api.example.com", 443),
        ("payment.external-partner.com", 443),
    ]
    check_all_hosts(target_hosts)

監視の自動化

このスクリプトをCronで週次実行し、WARNING または EXPIRED が検出されたらメール通知するパイプラインを組むと、証明書の期限切れ忘れを防げます。AWS Certificate Manager(ACM)などのマネージドサービスを使う場合は自動更新設定を有効にし、その確認もチェック項目に含めましょう。