無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

Building Secure and Reliable Systems 読書メモ - Chapter 6

f:id:muziyoshiz:20200430014145p:plain:w320

月一連載になっている SRS 本の読書メモです。

Chapter 6 の "Design for Understandability" は、最初にタイトルを見たときから気になっている章でした。実際読んでみて、勉強になる考え方が多かったです。

じっくり英訳しようとすると全然先に進めなくなってしまうので、今回から若干翻訳の手を抜いて、ほとんど意訳で書いてるのと、一部は英語のまま引用してます。また、細部はかなり省略してしまってます。興味が湧いた部分については原文をあたってみてください。

本章で頻出する "Understandability" という単語は、以下のメモでは「わかりやすさ」と訳しました。「理解可能性」のように訳したほうがいいかとも思ったのですが、文章が固くなりすぎるように感じたので、このメモでは「わかりやすさ」にしました。

また、本章には、"reason about" という表現も頻出します。これは「すべてのコードを深く読んで理解しなくても、システムの不変条件やメンタルモデルからそのことを説明できる」というような意味で使われています。適切な訳という自信はありませんが、以下では「論証する」と訳しました。

SRS 本について

SRS 本はこちらから無償でダウンロードできます。

前回までの読書メモは SRS Book タグ を参照のこと。

Chapter 6. Design for Understandability

本章では、システムのわかりやすさ(understandability)について議論する。

最初に、不変条件(invariants)とメンタルモデル(mental models)に従って、あなたのシステムを分析し、理解する方法についての議論から始める。そして、アイデンティティ、認可、アクセス制御のための標準フレームワークに基づく階層化されたシステムアーキテクチャが、わかりやすいシステムの設計を助けることを示す。

そして、セキュリティ境界(security boundaries)に関する話題を題材として、どのようにソフトウェア設計(特に、アプリケーションフレームワークとAPIの利用)が、セキュリティおよび信頼性に関する特性(security and reliability properties)に対する論証(reason about)を助けるかを示す。

本書では、システムのわかりやすさ(understandability)とは、関連分野の専門知識を持つ人が、以下の両方を、正確かつ自信を持って論証できる度合いと定義する。

  • The operational behavior of the system(システムの運用上の振る舞い)
  • The system’s invariants, including security and availability(セキュリティや可用性を含む、システムの不変条件)

Why Is Understandability Important?

わかりやすいシステム(understandable system)を設計し、そのわかりやすさを維持するには努力を要する。わかりやすいシステムは、プロジェクトのベロシティを維持するという(4章で示した)利点もあるが、それ以上に、以下のような明確な利点がある。

  • Decreases the likelihood of security vulnerabilities or resilience failures
    • わかりにくいシステムでは、修正時にバグや脆弱性を生みやすい。またそれらに気づきにくい
  • Facilitates effective incident response
    • わかりにくいシステムは、インシデント発生時の対応〜根本原因の解決を妨げる
  • Increases confidence in assertions about a system’s security posture
    • システムのセキュリティに対するアサーション(表明)は、一般に、不変条件(invariants)と呼ばれる。不変条件とは、そのシステムの、すべてのありうる振る舞いに対して成立しなければならない特性(property)のことである
    • 例えば、外部からの入力(悪意あるものを含む)に対して必ず特定の検査が行われる、というのは不変条件である。そのような抽象化がなければ、システムが外部からの入力に対して安全かどうかの論証が難しい

System Invariants

システムの不変条件(system invariant)とは、そのシステムを取り巻く環境が正しく振る舞うか間違って振る舞うかに関係なく、常に真である特性のこと。ここで言う環境は、あなたが直接コントロールできないものもすべて含む。

以下は、セキュリティと信頼性に関する望ましい特性の例:

  • Only authenticated and properly authorized users can access a system’s persistent data store.
  • All operations on sensitive data in a system’s persistent data store are recorded in an audit log in accordance with the system’s auditing policy.
  • All values received from outside a system’s trust boundary are appropriately validated or encoded before being passed to APIs that are prone to injection vulnerabilities (e.g., SQL query APIs or APIs for constructing HTML markup).
  • The number of queries received by a system’s backend scales relative to the number of queries received by the system’s frontend.
  • If a system’s backend fails to respond to a query after a predetermined amount of time, the system’s frontend gracefully degrades—for example, by responding with an approximate answer.
  • When the load on any component is greater than that component can handle, in order to reduce the risk of cascading failure, that component will serve overload errors rather than crashing.
  • A system can only receive RPCs from a set of designated systems and can only send RPCs to a set of designated systems.

あなたのシステムがこれらの望ましいセキュリティ特性に反している、つまり不変条件が実際には不変条件ではない、という場合には、そのシステムはセキュリティ上の弱点や脆弱性を持つということになる。

上記リストの4番目にあるような、信頼性に関する特性についても同様である。フロントエンドが外部から受け取った以上のリクエストを、バックエンドに対して生成するようなシステムは、可用性に関する潜在的な弱点を持つといえる。

Analyzing Invariants

意図した特性が実際に不変条件として成立しているかは、解析しないとわからない。不変条件が成立していないことによって起こりうる悪影響と、不変条件が成立するかを検証するために割く労力は、トレードオフの関係にある。

両極端な例を上げると、例えば少しのテストとバグチェックのためのソースコードレビューをするくらいでは、不変条件の違反を見逃す恐れがある。一方で、マイクロカーネルの実装で行われているように、セキュリティ特性を形式論理で証明できれば完璧だが、そのようなことを大規模なアプリケーション開発に適用するのは難しい。

本章では、その両極端な例の間にある、現実的な妥協案を示す。わかりやすさについての明確な目標を持ってシステムを設計することで、システムが特定の不変条件を満たすことを(形式的にとまではいかなくても)原理的に示すことができ、それらのアサーションをかなり高い度合いで信頼できる。Google では、このアプローチが、大規模ソフトウェア開発に対して実用的で、かつよくある種類の脆弱性の発生を効果的に減らすことができることを発見した。テストと検証についての詳細は Chapter 13 で示す。

Mental Models

複雑なシステムをそのまま全体的に理解することは難しいため、エンジニアや特定分野のエキスパートは、不必要な詳細を省いたメンタルモデルを構築する。複雑なシステムでは、メンタルモデルが複数になることもある。

メンタルモデルは有用だが、限界もある。もし、典型的な運用シナリオに基づいてメンタルモデルを作ったら、それは通常でない場合のシナリオでのシステムの振る舞いの予測には使えない。

例えば、通常はリクエスト数の増加に応じて段階的に負荷が増えるシステムが、ある閾値を超えると、全く違う振る舞いを見せることがある。そのようなシステムでは、単純すぎるメンタルモデルは、トラブルシューティングを阻害する。

極端な状況、または通常でない状況でも、システムが予測可能な振る舞いをして、メンタルモデルが保たれるようにシステムを設計すべきである。そのようなシステムであれば、障害発生時にも、そのメンタルモデルは有用なままになる。

※本文ではメモリのスラッシングを例に挙げて、システムの振る舞いを予測可能にする方法を例示している。

Designing Understandable Systems

この章の残りでは、システムをよりわかりやすくし、そのシステムが日々進化するなかでもわかりやすさを維持し続けるために使える、いくつかの具体的な指標について議論する。

Complexity Versus Understandability

わかりやすさに対する最初の敵は、管理されていない複雑さ(unmanaged complexity)である

いくつかの複雑さは、現在のソフトウェアシステム(特に分散システム)の規模と、それらのシステムが解決する問題による生来のもので、回避できない。Google では数万のエンジニアが、10億行を超えるコードを含むソースコードリポジトリを編集しているが、そこまでの規模でない組織でも、1つのプロダクトで数百を超える機能を提供することはざらにあるだろう。

例えば、Gmail は多くの機能を提供している(p.94 のリスト)。このように多数の機能を提供するシステムは、機能がより少ないシステムよりも本質的に複雑である。しかしこれらの機能は価値を生み出しているので、複雑さを下げるためにそれらの機能を削除しろとは言えない。私達がそれらの複雑さを管理できれば、システムを十分にセキュアかつ高信頼にすることができる。

私達の目標は、この本質的な複雑さをより小さい部品・コンパートメントに分割して含むように、システムの設計を構成することである。そして、それは具体的かつ重要なシステム特性や振る舞い(specific, relevant system properties and behaviors)を、高い確度で人間が論証できるような方法で行わなければならない。言い換えると、私達はわかりやすさの観点に立って、複雑さという特性を管理しなければならない。(※なんとなく言いたいことはわかるがうまく訳せない)

