0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

穴のないチーズは存在しない -- エンジニアのための多層防御設計入門

0
Last updated at Posted at 2026-04-16

深夜23時、認証が消えた

とあるWebサービスで深夜23時15分、Redisがメモリ不足で応答停止しました。セッション管理がRedisに依存していたため、セッションチェック関数がエラーをキャッチして return true を返した。認証ミドルウェアはその true をtruthy判定で通した。結果、認証なしでAPIにアクセス可能になりました。

23時45分、ユーザーから「ログインしてないのに他人のデータが見える」と報告が入るまで、30分間気づかなかった。

この事故で壊れた防御層は4つです。

  1. エラーハンドリング層: 例外時に true を返す設計
  2. 型チェック層: booleanとobjectを区別しない判定
  3. 監視層: 認証成功率の異常を検知していない
  4. フロントエンド層: サーバー認証に完全依存

どれか1つでも正しく作られていたら、事故は防げました。これが スイスチーズモデル の話です。

スイスチーズモデル -- 穴が重なった時にシステムは死ぬ

1990年、心理学者のJames Reasonが航空事故の分析から提唱したモデルです。防御システムをスイスチーズのスライスに見立てます。どのスライスにも穴がある。穴のないチーズは存在しません。でも、穴の位置がずれていれば、攻撃は途中で止まります。

防御層1  [○ ● ○]  ← 入力バリデーション
防御層2  [● ○ ●]  ← ビジネスロジック
防御層3  [○ ● ○]  ← データベース制約
防御層4  [● ○ ●]  ← 監視・アラート

●=穴(弱点)、○=防御

穴をゼロにすることはできない。穴が重ならないようにする。 これが多層防御の基本思想です。

航空業界の話を持ち出すと大げさに聞こえますが、冒頭のRedis事故はまさにこれです。エラーハンドリングの穴と型チェックの穴と監視の穴が一列に並んだ。単独では無害な穴が、組み合わさって重大事故になる。

Webアプリケーションの4層防御

スイスチーズモデルをWebアプリケーションに適用すると、典型的には4つの防御層に分かれます。

何を防ぐか 具体例
入力バリデーション 不正な入力 型チェック、範囲チェック、XSS対策
ビジネスロジック ルール違反 権限チェック、重複チェック、整合性検証
データベース制約 データ不整合 NOT NULL、UNIQUE、外部キー、CHECK制約
監視・アラート 異常の見逃し エラーレート監視、認証成功率、レイテンシ閾値

ポイントは 同じバグを複数の層で検出できるようにする ことです。入力バリデーションで弾き損ねても、ビジネスロジックで止まる。ビジネスロジックが通しても、データベース制約が弾く。全部すり抜けても、監視が異常を検知する。

「どこか1つで止まればいい」は楽観的すぎます。冒頭の事故がそれを証明しています。

サーキットブレーカー -- 連鎖崩壊を止める防御パターン

多層防御の具体的な実装パターンとして、サーキットブレーカーを紹介します。電気回路のブレーカーと同じ発想で、障害が連鎖するのを防ぎます。

実際にPythonで実装して検証しました。

class CircuitBreaker:
    def __init__(self, failure_threshold=3, recovery_timeout=30):
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = "CLOSED"  # CLOSED=正常, OPEN=遮断, HALF_OPEN=試行

    def call(self, func, fallback=None):
        if self.state == "OPEN":
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = "HALF_OPEN"
            elif fallback:
                return fallback()
            else:
                raise CircuitOpenError("Circuit is open")

        try:
            result = func()
            if self.state == "HALF_OPEN":
                self.state = "CLOSED"
                self.failure_count = 0
            return result
        except Exception:
            self.failure_count += 1
            self.last_failure_time = time.time()
            if self.failure_count >= self.failure_threshold:
                self.state = "OPEN"
            if fallback:
                return fallback()
            raise

検証結果

テスト 期待動作 結果
正常系 CLOSED、結果を返す
失敗3回 CLOSED→OPEN
OPEN状態でfallback fallback値を返す
OPEN状態でfallbackなし CircuitOpenError
recovery_timeout経過後 HALF_OPEN→成功でCLOSED

冒頭のRedis事故をサーキットブレーカーで防ぐとこうなります。

redis_breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

def check_session(session_id):
    def _get():
        return redis_client.get(session_id)

    result = redis_breaker.call(_get, fallback=lambda: None)
    #                                  ^^^^^^^^^^^^^^^^
    # trueではなくNoneを返す。これで穴が1つ塞がる
    return json.loads(result) if result else None

return true の代わりに fallbackで None を返す。None はfalsyなので、認証ミドルウェアで弾かれる。サーキットブレーカーという1つの層を追加するだけで、穴の重なりが断ち切れます。

明日から始める3つのこと

多層防御は大規模なリアーキテクトを必要としません。明日のPRから始められます。

1. エラーハンドリングで return true を書かない

例外時のデフォルト値は、安全側に倒す。認証なら false、データ取得なら null。「エラーだけど通す」は穴になります。

2. データベース制約を信頼する

アプリケーション層のバリデーションだけに頼らない。NOT NULL、UNIQUE、CHECK制約をデータベースに入れる。アプリが壊れても、データベースが最後の砦になります。テーブル定義を見直すだけなので、コード変更は不要です。

3. 認証成功率をモニタリングする

「認証成功率が95%を下回ったらアラート」。このルール1つで、冒頭の事故は23時15分に検知できました。30分も気づかない、ということはなくなります。

完璧な防御は不可能です。穴のないチーズは存在しません。でも、穴が重ならないように層を配置することはできる。1つの層が1つのPRで追加できるなら、今週中に4層全部揃えることも無理な話ではないはずです。


参考

  • James Reason 「Managing the Risks of Organizational Accidents」(1997)
  • Eric S. Raymond 「The Art of Unix Programming」 -- Rule 8: Robustness is the child of transparency and simplicity
  • Microsoft 「Circuit Breaker pattern」(Azure Architecture Center)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?