セキュアバイ・デザイン - セキュリティを設計に組み込む

「セキュアバイ・デザイン」(Dan Bergh Johnsson、Daniel Deogun、Daniel Sawano著)は、セキュリティをソフトウェア設計の中心に据えるという考え方を提唱する書籍です。セキュリティを後付けの対策としてではなく、設計の段階から組み込むアプローチについて学んでいます。

本書の概要

「セキュアバイ・デザイン」はセキュリティ脆弱性を防ぐために、ソフトウェアの設計段階からセキュリティを考慮するという考え方です。脆弱性の多くは、設計上の欠陥や実装時のミスから生まれます。 本書はドメイン駆動設計(DDD)の原則を用いながら、セキュアなソフトウェアを設計する方法を解説しています。

印象に残ったコンセプト

1. ドメインプリミティブ

本書で最も印象的だったのがドメインプリミティブという概念です。これは、ビジネスルールとバリデーションをカプセル化した値オブジェクトのことです。

// 悪い例:プリミティブ型をそのまま使う
function sendEmail(email: string) {
  // emailが有効なメールアドレスか保証されない
  mailService.send(email);
}

// 良い例:ドメインプリミティブを使う
class EmailAddress {
  private readonly value: string;

  constructor(email: string) {
    if (!this.isValid(email)) {
      throw new Error('Invalid email address');
    }
    this.value = email;
  }

  private isValid(email: string): boolean {
    // バリデーションロジック
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  toString(): string {
    return this.value;
  }
}

function sendEmail(email: EmailAddress) {
  // EmailAddressのインスタンスであれば必ず有効
  mailService.send(email.toString());
}

ドメインプリミティブを使うことで、不正な値がシステム内を流れることを防ぎます。バリデーションが一箇所に集約されるため、コードの重複も減り、保守性も向上します。

2. セキュリティを不変条件として扱う

セキュリティ要件を「満たすべき不変条件」として設計に組み込むという考え方も重要です。

例えば、ユーザーが自分のデータにしかアクセスできないという要件を考えます。

// 悪い例:呼び出し側が権限チェックの責任を負う
class OrderRepository {
  findById(orderId: string): Order {
    return database.findOrder(orderId);
  }
}

// 呼び出し側で毎回チェック(漏れる可能性がある)
const order = orderRepository.findById(orderId);
if (order.userId !== currentUser.id) {
  throw new Error('Unauthorized');
}

// 良い例:権限チェックをドメインモデルに組み込む
class OrderRepository {
  findByIdForUser(orderId: string, userId: string): Order {
    const order = database.findOrder(orderId);
    if (order.userId !== userId) {
      throw new Error('Unauthorized');
    }
    return order;
  }
}

// 呼び出し側はシンプルに
const order = orderRepository.findByIdForUser(orderId, currentUser.id);

このアプローチにより、権限チェックを忘れるというミスを防げます。

3. 失敗の想定

「完璧なコードは書けない」という前提に立ち、失敗を想定した設計を行うことの重要性も説かれています。

  • 入力検証: すべての外部入力を疑う
  • 出力エスケープ: 常にコンテキストに応じたエスケープを行う
  • 最小権限の原則: 必要最低限の権限のみを与える
  • 多層防御: 一つの防御が破られても、他の層が守る

4. セキュリティと可読性のトレードオフ

セキュリティを高めることが必ずしも複雑さを増幅させるわけではなく、適切な抽象化とカプセル化によってコードはシンプルになります。

DDDとの関連性

本書はドメイン駆動設計の原則を活用しています。DDDの以下の要素がセキュリティに寄与します。

  • 値オブジェクト: 不正な状態を表現できないようにする
  • エンティティ: ライフサイクルを通じて整合性を保つ
  • 集約: トランザクション境界とセキュリティ境界を一致させる
  • ドメインサービス: 複雑なセキュリティロジックをカプセル化する

DDDは単にビジネスロジックを整理するだけでなく、セキュアなシステムを作るうえでも有用という考えですね。

実践への示唆

  • プリミティブ型の安易な使用を避ける。stringnumberをそのまま渡すのではなくドメインプリミティブでラップする
  • API/データベース/外部サービスとの境界で必ず検証を行う
  • コンパイル時に多くのエラーを検出できるような型を設計する
  • セキュリティレビューを設計段階で行う