本書ではセキュリティと信頼性の観点で、わかりやすさについて議論している。しかし、私達が議論するパターンの話は、この2つの観点に限ったものではなく、一般的なソフトウェア設計テクニック全般に渡る。システム及びソフトウェア設計に関する全般的な読み物としては、John Ousterhout の A Philosophy of Software Design を参照のこと。

Breaking Down Complexity

複雑なシステムの振る舞いのすべての側面を理解するためには、巨大な1個のメンタルモデルを内面化して維持する必要がある。しかし、そのようなことは人間には難しい。

システムを複数の小さなコンポーネントにわけて、それを組み合わせることで、システムをわかりやすくすることができる。そして、個々のコンポーネントの特性を論証可能にして、そこからシステム全体の特性を引き出せるようにする。

このアプローチは、現実には単純明快ではない。そのようなことが可能かどうかは、そのシステム全体がどのように複数のコンポーネントへと分解されているか、そしてそれらのコンポーネント間のインターフェイスと信頼関係の性質に依存する。この点については p.97 からの "System Architecture" にて見ていく。

Centralized Responsibility for Security and Reliability Requirements

セキュリティおよび信頼性に関する要求(例えば何らかのチェック処理)を個々のコンポーネントで実装していたら、そのシステム全体がそれらの要求を満たしているか判断するのは難しい。

Chapter 4 で議論したように、そのような共通タスクを実装する責任は、その組織共通のコンポーネント(ライブラリやフレームワーク)に集約させることができる。

例えば、サービスで共通して必要なセキュリティ機能(認証、認可、ロギング)は RPC サービスフレームワークが実装する。また、信頼性に関する機能(リクエストのタイムアウトなど)も RPC サービスフレームワークに任せる。

セキュリティと信頼性に関する要求に関する責任を1箇所に集中させることで、以下の利点が得られる。

  • Improved understandability of the system
    • レビュアーは、システムの1箇所を見るだけで、セキュリティおよび信頼性に関する要求が満たされているかどうかを判断できる
  • Increased likelihood that the resulting system is actually correct
    • アプリケーション上での要求のアドホックな実装が間違っていたり、実装し忘れている可能性について考えずに済むようになる

System Architecture

システムを複数のレイヤーとコンポーネントに分割することは、複雑さを管理するためのキーツールである。

しかし、この分割をどのように行うかは、慎重に考える必要がある。もしコンポーネント同士が密結合していたら、モノリシックシステムと同様に、理解が難しいシステムになってしまう。システムをわかりやすくするためには、境界とインターフェイスに注意を払う必要がある。

経験豊富なソフトウェアエンジニアは、外部の環境から来た入力は信頼すべきでなく、それらの入力についてシステムはなんの仮定も置いてはいけない、ということを知っている。その一方で、システム内部からの呼び出しについては信頼し、想定通りの呼び出し方をされると期待しがちである。

しかし、システムのセキュリティ特性が実現されるかどうかは、実際にはシステム内部からの入力が正しいかどうかに依存する。そのため、セキュリティ特性を論証可能にするためには、システム内部からの呼び出しについてもなんの仮定も置けない。

コンポーネントが呼び出し元に対して置く仮定を減らせば減らすほど、そのコンポーネントの特性の論証が容易になる。理想的には、そのような仮定は全く無くしたい。

もし、呼び出し元について仮定を置くことを強制される場合は、その仮定を、インターフェイスの設計に明示的に加える事が重要である。例えば、呼び出し元のプリンシパルを制限する。

Understandable Interface Specifications

