『セキュア・バイ・デザイン 安全なソフトウェア設計』を読んだ

ドメイン駆動設計(DDD)の方法論をベースに、ドメインに対する深い理解を獲得し、その理解を設計に反映させてコードを書くことで、セキュアなソフトウェアの開発にも寄与するという主張を中心に据えた本。

ここで、この本では「設計」という言葉を「ソフトウェア開発のプロセス全体における能動的な意思決定」という意味で使っている。

この本を読むことで、セキュアコーディングという側面も考慮したDDDの活用法、実際に新規/既存のアプリケーションの開発にそれらのテクニックを導入して設計を改善する方法を学ぶことができる。

設計を中心に据える理由

開発において、コーディングを通じたモジュールやAPIの設計などに力を入れることが多い一方で、セキュリティについては後回しになりがちだ。そこで、この本で扱う方法論を取り入れることで、設計を通じてセキュリティも自然に向上できるという効果が得ることができる。結果として、セキュリティを単なる機能と捉え、バックログで後回しにされるというありがちな問題の解消に役立つ。

とはいえ、この本で紹介される手法は、あくまでもセキュリティにおける多層防御に役立つ方法の1つであり、システムに対してさまざまな側面からセキュリティ対策を施す必要があることには変わりない。

紹介されている方法

主に次のようなトピックが紹介されている。

  • セキュリティのCIA-T(機密性、完全性、可用性、追跡可能性)について
  • DDDの主要なコンセプト
  • 不変オブジェクト、契約による設計、妥当性検証などのテクニック
  • ドメイン・プリミティブやエンティティの導入
  • テスト自動化や機能フラグなどデリバリー・パイプラインでの考慮事項
  • 適切なエラーハンドリング
  • アプリケーション運用上のテクニック

興味深かったポイントをいくつか紹介する。詳しくは本を参照のこと。

ドメインに対する理解の深さ

浅いモデリングと深いモデリングという2つの観点でドメインモデリングを比較している。

浅いモデリングは、たとえば短絡的に商品の数量にint型を使うような、情報の意味が明示化されていないモデリングのことを指す。一方で、深いモデリングは、該当のコンテキストにおいてその数量自体にもルールが存在する(たとえば注文できる数量の上限下限が存在する、など)ことを理解し、そのルールを含めてモデリングすることを指す。

もちろん深いモデリングが望ましいとされていて、そのようなドメインモデルを作るにはドメインエキスパートとの協働が必要…というのはDDDの文脈でよく議論されているとおり。

設計のテクニック

ドメインに対する理解をもとに、最も基本的な構成要素として、特定のコンテキストで利用できるドメイン・プリミティブ1を作り出す。これは値オブジェクトをベースにしたもので、つねに不変条件を満たし、他のコンテキストの情報などがなくてもそれ自体で利用できる(完結した概念; conceptual whole)ドメインモデルである。

たとえば、「注文」のコンテキストにおいて、有効な数が1〜200個の「商品の数量」というドメインモデルはドメイン・プリミティブとして実現できる。

// Rustでのコード例

pub struct ProductQuantity {
    value: u8,
}

impl ProductQuantity {
    pub fn build(value: u8) -> Result<Self, &'static str> {
        if value == 0 || value > 200 {
            return Err("value must be between 1 and 200");
        }

        Ok(Self { value })
    }

    pub fn value(&self) -> u8 {
        self.value
    }

    pub fn add(&self, addend: u8) -> Result<Self, &'static str> {
        Self::build(self.value + addend)
    }
}

深いモデリングで得られたモデルの一部をドメイン・プリミティブにすることで、設計とセキュリティの両方でよい効果がある。

上記コードだと、ある関数に商品の数量を引数として渡すとき、このドメイン・プリミティブを使えば表現が明示的になるし、このモデルを通すことでバリデーションが実行されることを保証できるので、他の箇所にバリデーションロジックが分散するのを避けられる。また、外部から入力された整数値をそのまま商品の数量とみなさず、いったん不変条件を持つドメイン・プリミティブを通すことになるので、負の数を渡してカートの金額を減らそうとするかもしれない攻撃者にシステムの完全性を侵害される可能性を減らせる。

ドメイン・プリミティブは他のドメイン・プリミティブを持つこともある。エンティティを含め必ずドメイン・プリミティブを使うことで、データが正しい状態にあることを保証できる。

他のテクニックとしては、機密情報へのRead-Onceオブジェクトの利用、完全性を保証するエンティティの生成方法、複雑化したエンティティの状態管理などについて紹介されている。どれも読みやすく変更しやすいコードとセキュアなコードの両方に役立つものである。

安全なエラーハンドリング

エラーハンドリングについても、例外をビジネス例外と技術的例外に分類したのち、例外のデータに起因する情報漏洩に対する対策や、そもそも例外を使わないエラーハンドリング(ActiveModel::ErrorsやResult型などを想像するとわかりやすい)について紹介されている。

以前このブログでも書いたことがあるが、本当に例外的なエラーはバグや技術的に復旧できない問題のはずなので、できるだけ例外を使わずにエラーハンドリングすべしという主張は納得感がある。

blog.kymmt.com

それがセキュリティ向上にもつながるなら尚更だ。

安全なデリバリーや運用

ドメインモデルのコーディング以外の話題として、デリバリーや運用に関するテクニックも紹介されている。例えば、最近はデリバリーにおいて機能フラグを使い、未完成の機能も含めてトランクベース開発するという方法が一般的になっている。しかし、機能フラグが増えるとフラグ間の関係が煩雑になりがちで、かつ設定にミスがあり意図せず機能を公開してしまうと事故につながりかねない。そこで、テストで機能フラグやフラグを利用するロジックが適切に動いているか検証することが推奨されている。

また、システム管理サービス(要は社内管理画面)の重要性も説いている。運用で直接SQLを発行したりSSHでサーバに入るのではなく、顧客に提供しているサービス本体のアプリとは別のサービスとしてシステム管理サービスを運用するのがよいとされている。また、外から入ってきた文字列や機微情報を検証なくログに出力すると攻撃につながるという、いかにもやってしまいそうなケースについても、対象ドメインにおいて検証すべき外部入力や秘匿情報がなんなのかモデリングを通じて理解し、ドメイン・プリミティブやロガーを適切に使って防ぐべきだとしている。

所感

実事例やレガシーアプリケーションへの導入方法の章でも説明されている通り、まず取り組むべきは対象ドメインを詳しく理解するということだった。DDDではとかくコーディングに関するテクニックが着目されがちだが、ドメインの理解やコンテキストの分析が重要ということだろうと思う。ドメインで発生するイベントやリソースについて有識者も交えてしっかり開発者が理解し、そのあとにそれらを抽象化したドメインモデルをドメイン・プリミティブやエンティティなどコードとして落とし込むことが、セキュアなソフトウェア開発に必要な1つの要素なのだろうと思う。


  1. 書籍での表記どおりに中黒を入れている