先日、セキュア・バイ・デザインを読み切りました。
セキュア・バイ・デザイン 安全なソフトウェア設計 | Dan Bergh Johnsson, Daniel Deogun, Daniel Sawano, 須田智之 | Amazon
最初だけ流し読みしてずっと積読してたので1年越しに...。
会社で輪読会を週1回やって半年ちょっとで読み切りました。初めて知ったり参考になる部分も多かったけど、あるあると思うところも結構あって、自分もここ数年の経験でいろいろ知見が身に付いたのかなと思ったりしました。
以下、輪読会で書き溜めてた読書メモ。
第1章
学んだ / 知った
- CIA-T
- 機密性 (confidentiality)
- 完全性 (integrity)
- 可用性 (availability)
- 追跡可能性 (traceability)
- ソフトウェア開発で行われるすべての行為が設計の行為
- ソフトウェアの設計を正しく行えば真に安全なソフトウェアを実装できる
- 優れた設計パターンを取り入れることで安全なコードを書けるようになる
- ドメインを意識することで気が付かないうちにセキュリティに関する多くのバグが取り除けるようになる
- バリデーション
- サイズチェック : 入力値が期待する範囲内に収まっているか
- 字句 (lexical content) チェック : 入力値に正当な文字や正しいエンコードが使われているか
- 構文チェック : 正しいフォーマットに従っているか
- 多層セキュリティ (security in depth)
感想 / 意見 / その他
- この本で書いてるセキュリティには2つの側面がありそうかなと思った
- 外部からの悪意ある攻撃 (脆弱性)
- ビジネスルール
- 前者はライブラリやフレームワークを適切に選定して使うことで防ぐ → 優れた設計パターンを取り入れることで安全なコードを書けるようになる
- 後者は設計の精度を上げることが自然とセキュリティ対策にもなる → ドメインを意識することで気が付かないうちにセキュリティに関する多くのバグが取り除けるようになる
- セキュリティなどの非機能要件がユーザーストーリーとして表現されることはあまりなさそう
- Java コーディングスタンダード CERT/Oracle 版
第2章
学んだ / 知った
- 浅いモデリング (shallow) / 深いモデリング (deep)
- ビジネスルールにおける完全性 (integrity) → 技術ではなくビジネスにフォーカスする
- 明示的に表現される概念 (explicit) / 暗黙的に表現される概念 (implicit)
- 暗黙的な表現では制約が表現されない
- このドメインをどうすれば理解できるのか
感想 / 意見 / その他
- 問題があるデータを受け取ったときの各システムの振る舞いは異なる
- 全体では辻褄が合ってないけど個々のシステム内では想定内の結果になってることもある
- ドメインの構成要素を分解するとよさそう
- 構成要素が持つ制約を明確にする
- モデリングの際に上限と下限について聞く
- 特にこの制約が重要そうというところを聞く
第3.1章
学んだ / 知った
- ドメイン駆動設計はドメインの理解をコード上に表現することを開発者に要求するものである
- システムが何をすべきかを定義する=システムが何をすべきではないかを把握する必要がある
- ドメインモデルは値オブジェクトとエンティティで構成される
- 集約
- 境界づけられたコンテキスト
- コンテキストマップ
- 解決すべき問題の対象が技術的なものなのかドメインなのか
- モデルは必要な概念を選択して抽象化した理解
- ドメインエキスパートの頭の中には真のモデルはない
- ドメイン言語
- ユビキタス言語
- 言語警察👮♀️
感想 / 意見 / その他
- 用語をちゃんと定義することが大事そう (UI の文言と一致させる必要はあるんだろうか)
- モデルを考える際はドメインの理解とコンテキストや目的を把握してないといらない属性が出てきちゃいそう
第3.2~3.4章
学んだ / 知った
- エンティティ
- 識別性を持ち一意の識別子によって他エンティティと区別できるもの
- エンティティを構成する属性の中身は変わることがある
- エンティティが入れ子になる場合
- 子エンティティは親エンティティ内で一意であればいい (ローカルで一意となる識別子)
- 子エンティティの変更は親エンティティを介する必要がある (モデルの不変条件を担保するため)
- 値オブジェクト
- 不変性を持ち値によって識別できるもの
- 識別性が必要かどうかでエンティティか値オブジェクトかが変わる
- 集約ルート
- 境界の外から参照できる唯一のもの
- 境界づけられたコンテキスト
- 用語やモデルの意味が共有できる範囲
- 異なるコンテキスト間でモデルは共有しない (モデルの不変条件はコンテキストによって違うことがあるため)
- コンテキストにも上流下流がある
感想 / 意見 / その他
- 定義するモデルがエンティティなのか値オブジェクトなのかを意識する (そのモデルを識別するものは何か)
- 識別子を定義する際は一意性の範囲について考えるとよさそう
- エンティティもコードレベルでは不変である方がいいと思う
- 「値オブジェクトはエンティティを持てないが参照はできる」ってどういう意味?(引数には渡せるっていうこと?)
- 集約は同じライフサイクルや不変条件を持つものをまとめた概念という理解でいいのかしら
- 集約が大きいと実行時のオーバーヘッドが大きくなりがちなので適度に分解した方がいいかもしれない (集約は階層にできるんだろうか?)
- コンテキストの境界を見つけるために Conway の法則から考えるって書いてあるけど組織構造が変わることも多いので現実的に難しいんでは🤔 (だからこその逆 Conway の法則?)
第4.1~4.2章
学んだ / 知った
- 不変性 (immutability)
- 不変オブジェクトはスレッドセーフである
- 契約による設計 (design by contract) は 事前条件, 事後条件, 不変条件 によって成り立つ
感想 / 意見 / その他
- 不変性に可用性のメリットがあるとは思ってなかった (テスタビリティとかトレーサビリティくらいかと思ってた)
- 不変オブジェクトはインスタンス生成のオーバーヘッドが大きくなりがち (なので組み込みとかには向いてなさそう)
- fail fast は設計やプログラミングだけでなくシステムそのものの性質としても大事なことだと思う
第4.3章
学んだ / 知った
- 妥当性には様々な視点がある
- 妥当性確認の分類
- オリジン : 正当な送信元から送られたデータか
- サイズ : 適切なサイズか (期待する範囲内に収まっているか)
- 字句 (lexical content) : 正当な文字や正しいエンコードが使われているか
- 構文 (syntax) : 正しいフォーマットに従っているか
- 意味 (semantics) : 意味的に正しいか
- 負荷が低い妥当性確認を先にやることで負荷の高い処理を不必要に実行することが防げる
感想 / 意見 / その他
- ボットネットのレンタル1時間50ドル😳
- 妥当性確認の分類は堅牢なプログラムを書く上での観点としてもよさそう
- ↑以外にも↓も妥当性確認の文脈で押さえておくとよさそう
- 無害化 (sanitization)
- 正規化 (canonicalization)
- 標準化 (normalization)
第5.1~5.2章
学んだ / 知った
- ドメインプリミティブ
- ドメインモデルを構成する最小要素
- 不変であること
- 完結した概念であること (例えば郵便番号の上3桁だけでは不完全みたいなこと)
- 値によって識別する
- オブジェクト生成時に不変条件をチェックすることで想定外の状態にならないようにする
- 状態と振る舞いを近いところに置くのが大事 (凝集度)
- 概念は対象のコンテキストの境界内でのみ有効
- ドメインプリミティブライブラリ
- ドメインオブジェクトはそのまま外部 (コンテキスト外) に渡してはいけない
- Read-Once オブジェクト
- 機密性の高いデータはシリアライズさせない
感想 / 意見 / その他
- 知的負荷 (認知負荷) が下がる効果は大きいと思う (引数に int と String が20-30並んでるメソッドはしんどい)
- 命名の重要さを改めて感じた
- Externalizable 使ってるところ見たことないけどパスワードを扱うクラスはこれを実装するようにしてもいいかもしれない
第5.3~5.4章
学んだ / 知った
- ドメインプリミティブを使うメリット
- 型と不変条件によって安全で堅牢なコードになる
- 汚染解析 (taint analysis)
- contrast security にもそういう類いのものなのかしら
感想 / 意見 / その他
- ドメインプリミティブを採用するとテスタビリティが向上しそう
- 確証バイアスの防止は疑り深いひとにレビューしてもらうのがいいのかしら
第6.1~6.2章
学んだ / 知った
- 状態変化に対して適切なルールを適用する
- 状態変化をエンティティが扱うようにモデリングする (いろんなレイヤで状態に関する責務を持たない)
- すべてのエンティティは生成されたときから正しい状態となるように設計する
- ドメインモデルを永続層モデルから分離する
- fluent interface
- すべての public メソッドの終わりに不変条件を確認するメソッドを呼び出す
- ビルダーパターンはエンティティの生成過程を隠蔽して不完全な状態を公開しないようにする
感想 / 意見 / その他
- テストフレームワークとかテンプレートエンジンによってはデフォルトコンストラクタ必要だったりする (ドメインモデルを UI にマッピングしてたり)
- デフォルトコンストラクタを private で定義したり setter を提供しないアプローチはよさそう
- 引数ありコンストラクタでも null を渡してるやつはモデリングがちょっと怪しい気がする
- Lombok の Builder で不変条件をチェックする処理は挟めたっけ?(ビルダーパターンは使いやすいけど自前で作るのは面倒な印象)
第6.3~7.1章
学んだ / 知った
- エンティティの完全性
- 状態を外部から直接的に操作させないようにする
- 可変なオブジェクトを共有する際はオブジェクトの複製を返す
- とはいえできる限り不変オブジェクトにするのがいい
- コレクションに対する操作はエンティティ内に閉じて外部からは操作できないようにする
- エンティティの中で可変にする必要性を考える
感想 / 意見 / その他
- コレクションオブジェクトをそのまま返すコード書きがち (unmodifiedList でラップして返す)
- 複雑なものはどうしたって単純にすることはできないので複雑なものをいかにコントロールするかが大事
第7.2~7.3章
学んだ / 知った
- エンティティは状態によって実行可能な振る舞いが変わる (状態ごとに不変条件が違う)
- 状態オブジェクトはエンティティの状態をクラスとして表現する
- エンティティスナップショットはある時点の状態を表す不変なオブジェクト
- エンティティを読み書きで分離することによってエンティティの完全性を高めることができる
感想 / 意見 / その他
- 状態オブジェクトのサンプルコードは普通のクラスだったけど enum で表現することもありそう
- CQRS だと読み込み専用のエンティティを定義することはあまりなさそう (読み書きではなく登録更新でエンティティ分離したり)
第7.4章
学んだ / 知った
- エンティティリレー
- エンティティの生存期間をいくつのフェーズに分割し、分割したフェーズをエンティティとして表現する
- フェーズの遷移には不変条件がある
感想 / 意見 / その他
- フェーズのモデリングがキモ
- エンティティリレーでは前のフェーズに戻ることを推奨してないけどそのデメリットがイマイチ分からなかった
第8.1~8.2章
学んだ / 知った
- 設計をより安全なものにする単体テスト
- 何をすべきか/何をすべきでないかを確認する
- 正常値, 境界値, 異常値, 極端値
感想 / 意見 / その他
- 4章あたりのバリデーションの話に通じるものがある
- 極端値の観点でテストしたことはあまりないかもしれない
- 極端値の話と直接関係ないけど、将来的に扱うデータ量が N 倍になった場合に問題がないかという観点は設計において大事
第8.3~8.6章
学んだ / 知った
- 機能トグルが意図した通りに振る舞うことを検証するテストを作成する
- 性能限界を知る, 限界を迎える前にどのような振る舞いになるか, ボトルネックはどこかを把握する
- ドメイン DoS 攻撃
- デフォルトの振る舞いを知る
- デフォルトの振る舞いをテストする
感想 / 意見 / その他
- うちだと機能トグルの設定は環境変数に出して起動時に読み込むことが多いはず (ただその場合にどうやって検証するのがいいだろう)
- セキュリティテストの重要性は理解したけど自前で用意するのは大変そう (contrast security みたいなツールを導入する方がよさそう)
- インフラのセキュリティテストに関して、terraform だと terraform test とかがあるけど宣言的に定義できるコードにテストを書くメリットがあまり理解できてない
- ドメイン DoS 攻撃を検知するの難しそう
- 設定のホットスポットって結局なんなの
第9.1~9.2章
学んだ / 知った
- ビジネス例外/技術的例外
- 例外処理によってアプリケーション内部の情報や機密性の高い情報が漏洩しないように設計する
- あらゆるビジネスデータはコンテキストを越えると機密性が変わる可能性があることを考慮する
- read-once オブジェクト
- 例外を使わずに処理の失敗を表現する設計
- いろいろな角度から見てみるとよさそう
感想 / 意見 / その他
- ビジネス例外/技術的例外っていう表現はあまり聞いたことなかった (チェック例外/非チェック例外の方が馴染みがある)
- コラムにあった reduce で例外スローするやつおもしろい
第9.3~9.4章
学んだ / 知った
- 可用性の設計
- 回復性 (resilience)
- 応答性 (responsiveness)
- なんらかの応答があることはまったく応答がないことよりもマシ
- フォールバックの処理はドメインロジックに影響を与える
- バルクヘッドはアーキテクチャの様々なレイヤーに適用できる
- サービスやサーバーを分割してもその中に隠れた依存関係が存在しているかもしれない (例えば SPOF になっている箇所はよく注意した方がよさそう)
感想 / 意見 / その他
- 回復性に関して自分のシステムで縮退運転するとしたらどうなるか
- 購入はできないけど着券はできる
- ポイントやクーポンは使えないけど購入はできる (ただし公平性が失われそう)
- ある商品は買えるけど別の商品は買えない
- ...などなど
- いろいろな判断軸がありそうだしそれ次第で設計も変わりそう
- サーキットブレーカー導入してるけど、フォールバックメソッドではエラーを返すだけのところが多い気がする (回復性の観点ではエラー以外を返す方が適切なところもありそう)
第10.1~10.3章
学んだ / 知った
- クラウドアプリケーション特有の性質を備える
- 状態を持たない (stateless)
- 環境に依存しない (environment-agnostic)
- 構成情報はアプリケーションコードと分離する (環境変数 / パラメータストア)
- ステートレスにすることでアプリケーションのスケーラビリティが向上する
- 最小権限の原則 (AWS の Well-Architected にもあったような気がする)
- プロセスはいかなるときでも中断させられたり破棄させられたりする可能性がある (
@Async
が想定通りに動かないとか)
感想 / 意見 / その他
- Twelve-Factor App よく聞くけどちゃんと読んだことない
- 開発環境/本番環境の一致
- 同じ docker image をデプロイする事例をどこかで読んだことあるけど、同一のコードベースからは (振る舞い上は) 同一の docker image が生成されて欲しい気はする (生成されないとしたらそれは stateless と言えないのでは)
- 以前はアプリケーションの設定ファイルに user/password が書いてあったけどあれはアンチパターンど真ん中だった (今は削除されてるはず)
第10.4~10.6章
学んだ / 知った
- ログのサービス化 (logging as a service)
- 安全性とアクセスのしやすさは両立する
- エンタープライズセキュリティ
- Rotate (定期的な変更)
- Repave (作り直し)
- Repair (修復)
感想 / 意見 / その他
- 監査用とそれ以外の用途のログが混ざってるけど分離した方がいいのかしら (どうやって分離するんだろう)
- datadog にも監査証跡は残ってるんだろうか (cloudwatch は残ってそう)(cloudtrail だっけ?)
- ログの書き込み失敗でロールバックするという話で、そこまでする必要あるのかと思ったけど、監査証跡の文脈だとログを残すまでが1ユースケースであると言えるのかもしれない
- 保守運用として手動運用しているものをシステム管理機能として機能化するかどうかの判断がなかなか難しい (投資対効果とか)
- システムはデータの整合性を担保するための仕組みとも言えるので、データパッチのような仕組み外からのデータ変更はリスクが大きい (データパッチを安い早い旨いみたいに捉えられると危険)
第11章
学んだ / 知った
- ドメインについてしっかりと理解したあとに抽象化を行う
- 詳細を理解する前に抽象化を行うと未熟なモデルになりがち
感想 / 意見 / その他
- 内容は違うけどこういう事例と同じようなことが実際に起きてるので他人事ではない
- ユースケースや状態遷移を明確にするのが大事
- 決済取引が完了してから発券するべきなのかもしれない
第12.1~12.4章
学んだ / 知った
- 境界付けられたコンテキスト (bounded context) と意味的境界 (semantic boundary) を識別する
- 受け取った値を検査せずにログ出力することで間接的インジェクションのリスクになる
- 契約とドメインプリミティブを利用することで妥当性をチェックするロジックをまとめる
感想 / 意見 / その他
- 新しい API を追加して段階的に移行するのはよくやるパターン (規模が大きい段階的移行は途中で頓挫するとつらい)
- ドメインモデルを導入したことで厳格なルールが適用されて振る舞いが変わってしまう可能性があるのは留意しておきたい
- というか振る舞いが変わっちゃうくらいだともはやリファクタリングではないのでは🤔
第12.5~12.8章
学んだ / 知った
- DRY原則とはすべての知識はシステム内において単一かつ明確な信頼できる表現になっていること
- 概念や知識の重複がないかどうかの観点で適用する
- ドメイン固有の型で十分な妥当性確認ができているか
- 正常値テストや境界値テストだけてなく異常値テストや極端値テストによって安全性を高める
- 依存関係にあるもの (外部システムやDB) がうまく機能しない場合のテストをする
- ドメインプリミティブは完結した概念であること
感想 / 意見 / その他
- 十分な妥当性確認ができていない型わりとありそう (ドメインモデル貧血症)
- IF の金額を double で定義するのはよかったんだろうか (いわるゆ Money 型を定義した方がよかったかもしれない)
第13.1~13.2章
学んだ / 知った
- マイクロサービス
- 各サービスは独立した実行環境で稼働する
- 各サービスは個別に更新できる (変更の影響を受けるサービスは少なくする)
- 他のサービスが停止しても自身のサービスは (縮退して) 稼働できるようにする
- サービスや API を技術的な観点から考えるのではなく境界付けられたコンテキストとして考える
- 公開 API をドメインへの操作のみにすることでサービスの不変条件を守れる
感想 / 意見 / その他
- 依存するサービスが停止した際の振る舞いはなかなか考えられてない
- イベント駆動のアーキテクチャはトランザクション管理が難しくなりそう
- API 設計の例がいまちい分かりづらかったけど、Web API の設計の本は他にもいいのがたくさんあるはずなので読んでみよう
- 用語集
第13.3~13.4章
学んだ / 知った
- データの機密性はコンテキストによって変わる (完全性や可用性も同様)
- ログの機密性/完全性/追跡可能性/監査可能性
感想 / 意見 / その他
- datadog でいろいろ追跡できるようになってるけど、いろいろチューニングしてきて今の仕組みがあるのでありがたい (当たり前にある環境ではない)
- ログデータにもデジタル署名を使うことあるのか (完全性の観点で)
- ログデータの分類 (監査/システム/エラー) の考え方はよさそうだけど、ドメインに基づくログ API はやりすぎ感ある
第14.1~14.5章
学んだ / 知った
- セキュリティを意識したコードレビューを開発プロセスの中に自然に組み込む (自然にがポイント)
- 脆弱性を自動的に見つけて集約して一括で見えるツールまさに contrast security
- ビジネスルールは善意のもとで行われる行為をもとに定義されがち
- コンテキスト駆動テスト (CDT)
- テストに関するプラクティスはアプリケーション開発の状況やコンテキストによって変わる
感想 / 意見 / その他
- セキュリティを意識したコードレビューは生成 AI に任せられそう
- 脆弱性対応は SLO と同じように攻めと守りのバランスの指針を決めて優先度を決められるとよさそう
- ドッグフーディングとかでもよさそうかと思ったけど、侵入テストはより専門的な視点が必要そう
- 最近、セキュリティや脆弱性の情報収集をする重要さを感じている
- インシデントハンドリングと問題解決は、ようするに暫定対応と恒久対応のことだと思うけどインシデント対応ではこの視点は重要
- 問題解決に関するタスクの優先度の話、攻めと守りのバランスを取るのは意外と難しい (どうしても攻めのタスクに偏りがち) ので守りに回るタイミングを逃さないような働きかけが必要かもしれない