構造化されたインターフェイス(structured interfaces)、一貫したオブジェクトモデル(consistent object models)、そして冪等なオペレーション(idempotent operations)は、システムのわかりやすさに貢献する。

  • Prefer narrow interfaces that offer less room for interpretation
    • フレームワークは、RPC メソッドの入出力に対する型定義の機能を(gRPC, Thrift, OpenAPI のように)持っていたほうがいい
    • OpenAPI や Protocol buffer が持つようなバージョニング機能の有無は、将来のアップグレードのしやすさに影響する(アップデートに関する Protocol buffer のガイドライン
    • 任意の文字列(JSON の string 型)を受け付けるような API は、入出力が制約されないため、わかりやすさを損ないやすい。また、例えばクライアントとサーバが別のタイミングでアップデートされるときに、片方の処理を壊すことがありうる
    • 明確な API 仕様がないと、例えば Istio Authorization Policy のような認可フレームワーク上のポリシーと、サービスが公開する実際の境界面(actual surface area)を関連付けるための、自動的なセキュリティ監査システムの構築は難しくなる
  • Prefer interfaces that enforce a common object model
    • 複数のタイプのリソースを管理するシステムは、Kubernetes Objects のような共通オブジェクトモデル(common object model)から恩恵を得られる
    • そのオブジェクトで各リソースを取り扱えるようになるだけでなく、エンジニアが巨大なシステムを理解するのに役立つ単一のメンタルモデルを提供する(p.99 にその利点についての箇条書きあり)
    • Google は resource-oriented APIs を設計するための一般的なガイドライン を公開している
  • Pay attention to idempotent operations
    • API 呼び出しは失敗したり、成功したあとでその応答が届かないことがある。API が冪等(idempotent)であれば、呼び出し元は再実行すればいい。もし、冪等でなければ、呼び出し結果を確認するためのポーリングが必要になる
    • 冪等性は、エンジニアのメンタルモデルにも影響する。例えば、エンジニアがなにかデータを作成する API を冪等だと思って呼び出しているのに、実際にはそうでなかった場合、そのデータが意図せず重複して作られる、ということが起こりうる
    • 本質的に冪等でない処理であっても、サーバ側で冪等にすることができる。上記の例では、リクエストに一意な識別子(例:UUID)を含めることで、2回目の API 呼び出しをサーバ側で検出して、重複したデータの作成を防げる

Understandable Identities, Authentication, and Access Control

どんなシステムでも、機密性の高い(highly sensitive)リソースに、誰がアクセスしたかは特定できるようにすべきである。例えば課金システムでは、PII(personally identifiable information)データに、社内の誰がアクセスしたかを、監査できる必要がある。監査フレームワークは、アクセスのログを取り、そのログを自動的に解析し、定期的なチェックや、インシデント調査に活用できるべきである。

Identities

アイデンティティ(identity)とは、属性の集合、またはあるエンティティに関連する識別子のことである。クレデンシャル(credentials)は、特定のエンティティのアイデンティティを主張する。クレデンシャルは、認証プロトコル(authentication protocol)を使って送信される。

システムがどのように人間のエンティティ(顧客および管理者)識別するかを論証することは相対的に簡単な一方で、巨大なシステムは人間以外のエンティティも識別できる必要がある。マイクロサービスの集合から構成された巨大システムは、人が介在する場合もしない場合も含めて、相互に呼び出し合う。エンティティには、人、ソフトウェアコンポーネント、ハードウェアコンポーネントが含まれる。

一般に、アイデンティティと認証のサブシステム(identity and authentication subsystems)は、アイデンティティのモデル化と、それらのアイデンティティを表すためのクレデンシャルの提供に責任を持つ。アイデンティティが意味があるもの("meaningful")であるためには、以下の条件を満たす必要がある。

  • Have understandable identifiers
    • 人間にとってわかりやすい文字列であること(例:widget-store-frontend-prod のような文字列)
    • わかりやすい文字列には、設定ミスを防げたり、異常なアクセスにすぐ気づけたりするメリットがある
    • これが 24245223 のような無意味な数字だと、間違った設定をしていても気づけない
  • Be robust against spoofing
    • ID/password を clear-text channel でやり取りするようなものは簡単に spoofing できてしまう
  • Have nonreusable identifiers
    • 例えば、社員のメールアドレスを管理ツールのアイデンティティに使っていて、その管理者が退職したあとで同じメールアドレスを新入社員に割り当てたら、いろいろな権限が渡ってしまう可能性がある

意味のあるアイデンティティを、システム上のすべてのアクティブなエンティティに割り当てることが、わかりやすさへの基本的な第一歩である。組織内で統一されたアイデンティティシステム(organization-wide identity system)は、エンティティを参照するための共通の方法を提供し、全従業員が共通のメンタルモデルを持つことを助ける。

Example: Identity model for the Google production system.

例として、Google のシステムでは、異なる種類のアクティブなエンティティを以下のように分類している。

  • Administrators
    • システムの状態を変更する(例えば新しいリリースを行ったり、設定を変更する)権限を持つエンジニア
    • Administrator は、システムの状態を直接変更せず、そのような一連のワークロードを起動する
    • そのアイデンティティは global directory service で管理されて、シングルサインオンに使われる
  • Machines
    • Google データセンタ内の物理マシン
    • global inventory service/machine database で管理されている
    • Google プロダクション環境では、マシンは DNS 名で解決可能
    • そのマシン上で動作するソフトウェアの修正権限を表すために、administrators と machines の間の関連を管理する
  • Workloads
    • Borg オーケストレーションによってスケジューリングされたワークロード
    • ワークロードと、それが動作するマシンのアイデンティティは、大抵の場合異なる
    • オーケストレーションシステムは、ワークロードと、それが動作可能なマシンの関係(制約)を強制する
  • Customers
    • Google が提供するサービスにアクセスする顧客
    • Customers のアイデンティティは、専用のアイデンティティサブシステムで管理される
    • Google は OpenID Connect workflows を提供しており、顧客は Google identity を、Google 外のエンドポイントで使えるようにしている
Authentication and transport security

認証およびトランスポートセキュリティの分野は難しいので、すべてのエンジニアが完全に理解することは期待できない。その代わりに、それらの抽象化とAPIを理解すべき。

Google ではエンジニアに Application Layer Transport Security (ALTS) を提供している。ALTS は、以下のようなシンプルなメンタルモデルを提供する。

  • An application is run as a meaningful identity:
    • A tool on an administrator’s workstation to access production typically runs as that administrator’s identity.
    • A privileged process on a machine typically runs as that machine’s identity.
    • An application deployed as a workload using an orchestration framework typ‐ ically runs as a workload identity specific to the environment and service pro‐ vided (such as myservice-frontend-prod).
  • ALTS provides zero-config transport security on the wire.
  • An API for common access control frameworks retrieves authenticated peer information.

ALTS に類似のシステムとして、Istio のセキュリティモデル がある。

このようなシステムがない場合、セキュリティ対策が正しく行われるか確認するためには、すべてのアプリケーションコードに目を通す必要が出てきてしまう。

Access control

アクセス制御の部分をフレームワークで提供することは、システム全体のわかりやすさを大きく向上させる。フレームワークはアクセス制御に関する共通の知識、共通の方法をエンジニアに提供する。

フレームワークは、複数のコンポーネント(Ingress → Frontend → Backend)を渡るワークロードに対するアクセス権限、といった難しい問題の扱いを助けることができる。このようなワークロードチェインでは、リクエストを送った Customer の権限と、ワークロードを実行する Machine の権限があり、両方を考慮する必要がある。

Security Boundaries

あるシステムの信頼されたコンピューティングベース(trusted computing base, TCB)とは、「セキュリティポリシーが適用されることを確実にするのに十分な正確な機能を持つ(ハードウェア、ソフトウェア、人間、...)の集合であり、より端的に言えば、その失敗がセキュリティポリシーの違反を引き起こす可能性があるもの」である(Anderson, Ross J. 2008. Security Engineering: A Guide to Building Dependable Distributed Systems. Hoboken, NJ: Wiley. からの引用)。

TCB と「それ以外のすべて」の間のインターフェイスを、セキュリティ境界(security boundary)と呼ぶ。TCB は、そのセキュリティ境界を超えてくるすべての入力を、信頼できないものとして扱わなければならない。

Chapter 4 の web application(ウィジェット販売サイト)を例に挙げる。このサイトは発送先住所を扱う。1個のサーバがすべての機能を提供するモノリシックシステムでは、発送先住所の TCB(TCB_AddressData)はそのシステム全体になる。例えば、発送先住所とは関係ない機能(例えばカタログ検索機能)に SQL インジェクション脆弱性があると、それは即座に発送先住所の漏洩に繋がってしまう。

※ 以下の TCB の具体例については、図を見たほうがわかりやすいと思う。p.107 以降を参照のこと。

アプリケーションをマイクロサービスに分割することで、TCB を小さくし、セキュリティを改善することができる。上記の例では、カタログ検索機能のバックエンドおよびデータベースを、(発送先住所を含む)販売機能のバックエンドおよびデータベースと分けることで、発送先住所の TCB を小さくできる。

ただ、このようにマイクロサービスに分割しても、フロントエンドに全ユーザーの発送先住所を表示できる機能の実装を許すと、TCB の範囲は web frontend まで広がる。Web frontend の脆弱性が、全ユーザーの発送先住所の漏洩に繋がるためである。

フロントエンドに強すぎる権限を与える代わりに、フロントエンドとバックエンドの間で、OAuth2 のトークンのような エンドユーザーコンテキストチケット(EUC) をやり取りするという方法がある。この方法では、フロントエンドは任意のユーザーの EUC を取得することができず、フロントエンドの脆弱性があっても、攻撃者がアクセスできるデータは限定される。

また、カタログ検索機能と販売機能のフロントエンドが同一のドメインで動いていたら、片方の XSS 脆弱性が、もう一方にも影響してしまう。つまり TCB は再びシステム全体にまで広がってしまう。両者のフロントエンドが別ドメインで動いていれば、TCB は狭まる。

TCB にはセキュリティ上の利点だけでなく、システムをわかりやすくするというメリットもある。TCB として成り立つことを示すためには、コンポーネントはそれ以外の部分から独立していなければならない。そのためには、コンポーネントは明確に定義された、きれいなインターフェイスを持たなければならない。もし、コンポーネントの正しさが、そのコンポーネント外の環境に対する仮定に依存するなら、それは定義上 TCB とは言えない。

TCB はしばしばそれ自身の failure domain を持ち、バグの発生や DoS 攻撃、またはその他の運用上の影響に対して、どのように振る舞えばいいかについての理解を与える。Chapter 8 で、システムを区分することによる利点について、さらに詳しく議論する。

Software Design

巨大なシステムを一度、セキュリティ境界によって区分された複数のコンポーネントによって構造化したあとも、あなたはそのすべてのコードとサブコンポーネントを引き続き論証し続ける必要がある。そして大抵の場合、分割後のコードも十分に巨大かつ複雑なソフトウェアになる。

この節では、不変条件を、より小さいソフトウェアコンポーネント(モジュールやライブラリ、API)のレベルでさらに論証できるように、ソフトウェアを構成するための技術について議論する。

Using Application Frameworks for Service-Wide Requirements

これまでに議論したように、フレームワークは再利用可能な機能の部品を提供する。あるシステムは、認証フレームワーク、認可フレームワーク、RPC フレームワーク、オーケストレーションフレームワーク、監視フレームワーク、ソフトウェアリリースフレームワーク、などなど……から成るということがありうる。これらのフレームワークは柔軟性を持つが、柔軟性を持ちすぎる。あり得るフレームワークの組み合わせや、その設定方法は無数にあり、エンジニアを圧倒するほどである。

Googleでは、この複雑さを管理するために、より高レベルのフレームワークを作ることが有用だと気づいた。これをGoogleでは "application frameworks" と呼ぶ。あるいは、"full-stack" または "batteries-included frameworks" とも呼ばれる。

Application frameworks は、サブフレームワークの標準的なセットを、妥当なデフォルト値と、それらのサブフレームワークが相互動作することの保証とともに提供する。

例えば、開発者が自分たちのお気に入りのフレームワークを使った開発したとする。そして、認証のための設定はしたが、認可やアクセス制御のための設定をし忘れるということがありうる。Application framework は、すべてのアプリケーションが有効な(valid)認可ポリシーを持つことを保証し、明示的な許可を持たないすべてのアクセスを拒否するというデフォルト設定を持つ。したがって、設定し忘れのような問題を回避できる。

全般的に、application frameworks は、アプリケーション開発者やサービスオーナーが必要とするすべての機能(下記)を有効化および設定するための、こうあるべきという方法(an opinionated way)を提供できなければならない。

  • Request dispatching, request forwarding, and deadline propagation
  • User input sanitization and locale detection
  • Authentication, authorization, and data access auditing
  • Logging and error reporting
  • Health management, monitoring, and diagnostics
  • Quota enforcement
  • Load balancing and traffic management
  • Binary and configuration deployments
  • Integration, prerelease, and load testing
  • Dashboards and alerting
  • Capacity planning and provisioning
  • Handling of planned infrastructure outages

※ "deadline" は SRE 本では「タイムアウト」と訳されている。deadline propagationは「タイムアウトの伝播」。例えば、全体のタイムアウトが30秒で、あるリクエストで10秒かかったら、後続処理のタイムアウト時間は20秒にする、というように伝播させる。

Understanding Complex Data Flows

多くのセキュリティ特性は、システムの中を流れる値(values)についてのアサーションに依存している。

例えば、多くの Web サービスは様々な目的に URL を使っている。URL パラメータは string 型。string 型の値は、その URL が well-formed かどうかに関わらず、その値に関する明示的なアサーションを渡すことができない。したがって、セキュリティ特性を論証するためには、システムのなかで、その文字列が正しく渡されていくかを、上流のコード(upstream code)すべてを読んで理解する必要がある。

型を用いれば、検証すべきコードを大幅に減らすことができて、システムをわかりやすくする助けになる。URL を型として表現し、例えば Url.parse() のようなコンストラクタにデータ検証処理を実装すれば、あなたはその Url.parse() のコードを理解するだけでセキュリティ特性を論証できるようになる。

このような型の実装は、TCB のように振る舞う(すべての URL が well-formed である、という特性に責任を持つ)。しかし、これはその型(のモジュール)を呼び出す側のコードに悪意がなく、周りの環境に欠陥がある(compromised)状態ではない、という仮定があったうえでの話である。そのような仮定が置けない場合は、セキュリティチームは結果として発生する最悪のシナリオに対処する必要がある(Part IV を参照)。

より複雑なセキュリティ特性に対して、型を用いることもできる。例えば、インジェクション脆弱性(XSS または SQL インジェクション)は、外部から渡された信用できない入力に対する検証やエンコーディングに依存する。このような脆弱性を防ぐための効果的な方法の一つが、SQL クエリや HTML マークアップのような injection sink context(※なんて訳せばいい??)で安全に使えるとすでにわかっている値とそうでない値を区別するために、型を使うという方法である。

  • 内部的に、SafeSql または SafeHtml のような型を用意する。安全だと検証される前の String と区別する
  • SQL や HTML を受け取る側が、String ではなく、SafeSql または SafeHtml 型を受け取るようにする
  • 引き続き String 型を受け取るかもしれないが、その値の安全性に対してなんのアサーションも持ってはならない

このようにすることで、アプリケーション全体が XSS 脆弱性に対して安全かどうかを調べるためには、type と、type-safe sink API の実装のみを理解すればよくなる。Chapter 12 では、呼び出し元についての仮定をなにも持たずに、型の contract を保証するためのアプローチについて議論する。

Considering API Usability

API の適用および利用が、あなたの組織の開発者とその生産性に与える影響について考えるのは良いアイディアだ。もし API が使いづらければ、開発者はその採用に気乗りせず、動きが遅くなってしまう。

Secure-by-construction API(※なんて訳せばいい?? 構造による保証?)は、コードをわかりやすくし、エンジニアがそのアプリケーションのロジックに集中できるようにする、という二重のメリットを持つ。加えて、セキュアなアプローチをあなたの組織文化へと自動的に組み込む。

開発者にとって利益のあるライブラリやフレームワーク(secure-by-construction APIs のようなもの)を設計することは、可能である。また、これはセキュリティと信頼性の文化を促進する。

例として、エスケープを自動的に行う HTML テンプレートシステムを考える。これは、XSS 脆弱性を起こさないことを保証するセキュリティ不変条件であると同時に、開発者の観点では通常の HTML テンプレートと変わらず利用できる。

暗号化技術を正しく使うのはとても難しく、微妙なミスが脆弱性を生むことが多い。そのような経験から、Google では Tink という名前のライブラリを開発した。Tink は、Google が開発するアプリケーション内で、暗号化技術を安全に使う ことを簡単にする(間違って使うことを難しくする)ような API を提供する。

Tink の設計および開発を導いた原則:

  • Secure by default
  • Usability
  • Readability and auditability
  • Extensibility
  • Agility
  • Interoperability
    • Tink is available in many languages and on many platforms.

通常の暗号化ライブラリは、ローカルディスク上に秘密鍵を持つことを簡単にしているが、そのようなライブラリを使うと、鍵管理に関するインシデントを完全に防ぐことは難しい。対象的に、Tink の API は生の鍵情報(raw key material)を受け付けない。その代わりに、Tink はCloud Key Management Service (KMS)、AWS Key Management Service、Android Keystore のような鍵管理サービスと統合された鍵管理機能を提供することで、鍵管理サービスの利用を促進する。

注意点として、データの重要さに合わせて、適切な暗号化方法を選ぶのは Tink を利用するエンジニアの責任である。そのような設計レベルのミスを Tink で防ぐことはできない。ソフトウェア開発者とレビュアーは、セキュリティおよび信頼性に関する特性のうち、ライブラリやフレームワークが保証するものとしないものをしっかり理解しなければならない。

Tink に限界があるのと同様に、 secure-by-construction web framework も、XSS 脆弱性を防ぐことはできるが、アプリケーションのビジネスロジックに含まれるセキュリティバグは防げない。

Conclusion

システムのわかりやすさと、信頼性およびセキュリティは深く結びついている。

わかりやすいシステムを構築するための私たちからの第一のガイダンスは、システムを、明確かつ限られた目的を持つコンポーネントを用いて構築することである。それらのコンポーネントのなかにはそれぞれの TCB を構成するものもあり、セキュリティリスクへの対応に集中するものもある。

また、私達は望ましい特性(例えばセキュリティ不変条件や、構造的な回復力、データ耐久性)を、それらのコンポーネント内やコンポーネント間で強制するための戦略についても議論した。これらの戦略は以下を含む。

  • Narrow, consistent, typed interfaces: 狭く、一貫性があり、型を持つインターフェイス
  • Consistent and carefully implemented authentication, authorization, and accounting strategies: 一貫性があり、注意深く実装された認証、認可、課金の戦略
  • Clear assignment of identities to active entities, whether they are software com‐ ponents or human administrators: アクティブなエンティティ(ソフトウェアコンポーネントや人間の管理者)に対するアイデンティティの明快な割り当て
  • Application framework libraries and data types that encapsulate security invari‐ ants to ensure that components follow best practices consistently: コンポーネントがベストプラクティスに一貫して従うことを保証するように、セキュリティ不変条件を埋め込んだ、アプリケーションフレームワークライブラリやデータ型

あなたの最も重要なシステムが誤動作したときに、システムのわかりやすさが、それをささいなインシデントにするか、あるいは大災害にするかの明暗を分ける。SRE は自らの職務のために、そのシステムのセキュリティ不変条件を知っておかなければならない。極端なケースでは、SRE はセキュリティインシデントの発生時に、セキュリティのために可用性を犠牲にして、サービスをダウンさせなければならないかもしれない。

ここまでの感想

6章も、5章に続いて読み応えのある内容でした。タイトルにある Understandability(わかりやすさ)という言葉からは予想できなかった、説得力のある議論が次々と続いて、読んでいて飽きませんでした。

システムのわかりやすさ(understandability)について、明確な定義がなされていて、正直言って一度読んだだけでは理解しきれてない気がしますが、うまく身につけたら今後役立ちそうです。

文中で触れられていた A Philosophy of Software Design は、日本語訳がないようですが、ちょっと読んでみたいですね。検索したところ、日本語の解説記事がいくつかあったので、まずはそのあたりから読んでみます。

詳しい説明のなかでは、通常時と障害発生時でメンタルモデルが変わってしまうとシステムがわかりにくくなる、という指摘は、なるほどと思いました。

また、コンポーネントの責務を明確にするというのは、つまり特定の不変条件に対して責任を持つコンポーネントを明確にすることで、その結果、ある不変条件が成立していることを論証するために読むべきコード量が減る、というのはうまい説明方法だと思いました。現実には、なかなかそんなふうにきれいな構造のコードになってないことってありますよね……。この説明方法は、今後うまく活用していきたいです。

Building Secure and Reliable Systems 読書メモ - Chapter 5

f:id:muziyoshiz:20200430014145p:plain:w320

SRS 本の読書メモの続きです。Chapter 5 はなかなか重たい内容だったので、この章のみでメモを上げます。

SRS 本はこちらから無償でダウンロードできます。

前回までの読書メモは SRS Book タグ を参照のこと。

Chapter 5. Design for Least Privilege

最小権限の原則(The principle of least privilege)とは、アクセス主体が人間かシステムかに関わらず、タスクを達成するのに必要な最低限度のアクセス権限のみを与えるべきという原則である。新機能の設計段階から考慮することで、この原則が最も有効に働く。

社員の操作に完璧は期待できない。そこに悪意があるにせよないにせよ。思考実験として、もしあなたがなにか悪いことをしたいと思ったら、自分の組織にどのようなダメージを与えられるか考えてみよう。どんなことを、どのように実行できる? あなたの悪事は気づかれる? あなたの操作履歴を隠すことはできる? あるいは、もし良い意図だとしても、あなたができる最も破壊的なミスはどんなもの? 普段どれくらいのコマンドを手で実行していて、そのなかにどれくらいのタイミングミスやコピペミスがある?

そのような悪いアクションによる影響を最小化できる、または取り除けるようにシステムを設計することを推奨する。ここでも SRE の格言を引用すると、hope is not a strategy. である。

Concepts and Terminology

ベストプラクティスの詳細に入る前に、業界および Google での用語の定義について。

  • Least privilege(最小権限)
    • セキュリティ業界で確立された、幅広いコンセプト
    • 最小権限の目的は、そのシステムのすべての認証・認可のレイヤに渡って拡張されるべき
  • Zero Trust Networking
    • ユーザのネットワーク的な位置(それが社内ネットワークかどうか)によって、何の権限も付与しないという原則
    • Google では BeyondCorp program によって大規模な zero trust networking の実装に成功した
  • Zero Touch
    • SRE が直接システムやネットワークに触らず、拡張可能な自動化や、安全な API や、回復性のある多要素承認システム(resilient multi-party approval system)を通してのみ変更を行うこと
    • Google ではこれを実現するために Zero Touch Production (ZTP) や Zero Touch Networking (ZTN) などのインターフェイスを開発してきた

Classifying Access Based on Risk

すべてのアクセスを同じ方法で一律に制限するのではなく、セキュリティリスクや影響度に応じてアクセスを分類する必要がある。例えば、アクセスされるデータの種類による分類や、その API が書き込み可能かどうかによる分類がありがちだろう。

あなたのシステムの分類が明確に定義され、一貫性を持って適用され、広く理解されれば、開発者はそれを共通言語としてシステムやサービスを設計できる。この分類フレームワーク(classification framework)は、データストア、API、サービスやその他のエンティティに幅広く適用されうる。

p.64 の Table 5-1. Example access classifications based on risk に、アクセスを Public, Sensitive, Highly sensitive の3種類に分類した例を示す。

Best Practices

著者らが推奨する、最小権限モデルを実装する際のベストプラクティスを紹介する。

  • Small Functional APIs
    • さながら UNIX 哲学にあるように、1つのことだけをうまくやる API を作る
    • POSIX API のような巨大な API に対しては、制限や監査ログの記録(audit)は難しい
    • ユーザー向けの API より、管理用の API(Administrative API)のほうが権限が強い分、より一層の注意が必要
  • Breakglass
    • ガラスを割って非常ボタンを押すように、非常時に認証システムをバイパスする仕組みを breakglass mechanism と呼ぶ
    • 予期せぬ状況から復旧するために、このような仕組みは有用
    • For more context, see “Graceful Failure and Breakglass Mechanisms” on page 74 and “Diagnosing Access Denials” on page 73.
  • Auditing
    • 監査ログの記録から、意味のある検知ができるかどうかは、そのシステムの「アクセス制限がどれくらいの粒度か」と「リクエストに関するメタデータがどれくらい明確に記録されるか」に依存している
  • Testing and Least Privilege
    • 最小権限に関連するテストには、2つの重要な次元がある
    • Testing of least privilege
      • 必要なリソースのみに対するアクセス権限を正しく与えられていることを保証する
      • 本番環境への悪影響を避けるため、アクセス権限を実際に変更する前に、このテストは自動実行されるべき。テストカバレッジが不完全な場合は、監視やアラートの仕組みで一部補うこともできる
    • Testing with least privilege
      • テストのためのインフラが、そのテストに必要なアクセスのみを与えられていることを保証する
      • 例えば、新しい機能を開発したい人(データアナリスト)向けに、限られたデータセットへのアクセスのみを許可したいケースがある
      • テスト環境を作る代わりに、本番環境にテストアカウントを作るという方法もあるが、これは監査やACLにノイズを増やすことにもなる。テスト環境は高価だが、それを用意しなかった場合のリスクもある。あなたの状況ごとに、費用対効果をよく考えることが重要
  • Dignosting Access Denials
    • ユーザーの持っている権限によって、アクセス拒否時に渡す情報を変える。情報を与えすぎるとシステムを攻撃するための情報を与えることになりかねないが、与えなさすぎるとセキュリティポリシーチームのサポート負荷が限界を超えてしまう
    • Zero trust model の実装の初期段階では、アクセス拒否時にトークンを提示し、サポートチャンネルに問い合わせさせることを推奨する
  • Graceful Failure and Breakglass Mechanisms
    • breakglass mechanism を導入する際に考慮すべきガイドライン:
      • breakglass mechanism の利用は強く制限されるべき。一般的に、そのシステムの SLA に責任を持つ SRE チームに制限するべき
      • Zero trust networking のための breakglass mechanism は、特定の場所(例えば、物理的なアクセス制限のある panic rooms)からのみ利用できるようにすべき。ネットワーク上の位置を信頼しないという ZTN のコンセプトに反しているように見えるかもしれないが、この場合は、物理的なアクセス制限を前提として信頼する
      • breakglass mechanism の全ての利用は入念に監視されるべき
      • breakglass mechanism は、本番サービスに責任を持つチームによって定期的にテストされるべき

Auditing のベストプラクティスについては、この節のなかで更に深堀りされている。

  • よい監査ログを集める
    • よい監査ログを集めるうえで、Small Functional APIs には強いアドバンテージがある。Small Functional APIs がないと、一時的に強くて広い権限の API へのアクセスを与えるか、事前に監査を受けた上でシステムの直接操作を許可することになる。いずれも監査ログの追跡を難しくする
    • 最終的には、管理用 API と自動化を構築するチームは、監査を容易にする方法でそれらを設計する必要がある。そのためには、監査を重視する文化が必要。文化なしでは、監査は常に承認され、breakglass は日常利用され、意味をなさなくなってしまう
  • 監査者(auditor)を選ぶ
    • 監査の目的次第で、どのような監査者を選ぶべきかは変わる。Google では監査を以下の大きな2カテゴリに分類している
      • ベストプラクティスに沿っていることを確実にするために監査する(Audits to ensure best practices are being followed)
      • セキュリティ違反を特定するために監査する(Audits to identify security breaches)
    • 前者は、例えば breakglass を多用しすぎていないかを毎週の SRE チームミーティングでレビューすること。Google では breakglass review をチームレベルで分散して、社会規範化(social norming)を促進している。これは、現行の administrative APIs の欠点を明らかにし、改善を促すことにもなる
    • 後者は、Google では central auditing team に集約している。そのような攻撃は、複数の組織の監査ログを俯瞰的に見ないとわからないことがある
    • 著者らは、Google にて、structured justification(構造化された正当化)というものを使って、audit log events を構造化されたデータと関連付けている。これは、バグ番号や、チケット番号、カスタマーケース番号などに対する構造化された参照のことである。このような構造化されたデータがあれば、監査ログを自動的に検証できる

Worked Example: Configuration Distribution

具体例として、Web サーバ群に設定ファイルを配布する事例を考える。設定ファイルの管理に関するベストプラクティスは次のようになる。

  1. バージョン管理システム上に設定ファイルを保管する
  2. そのファイルに対する変更をコードレビューする
  3. その変更をまず canary set に配布し、ヘルスチェックを通ったら、すべての Web サーバに対して段階的に配布する。このステップは、この自動的なアクセスに対して、設定ファイルの更新権限を与えることを要求する

この更新機能を実現するために公開する Small API のアプローチはいくつかある。この節では以下の4通りを考える(p.75 の Table 5-2 に詳細)。

  • POSIX API via OpenSSH
  • Software update API
  • Custom OpenSSH ForceCommand
  • Custom HTTP receiver

POSIX API via OpenSSH はシンプルで一般的だが、何でもできてしまうため様々なリスクがある。Software update API は apt-get などのパッケージを使った更新のことだが、バイナリの更新と設定ファイルの更新を同時に行うため、やりたいことに対して操作が高価かつ破壊的すぎる(詳しくは Chapter 9 で触れる)。

Custom OpenSSH ForceCommand は OpenSSH 経由の操作を安全にできるが、いろいろな操作に広げていくことが難しい。OpenSSH の上でいろいろなコマンドを送れるようにしていくと、それは結局独自の RPC メカニズムを作ることになる。それなら gRPC や Thrift の上に作ったほうが良い。

Custom HTTP Receiver は Sidecar として実装する方法と、In-Process に実装する方法がある。In-Process の方法が最も柔軟性があり、Google での設定ファイルの管理方法にとても近い。

A Policy Framework for Authentication and Authorization Decisions

API に対するアクセス制限として、認証(Authentication)と認可(Authorization)の2つのステップをどのように行うかを決めなければいけない。この節では、Google で有用とわかったさまざまなテクニックについて議論する。

  • Using Advanced Authorization Controls
    • 認証、認可を正しく実装するのは大変なので、AWS や GCP が提供する IAM 機能を使ったほうがいい
  • Investing in a Widely Used Authorization Framework
    • 認証、認可のフレームワークを共通化することで、すべてのサービスエンドポイントに対する機能の強化(後述する MFA や MPA のサポート追加など)や設定変更が可能になる
  • Avoiding Potential Pitfalls
    • ポリシー言語の設計は難しい。ポリシー言語をシンプルにしすぎると目的を達成できず、汎用的にしすぎるとその内容を推測するのが難しくなる。いずれかの極端によらないよう、反復的なアプローチで設計するのがよい

Advanced Controls

多くの認可に関する判断は yes か no かの二択になるが、厳密な二択にする代わりに、逃がし弁としての "maybe"(追加のチェック) を用意することで、システムにおける緊張を劇的に緩和できる。

以下で示す方法は、単独でも使えるし、組み合わせて使うこともできる。どのような使い方が適切かは、データの重要性、操作の危険性や、既存のビジネスプロセス次第である。

  • Multi-Party Authorization (MPA)
    • 他の人に認可を与えてもらうこと。ミスを防げる、悪事への抑止効果がある、などのいくつかのメリットがある
    • MPA は、広範なアクセス権限を与えるために使われがちであるが、可能なら Small Functional API と組み合わせたほうがよい
    • MPA を導入するだけでなく、組織的、あるいは仕組み的に No といえる環境を用意する必要がある。悩んだ場合はセキュリティチームにエスカレーションできるような仕組みがあれば、組織的な圧力を弱めることができる
  • Three-Factor Authorization (3FA)
    • 1個のプラットフォーム(例えば全員が共通で使っているワークステーション1台)が攻撃されただけでは権限を奪われないようにしたい。MPA だとこの種の攻撃は防げない
    • 少なくとも2個のプラットフォームを用意したい。安価な方法としては、リスクが高い操作については、セキュリティ的に強化されたモバイルプラットフォーム(hardened mobile platform)からの認可を求める、という方法がある
    • 3FA と Two-factor authentication(2FA、二要素認証)や Multi-factor authentication(MFA、多要素認証)は別のものである。3FA は強い認可を提供するものであり、強い認証を提供するものではない。ただし、3FA の 3 が誤解を招く用語であることは確か。3FA は第2のプラットフォームからの認可を求めるものであり、第3ではない
    • MPAと3FAによって防がれる脅威は別のものである、ということを理解することが重要。そうすることで、一貫したポリシーに基づいて、これらの技術を適用できるようになる
  • Business Justifications
    • 認可を求める際に、ビジネス的な正当性を、バグID、インシデントID、チケットID、ケースID、関連アカウントなどと紐付けることを強制する
    • また、それらの ID に関連して、アクセスを許可するデータも特定のものに限定すべきである。適切に構成されたシステムでは、入力されたチケット ID に関連する、特定の顧客に関連するデータへのアクセスしか許可しないことも可能になる
  • Temporary Access
    • 一時的なアクセス権限を与えることで、認可に関する決定によるリスクを限定することができる。これは、特に、すべての操作に対して細かなアクセス権限を設定できない場合に有用である。また、root 権限のような ambient authority(環境全体に関する認可)を減らすことにも繋がる
  • Proxies
    • バックエンドサービスに対する細かなアクセス権限を設定できない場合、次善の策として、強く監視され、利用が制限されたプロクシ(あるいは踏み台)を用いることができる
    • 例えば、緊急時に、他の API による操作では復旧できない場合に、プロクシ経由の操作を許可する

Tradeoffs and Tensions

最小権限アクセスモデルを導入することで、あなたの組織のセキュリティに対する姿勢は間違いなく改善される。しかし、これまでに説明した利点と、モデルを導入することによるコストを割り引いて考えなければいけない。この節ではそれらのコストについて考える。

  • セキュリティの複雑性が増す。このユーザーはこのデータにアクセスできるのか? このデータにアクセスできるのは誰なのか?
  • すべてのデータへのアクセスを同様に禁じると、情報の透明性がなくなり、組織文化に影響する。例えば、ソースコードも機密情報の一種である。しかし、ある程度のリスクを許容して、開発者にソースコードへの広範なアクセス権限を与えた場合、開発者はソースコードから学んだり、コントリビュートすることができる。この問題については Chapter 21 で詳しく触れる
  • 最小権限を与えるためのデータ(コンテキスト)の品質が悪いと、セキュリティに影響する。セキュリティに影響するデータの品質をできるだけ高くするために、それらのデータを作るシステムをレビューすべきである
  • ワークフローを行うユーザーにとっての利便性が下がる。ユーザーの苦痛をなるべく小さくするためのナビゲーションや、自己診断〜サポートへの連絡を助ける方法が必要である
  • 最小権限のためのモデルが導入されたら、開発者はそれに従う必要がある。開発者にこのモデルを理解してもらうために、トレーニング教材や API ドキュメントを整備すべきである。また、開発者がセキュリティエンジニアによるレビューを気軽かつ迅速に受けられるようにすべきである

Conclusion

複雑なシステムを設計するにあたって、最小権限モデルは、クライアントにその目的を達成するために必要な能力を与え、かつそれ以上のものを与えないための最も安全な方法である。

Google はこのモデルを実装するために莫大な時間および労力を投じてきた。最後に、最小権限モデルを実装するための主要なコンポーネントを挙げる。

  • あなたのシステムの機能に関する包括的な知識。この知識があることで、あなたはそれぞれの機能が持つセキュリティリスクのレベルに従って、それらの機能を分類できる
  • この分類に基づく、できるだけ細かいレベルでの、あなたのシステムおよびあなたのデータへのアクセスの分割(partitioning)
  • あなたのシステムへのアクセスを試みるユーザーのクレデンシャルを検証するための認証システム
  • 適切に分割されたシステムに対してアタッチされた、うまく定義されたセキュリティポリシーを適用するための認可システム
  • 細かな認可を与えるための、高度な制御機能。例えば、一時的な認可、MPA、3FA など
  • これらのキーコンセプトを支えるための、あなたのシステムに対する運用面での要件。少なくとも、以下が必要である
    • すべてのアクセスを監査し、あなたが脅威を見つけてフォレンジック調査を行うきっかけとなるシグナルを生成する能力
    • セキュリティポリシーを論理的に考えて、定義し、テストし、デバッグするための手段、およびそのポリシーに関するエンドユーザーサポートを提供する手段
    • あなたのシステムが想定通りに動かなかった場合に breakglass mechanism を提供する手段

ここまでの感想

5章だけで30ページ近くあり、具体例も豊富で勉強になりました。例えば、Auditor には2種類ある、というのは僕にとっては新しい観点でした。MPA と 3FA で目的が違う(MFA もまた違う)、というのも本書を読んでよく理解できました。

扱われている話題の範囲も広く、Google が実現しているレベルに達するのはかなり大変そうだ……というのが率直な感想です。

その一方で、いままで自分たちもやっていたことだけど、Google ではそう呼んでいるのか、と気づいた部分もありました。システムに対する作業を、今の仕事では Backlog の課題キー(チケット ID に相当するもの)と関連付けて記録しているのですが、これはまさに本章で "Business Justification" と呼ばれているものでした。

Google の事例を見て落ち込みすぎず、本書の内容を手本として、ベストプラクティスをできるところから少しずつ取り入れていきたいですね。

Building Secure and Reliable Systems 読書メモ - Chapter 3 & 4

f:id:muziyoshiz:20200430014145p:plain:w320

SRS 本の読書メモの続きです。Chapter 5 が結構長いので、とりあえず Chapter 3〜4 の読書メモを書きました。

SRS 本はこちらから無償でダウンロードできます。

前回までの読書メモはこちら。

Part II. Designing Systems

Part II (Chapter 3〜10) では、セキュリティと信頼性に関する要求を実装するための最もコスト効率の良い方法、すなわちソフトウェア開発サイクルのできるだけ早い段階、システムの設計時にそれらに着手するということにフォーカスする。

Chapter 3. Case Study: Safe Proxies

この章は Google でのケーススタディ(Safe proxies)の簡単な紹介のみ。このケーススタディに含まれる要素は、Chapter 4 以降で詳しく紹介される。

Safe proxies とは、認可された人に対して、物理サーバや仮想マシン、特定のアプリケーションへのアクセスや状態変更を許可するためのフレームワーク。Google では、システムへの SSH 接続の必要なしに、リスクのあるコマンドのレビュー、承認、実行を行うことが出来る。

Safe proxies は本番環境への単一のエントリーポイントとなり、以下のようなことを可能にする(Figure 3-1 に Safe proxy model が図示されている)。

  • fleet 内でのすべての操作の監査
  • リソースへのアクセス制御
  • 人的ミスが本番環境へ大規模に広がることに対する保護

Zero Touch Prod という Google のプロジェクトがある。その目的は、本番環境に対する変更はすべて自動的に行われるか、ソフトウェアによって事前検証されるか、監査済みの breakglass mechanism を通して実行されるようにすることにある。Safe proxies はこれらの原則を達成するために用いられるツールセットの一つである。

Safe proxies の導入によって生じる摩擦を減らすために、この章の著者らはエンジニアと密に連携し、緊急時には brakeglass mechanism を通してエンジニアがシステムにアクセスできるようにしている。breakglass mechanism とは、緊急時にセキュリティポリシーをバイパスして操作できるようにするためのメカニズムのこと(火災報知器を鳴らすときにガラスを割る=breakglassからの連想)。breakglass mechanism は権限が強いゆえに、Multi-Party Authorization (MPA) や厳密な監査ログなどとセットで実現される。これらの詳細は Chapter 5 に書かれている。

Google Tool Proxy(Safe proxies と呼ばれるプロクシ群の1つ?)は、CLI の実行をプロクシするツールである。エンジニアはマシン上で直接コマンドを実行できない。Tool Proxy が任意のコマンドを gRPC で受け取り、ポリシーと照らし合わせ、監査ログを記録し、MPA を要求した後にコマンドを実行する。文中では borg CLI を例に挙げている。

Chapter 4. Design Tradeoffs

セキュリティ、信頼性、および機能面での要求の間のトレードオフに関する章。

セキュリティと信頼性を後回しにして一時的に開発速度を上げても、あとから重大なコストとリスクを生じる。しかし、システムのライフサイクルの初期からこれらを考慮した設計にすることで、この三者をすべて満たすことができる、というのがこの章での主張。

典型的には、機能要件(feature requirements, functional requirements)が最初に作られ、これがシステム設計に関する決定の推進力になる。それに対して、特定の機能に紐付かない、セキュリティや信頼性に関するものは非機能要件に含まれる。また、開発やデプロイのベロシティに関する要件も非機能要件に含まれる。

機能要件は仕様を書き出して、実装して、仕様通りかをテストする、という流れがわかりやすいが、セキュリティや信頼性についてはそれが難しい。信頼性とセキュリティは、システムデザインから現れてくる特性(emergent property)であり、単一の機能を実装するという類のものではない。コラム "Reliability and Security as Emergent Properties of System Design"に、この特性に関係する要素がいくつか挙げられている。

システムの初期段階から信頼性とセキュリティについて考える方法の一例として、Google で用いられている Design document template がある。このテンプレートには信頼性およびセキュリティに関するセクションがあり、p.47以降で各セクションが詳しく紹介されている。

機能要件と非機能要件のバランス、トレードオフの解決は難しい。"Example: Payment Processing" では支払い処理を例に挙げて、reliability risk を避けようとすると、security risks が出てくる、というケースを説明している。

Google ではマイクロサービスのための社内向けフレームワーク(Google Web Application Framework)を進化させてきた。

Google Web Application Framework は、アプリケーションコードがコーディングガイドラインやベストプラクティスに一致するかどうかのチェック(conformance checks)を静的または動的に適用することができる。また、開発やデプロイで必要となる共通タスクが最初から組み込まれている。フレームワークにこれらの機能が含まれているため、それを開発者が自分で作った場合に起こる脆弱性の混入を防いでくれる。

また、ビルドから SLA の計測まで自動化されていることで、error budget を消費した製品についてはリリースを止めることもできる(コラム "Reliability and Security Benefits of Software Development Frameworks" 内の記載)。

セキュリティと信頼性に対する優先度を上げることは、他の優先すべきこと(機能要件)を妨げない。むしろ上記の検査などが自動化されていれば、開発速度は上がる。セキュリティ問題に柔軟に対応できる設計ということは、新機能を追加しやすい設計ということでもある。

開発初期のベロシティ(Initial Velocity)と、長期的なベロシティ(Sustained Velocity)を区別して考える必要がある。開発初期のベロシティを重視してセキュリティと信頼性を考慮しないと、長期的にはスローダウンする。そのため、セキュリティと信頼性をチームの文化に埋め込む必要がある(チームの文化については Chapter 21)。

ここまでの感想

Chapter 3〜4 を読むと、Google はそのスケールメリットを生かして、セキュリティと信頼性に関わるツールやフレームワークを共通化しているということがよくわかります。しかし、このアプローチは Google ほどの規模がないと難しそうですし、普通の会社はどうしたらいいんだろう……というのが、ここまで読んだ率直な感想でした。

あと、個人的には Chapter 4 の文章がところどころ胸に刺さりました。例えば、コラム "Cost of Adding Reliability and Security to Existing Systems" の以下の記載。

Accommodating security and reliability requirements in an existing system often requires significant design changes, major refactorings, or even partial rewrites, and can become very expensive and time-consuming. Furthermore, such changes might have to be made under time pressure in response to a security or reliability incident— but making significant design changes to a deployed system in a hurry comes with a significant risk of introducing additional flaws.

既存システムのセキュリティと信頼性の要件を満たすためには、多くの場合、大幅な設計変更、大規模なリファクタリング、あるいは部分的な書き換えが必要となり、非常に高価で時間のかかる作業になることがあります。 さらに、このような変更は、セキュリティや信頼性のインシデントに対応するために、時間的なプレッシャーの中で行わなければならないかもしれませんが、デプロイされたシステムの大幅な設計変更を急いで行うことは、さらなる不具合を導入する大きなリスクを伴います。

あとは "Initial Velocity Versus Sustained Velocity" の節にある以下の文章も個人的には刺さりました。

Furthermore, tests retrofitted to a mature system can sometimes fall into the trap of exercising the current buggy behavior more than the correct, intended behavior.

さらに、成熟したシステムにあとから組み込まれたテストは、正しい、意図された振る舞いをテストするというより、現在のバグった振る舞いを再現していることをテストする、という罠に陥ることがあります。

そういうことってありますよね……。

いまあるシステムにとっては何の慰めにもならない文章ばかりですが、これから作るシステムについてはせめて最初からセキュリティと信頼性について考えよう、と思わせてくれる内容でした。

Building Secure and Reliable Systems 読書メモ - Chapter 1 & 2

f:id:muziyoshiz:20200430014145p:plain:w320

今月中旬に、Google SRE による SRE 本の3冊目 "Building Secure and Reliable Systems" が無償公開されました。

www.publickey1.jp

以下のサイトから PDF, EPUB, MOBI 形式でダウンロードできます。過去の2冊は HTML で公開されていますが、今回は電子書籍のデータ形式での公開のようです。

landing.google.com

日本語版は、もし出版されたとしても2年後くらいになりそうなので、僕もそろそろ重い腰を上げて読み始めました。

導入にあたる Part I(Chapter 1 & 2)まで読み終わったので、ここまでの読書メモと感想を書いておきます。久しぶりに洋書読んでて結構疲れたので、自分への動機づけのために、今後、何章か読むたびに読書メモを書いていこうと思います。

読書メモ

以下、内容に関する僕の理解と、僕が特に重要と思った箇所のメモです。また、一部はメモに加えて、原文を引用しています。

あくまで内容の一部ですし、まだ全部読み終わってないので誤解も多いと思います。気になったキーワードがあったら原著をあたってください。

Foreword by Royal Hansen (Vice President, Security Engineering)

SRE モデル(DevOps の SRE-like version)が一般的になるにつれ、SRE が取り組んでいる問題領域は、同様の原動力をセキュリティの問題にももたらすのではないかと気づいた。いくつかの組織はこの2つを "DevSecOps" と呼ばれるアプローチに統合している。

SRE と security engineer には似たところが多い。SRE はチーム間を結びつけるモデル(error budget などのこと?)を作ったが、security engineer にも同様のものが必要。Hansen 氏とその同僚は、セキュリティは first-class であるべきと主張してきた。大企業内でのセキュリティに対するアプローチはこの20年間で劇的に変わった。

SRE が信頼性をソフトウェアのライフサイクルに組み込んだように、セキュリティもソフトウェアのライフサイクルに組み込むことが重要と思う。

Foreword by Michael Wildpaner (Senior Director, Site Reliability Engineering)

Site Reliability Engineering と Security Engineering はシステムの可用性を保つことに関係している。セキュリティインシデントはユーザーの信頼を壊し、システムの可用性を壊す。システムセキュリティは SRE の心の常にトップを占める。

Gmail の SRE テックリードの一人だったときに、SRE が、悪い設計や悪い実装によるセキュリティへの悪影響を防ぐ最後の壁として働くのを目にした。

Google の SRE に関する過去の2冊の書籍は、信頼性とセキュリティの交点(intersection of reliability and security)についての詳細は触れなかった。本書はそのギャップを埋め、セキュリティに特化したトピックにも紙面を取っている。

Preface

本書の目的は、セキュリティと信頼性に特化した実践者からの、システム設計、実装、およびメンテナンスに関する知見を提供すること。

We argue that everyone should be thinking about the fundamentals of reliability and security from the very beginning of the development process, and integrating those principles early in the system lifecycle.

私達は、すべての人々がその開発プロセスの初期から、信頼性とセキュリティの基盤について考え、それらの原則をシステムライフサイクルに統合すべきと強く考えている。

Because security and reliability are everyone’s responsibility, we’re targeting a broad audience: people who design, implement, and maintain systems. We’re challenging the dividing lines between the traditional professional roles of developers, architects, Site Reliability Engineers (SREs), systems administrators, and security engineers.

セキュリティと信頼性は全員の義務であるという考えから、本書は広い範囲の読者を対象としている。私達は従来の役割の線引き(開発者、アーキテクト、SRE、システム管理者、セキュリティエンジニア)を超えた話をしようとしている。自分のいまの立場とは関係なく、それぞれの立場に立って読んでみてほしい。

Chapter 1 と 2 を最初に読み、そのあとはあなたが最も興味を持った章を読むことをお勧めする。

Chapter 1. The Intersection of Security and Reliability

Google で2012年に起きた、社内システムの障害の事例。ある WiFi パスワード変更の告知がきっかけで、社内向けのパスワードマネージャ(数年前に作られたまま放置状態)に想定外の高負荷がかかりダウン。その信頼性の低さに反して、サーバ再起動のためのセキュリティが厳重で、最終的に金庫を電動ドリルで開けて HSM を取り出す羽目になった。

セキュリティと信頼性は、真に信用に足るシステムの重要な構成要素だが、セキュリティと信頼性の両方を満たすシステムを作るのは難しい。共通点もあるが、設計時に考慮すべき点は異なる。両者の微妙な連携が崩れると予想外の結果が生じる。

信頼性を上げるための設計が、セキュリティに影響する。トレードオフがある。冗長性を上げると攻撃対象が増える。インシデント対応のためにログの情報量を増やすと、ログも攻撃の対象になりうる。

Both security and reliability are concerned with the confidentiality, integrity, and availability of systems, but they view these properties through different lenses. The key difference between the two viewpoints is the presence or lack of a malicious adversary.

セキュリティと信頼性は、いずれも CIA(機密性、完全性、可用性)に関係するが、悪意ある攻撃者を想定するかどうか、という点が大きな違い。

セキュリティと信頼性は、いずれもシステムの設計から現れる特性である。いずれもシステムの設計初期から考慮しなければならないが、一見無害に思える変更で容易に壊れてしまう。その他にもいくつかの共通点がある。

  • Invisibility
    • いずれもうまく行っているときは目に見えず、問題が生じたときだけ目につく
    • いずれも、損なった場合の損害が大きい
  • Assessment
    • いずれも悪影響が起きたときのコストを評価する必要があるが、セキュリティの方はその評価が更に難しい
  • Simplicity
    • いずれも、システム設計をできるだけシンプルに保つことで達成しやすくなる
  • Evolution
    • 市場の要求に答えるための機能追加などによりシステムは徐々に複雑になり、その影響は読みづらい
    • セキュリティに関しては攻撃手段も日々進化する
  • Resilience
    • システムが複雑になるにつれ、システムが回復性を持つと証明することは難しくなっていく
    • Defense in depth(多重防御、縦深防御)、Distinct failure domains(障害の影響範囲の明確化)、The princeple of least privilege(最小限の権限の原則)などで脅威による影響を狭める
  • From Design to Production
    • 本番へのデプロイ後も設計を維持する必要がある。コードレビューや共通フレームワークの活用、テスト、デプロイのためのシステムでそれを助ける
  • Investigating Systems and Logging
    • セキュリティと信頼性を完全に維持することは難しいため、それが崩れたときに検知する仕組みが必要
    • 検知のためにはログは多く、詳細なほどいいが、コストや情報漏えいのリスクとのトレードオフがある
  • Crisis response
    • Google は Incident Management at Google (IMAG) と呼ばれるプログラムに体系化している。IMAG はアメリカ政府の Incident Command System (ICS) をモデルにしている
    • ある危機が起きてから次の危機が起こるまでには長いインターバルがあるため、その間にスキルとモチベーションを維持するために Disaster Recovery Testing program (DiRT) を定期的に実行している。内部システム障害をシミュレートし、チームでの対処を強制する
  • Recovery
    • 障害からの復旧は迅速に行いたいが、全体に同時にデプロイすると、新たな不具合や脆弱性も同様に全体へデプロイされる危険がある
    • 変更を段階的にデプロイし、問題が起きたらすぐ発見できる仕組みが必要

Chapter 2. Understanding Adversaries

実際の攻撃者の分類についての詳細な解説。古典的なイメージのハッカーから離れて、実際の攻撃者、国家的な組織や犯罪者などについて詳しく知ることで、防御について考えることができる。

また、インサイダー(内部関係者)によるリスクにかなりのページを割いている。悪意あるインサイダーだけでなく、操作ミスなどによる影響もここに含む。Table 2-1. General categories of insiders and examples と Table 2-2. Framework for modeling insider risk はこのテーマについて話すときの必須資料になりそう。

インサイダーへの対策として、以下のコンセプトを取り上げている。これらは3章以降で詳しく説明される。

  • Least privilege
  • Zero trust
  • Multi-party authorization
  • Business justifications
  • Auditing and detection
  • Recoverability

攻撃者について理解するために、攻撃方法についての知識を得る方法を紹介している。

  • セキュリティファームによる threat intelligence(脅威情報)
    • 攻撃者やマルウェアの分析レポート、Indicators of compromise (IOCs)
  • Cyber Kill Chain (TM)
    • 攻撃者の行動を、攻撃者が最終的な目標達成までに取るステージという形で理解するためのフレームワーク
  • 攻撃方法をカタログ化したフレームワーク

著者らが見出した、リスクを評価する上での心構え。

  • You may not realize you’re a target. (自分たちが狙われる明確な理由はなさそうでも、他の攻撃の土台としてターゲットになりうる)
  • Attack sophistication is not a true predictor of success. (複雑な攻撃方法を気にするより、基本的な攻撃方法から対処せよ)
  • Don’t underestimate your adversary. (攻撃者があなたにかける労力を過小評価するな)
  • Attribution is hard. (攻撃者を特定しようとはせずに、攻撃者の取りうる行動、TTP への対処に集中せよ)
  • Attackers aren’t always afraid of being caught. (攻撃者は国をまたぐことも多く、もし特定できても逮捕は難しい)

ここまでの感想

自分の経験上も SRE とセキュリティって強い関連性があると思います。たいていの会社ではセキュリティエンジニアって希少で、セキュリティ上の問題が起こると SRE は必ず何らかの形で動くことになるんじゃないでしょうか?

本書の序文でもセキュリティエンジニアについて言及されていましたが、ここまで読んだ限りでは、ここから SRE とセキュリティエンジニアの連携の話が始まるのか、SRE がセキュリティエンジニアの役割を兼ねる話が始まるのかはよくわかりませんでした。あと、いわゆる情シスとの関係ってどうなんでしょう? Chapter 3 以降で詳しい話が出てくると思うので、この先が楽しみです。

本書は全体的に具体例が多く、読んでいて飽きないです。ただ、その分、読んでいると余計なことをいろいろ考えてしまいます。

特に、Chapter 1 の冒頭にあるパスワードマネージャの障害の話。勝手な想像ですけど、最初はなにかの片手間で作ったようなシステムが、いつの間にか全社で使われるようになって、あとから社内監査で文句を言われてセキュリティが強化された、とかですよねきっと……。具体例があるあるすぎて、つい我が身を振り返ってしまいます。

次は?

Chapter 6 と 21 あたりが気になってます。次は、これらの章か、Chapter 6 の前提知識になりそうな Chapter 3〜5 あたりを読んでいこうと思います。

  • Chapter 6. Design for Understandability
  • Chapter 21. Defining a Healthy Security and Reliability Culture

目次を読むだけでも刺さる単語がいくつか出てきて、自分の現場にも持ち帰れる知見がありそうです。