付録D: セキュリティベストプラクティス

セキュリティ確認の基本手順

セキュリティ上の問題は、コード例だけでは完結しません。ゲノムデータや研究データを扱う場合は、インシデント対応と平時運用の両方で 症状 / 原因 / 確認コマンド / 対処 / 再発防止 を分けて記録します。

  • 症状: 想定外のユーザーがデータを読める、鍵やtokenがリポジトリに混入する、監査ログが残っていない、暗号化設定が環境ごとに異なる。
  • 原因: 最小権限の未適用、秘密情報の配布経路不備、設定差分、ログ保管期間不足、バックアップや一時ファイルの見落とし。
  • 確認コマンド: git status --short, git log -- <path>, grep -RE "PRIVATE KEY|TOKEN|PASSWORD" ., クラウドIAM/Secrets/監査ログの一覧確認を行う。
  • 対処: 秘密情報の失効とローテーション、権限の縮小、監査ログの有効化、暗号化鍵のKMS/Vault管理、不要なコピーの削除を行う。
  • 再発防止: secret scan、アクセス権レビュー、鍵ローテーション手順、バックアップ復旧訓練、監査ログ確認を定期運用に入れる。

ゲノムデータの安全な取り扱い

  • 症状: サンプルIDや機微データが平文で保存される、再識別キーと解析データが同じ場所に置かれる、暗号化鍵の所在が不明になる。
  • 原因: 仮名化、匿名化、暗号化、鍵管理、アクセス制御の責務を分離していない。
  • 確認コマンド: grep -RE "sample_id|patient|subject" <data-dir>, find <data-dir> -maxdepth 2 -type f, KMS/Vault/IAM設定の一覧で保存場所と権限を確認する。
  • 対処: HMAC等の秘密あり仮名化、暗号化 at rest、鍵の分離管理、不要コピー削除、保持期間ポリシーを適用する。
  • 再発防止: データ分類、鍵管理手順、再識別キーの保管場所、監査ログ、削除・ローテーション手順を運用文書に残す。

🧪 概念例(安全な取り扱いの説明例)

import base64
import hashlib
import hmac
from cryptography.fernet import Fernet

class SecureGenomicDataHandler:
    """ゲノムデータの安全な取り扱い"""
    
    def __init__(self, data_encryption_key, pseudonymization_secret):
        """
        引数:
          - data_encryption_key: 対称暗号鍵(Fernetの形式, bytes または UTF-8 文字列)。実運用ではKMS/Vault等で生成・保管し、
            アプリケーションには必要最小限の権限で安全に注入する。
          - pseudonymization_secret: 仮名化用の秘密情報(pepper, bytes または UTF-8 文字列)。ハッシュのみ(秘密なし)は推測され得るため、
            小さいID空間や辞書攻撃が成立するケースでは避ける。
        """
        if isinstance(data_encryption_key, str):
            data_encryption_key = data_encryption_key.encode("utf-8")
        if isinstance(pseudonymization_secret, str):
            pseudonymization_secret = pseudonymization_secret.encode("utf-8")
        self.cipher = Fernet(data_encryption_key)
        self._pseudonymization_secret = pseudonymization_secret
    
    def pseudonymize_sample_id(self, original_id, length=16):
        """
        サンプルIDの仮名化(再識別キーを保持する前提)。
        匿名化とは異なり、運用・法務要件に応じて再識別可能性を管理する。
        """
        digest = hmac.new(
            self._pseudonymization_secret,
            original_id.encode("utf-8"),
            hashlib.sha256,
        ).digest()
        # Base64URL形式にしてパディングを除去してから短縮(衝突リスクは length と入力規模で評価)
        token = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
        return token[:length]
    
    def encrypt_sensitive_data(self, data):
        """機密データの暗号化"""
        if isinstance(data, str):
            data = data.encode("utf-8")
        
        encrypted = self.cipher.encrypt(data)
        return encrypted

匿名化/仮名化/暗号化の整理(最低限):

  • 匿名化: 個人を合理的に再識別できない状態にする(一般に要件が厳しい)
  • 仮名化: 秘密情報(対応表/鍵)を適切に分離・管理し、識別子を別名に置換する
  • 暗号化: 鍵があれば復号可能。鍵管理が不適切だと保護にならない

注意点(誤運用の典型):

  • SHA-256等の「秘密なしハッシュ」は、入力空間が小さい場合(例: 連番ID)や辞書が作れる場合に推測され得るため、 匿名化として扱わない。必要に応じて、HMAC等の「秘密あり」方式を用い、秘密情報はKMS/Vault等で管理する。
  • 「安全な削除(上書き)」はストレージ種別(SSD/スナップショット/オブジェクトストレージ等)に依存し万能ではない。 実運用では暗号化(at rest)+ 鍵の破棄/ローテーション、アクセス制御、保持期間ポリシーを優先する。

アクセス制御の実装

  • 症状: 権限不足のユーザーがデータへアクセスできる、期限切れtokenが受理される、権限変更がログに残らない。
  • 原因: 認可チェック漏れ、token検証条件不足、ロール設計の曖昧さ、監査ログ未設定。
  • 確認コマンド: 代表ユーザーごとの権限一覧、token expiry、APIログ、失敗時ログ、監査ログを確認する。
  • 対処: deny-by-default、最小権限、短い有効期限、権限変更の承認フロー、認可テストを導入する。
  • 再発防止: 定期的なアクセス権レビュー、退職・異動時の権限剥奪、監査ログの保管期間、権限テストをCIや運用手順に入れる。

🧪 概念例(アクセス制御の説明例)

from functools import wraps
import jwt
from datetime import datetime, timedelta

class AccessControl:
    """ゲノムデータへのアクセス制御"""
    
    def __init__(self, secret_key):
        self.secret_key = secret_key
        
    def generate_token(self, user_id, permissions):
        """アクセストークンの生成"""
        payload = {
            'user_id': user_id,
            'permissions': permissions,
            'exp': datetime.utcnow() + timedelta(hours=24)
        }
        
        token = jwt.encode(payload, self.secret_key, algorithm='HS256')
        return token
    
    def verify_permission(self, required_permission):
        """権限検証デコレータ"""
        def decorator(func):
            @wraps(func)
            def wrapper(self, token, *args, **kwargs):
                try:
                    payload = jwt.decode(
                        token, 
                        self.secret_key, 
                        algorithms=['HS256']
                    )
                    
                    if required_permission not in payload['permissions']:
                        raise PermissionError("Insufficient permissions")
                        
                    return func(self, *args, **kwargs)
                    
                except jwt.ExpiredSignatureError:
                    raise ValueError("Token expired")
                except jwt.InvalidTokenError:
                    raise ValueError("Invalid token")
                    
            return wrapper
        return decorator

← 前へ: パフォーマンス最適化 目次に戻る 次へ: コード例集 →