セキュリティ要件定義書とは
セキュリティ要件定義書は、システムが満たすべきセキュリティ対策を具体的な要件として定義した設計書です。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)の設計と、機能・データへのアクセス権限マトリクスを定義します。
| ロール | 商品管理 | 受注管理 | マスタ管理 | ユーザー管理 | システム設定 |
|---|---|---|---|---|---|
| システム管理者 | CRUD | CRUD | CRUD | CRUD | CRUD |
| 業務管理者 | CRUD | CRUD | CRU | 参照 | × |
| 一般ユーザー | CR | CRU | R | × | × |
| 閲覧専用 | R | R | R | × | × |
⚠️ 水平的アクセス制御(IDORへの対策)
同じロールのユーザーAとユーザーBが存在する場合、AがBのデータにアクセスできないよう「テナント制御・オーナーシップチェック」を実装します。APIレベルでのチェック(「このリソースのオーナーはリクエスト者か」)が必須です。フロントエンドの表示制御だけでは不十分です。
③ 通信暗号化・データ保護要件
| 対象 | 要件 |
|---|---|
| 通信経路暗号化 | 全通信をTLS 1.2以上で暗号化。TLS 1.0/1.1は無効化。HTTP通信はHTTPSにリダイレクト |
| SSL証明書 | 有効期限90日以内の証明書を使用(Let's Encrypt自動更新 or 商用証明書)。有効期限30日前にアラート |
| HSTS | Strict-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年 |
| エラーログ | 日時・エラーレベル・スタックトレース・リクエストID | 6ヶ月 |
⚠️ ログへのパスワード・個人情報の混入禁止
ログファイルにパスワード・クレジットカード番号・マイナンバーなどの機密情報が記録されないよう、ログ出力箇所でマスキング処理を実装します。また、ログファイル自体へのアクセスは管理者のみに制限します。
⑥ セキュリティテスト計画
| テスト種別 | 実施タイミング | ツール・手法 |
|---|---|---|
| 静的アプリケーションセキュリティテスト(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証明書有効期限チェックを自動化する
"""
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)などのマネージドサービスを使う場合は自動更新設定を有効にし、その確認もチェック項目に含めましょう。