無印吉澤

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

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

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

SRS 本の読書メモです。

Chapter 7 は、Google での事例を中心に、変更にはその内容によって異なるタイムライン(短期、中期、長期)があることと、タイムラインの種類ごとに注意すべき点を解説しています。

事例情報がメインなので、要約してしまうと面白さが減ってしまいそうな章でした。そのため、今回のメモは短めです。興味を持たれた方は原文をご参照ください。

SRS 本について

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

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

Chapter 7. Design for a Changing Landscape

この章では、Googleが長年、どのような変更を行ってきたか、そしてそれをどのように適用してきたかという事例を複数紹介する。そして、その道程で私達が学んできた教訓を示す。

ほとんどの設計上の判断は、特にそれがアーキテクチャに関するものは、システムの設計フェーズで実装するのが最も容易かつ安価である。しかし、この章で示すベストプラクティスのほとんどは、システムライフサイクルの後半のステージで実装されたものである。

私達が日々扱うライブラリやアプリケーションの数は増え続けており、それらには常に新たな脆弱性の影響を受ける可能性がある。また、ユーザーや各種規制からの、セキュリティやプライバシーに対する期待も常に上がり続けている。

このような状況に対応するためには、自分たちのインフラを頻繁かつ迅速に変更できる必要があるが、その一方で高信頼なシステムを維持する必要もある。このバランスをとるためには、変更をいつ、どのくらいのスピードでロールアウトする(展開する)かを決めることが重要になる。

Types of Security Changes

自分たちのセキュリティ体制(security posture)を改善したり、セキュリティインフラの回復力を高めるためには、さまざまな変更が考えられる:

  • Changes in response to security incidents (see Chapter 18)
  • Changes in response to newly discovered vulnerabilities
  • Product or feature changes
  • Internally motivated changes to improve your security posture
  • Externally motivated changes, such as new regulatory requirements

Designing Your Change

セキュリティ上の変更(security changes)は、他のソフトウェア変更と同様、基本的な信頼性要件とリリースエンジニアリング原則の対象となる(本書の Chapter 4 や SRE 本の Chaprter 8 を参照)。セキュリティ上の変更をロールアウトするためのタイムラインは異なるかもしれないが、その全体的なプロセスは同じベストプラクティスに従うべきである。

すべての変更がもつべき特性(characteristics)は以下の通り:

  • Incremental
    • 個々の変更を、小さく、独立したもののする。リファクタリングのような、無関係な変更を混ぜたくなる誘惑に逆らう
  • Documented
    • その変更内容およびロールアウトの相対的な緊急性を理解できるように、変更についての "how" と "why" を記載する
    • 文書に含まれる内容:
      • Requirements
      • Systems and teams in scope
      • Lessons learned from a proof of concept
      • Rationale for decisions (in case plans need to be reevaluated)
      • Points of contact for all teams involved
  • Tested
    • ユニットテスト、および可能ならインテグレーションテストを行う
  • Isolated
    • Feature flag を用いて、機能のオンオフと、バイナリの更新を別に扱う(詳しい情報は SRE workbook の Chapter 16 を参照)
  • Qualified
    • 複数のステージからなる、正規のリリースプロセスに従ってロールアウトする
  • Staged
    • カナリアリリースのような方法で、段階的にリリースする。変更前後でのシステムの振る舞いの違いを確認できるようにすべき

これらのプラクティスは "slow and steady" approach を取ることを勧めているが、スピードと安全さの間にはトレードオフがある。

Architecture Decisions to Make Changes Easier

システムを変更しやすい状態にするための戦略について。

Keep Dependencies Up to Date and Rebuild Frequently

自分がシステムが依存しているソフトウェア(OpenSSL や Linux カーネルなど)について、最新のバージョンを利用するように保ち続ける。最新版を利用していればパッチを当てるのも用意になる。

Release Frequently Using Automated Testing

SRE の基本原則では、巨大なリリースを行う代わりに、そのリリースをより小さなものに分割し、定期的にロールアウトすることを推奨している。リリースに含まれる変更が小さいほど、ロールバックの可能性も減る。SRE Workbook の Figure 16-1 にある "virtuous cycle"(好循環)を参照。

同様に、コンテナやマイクロサービスを用いることで、パッチを当てるべき境界面を減らすことができる。

Use Containers

コンテナは、あなたのアプリケーションに必要なバイナリとライブラリを、その下にあるホスト OS と切り離し、依存関係を減らす。コンテナレジストリを更新したら、その更新後のコンテナが自動的にロールアウトされていく。

脆弱性が発生した場合は、コンテナそのものではなく、コンテナイメージをスキャンすることで影響範囲を特定できる。パッチがあたっていない古いイメージがデプロイされないように、本番環境には最新のイメージのみデプロイされるように強制すべきである。

コンテナに関連する話題は、SRE 本の Chapter 7 の "Case Study 4: Running Hundreds of Microservices on a Shared Platform"、および SRE workbook の Chapter 16(blue/green deployment によるパッチ当ての話)にある。

Use Microservices

マイクロサービス化することで、それぞれのマイクロサービスをスケールアウト、負荷分散、ロールアウトできる。つまりインフラの変更を柔軟に行えるようになる。

マイクロサービス化すると、あらゆるトラフィックがそこを流れるようになるので、limited or zero trusted network が必要になる。マイクロサービス化により、チーム間で共通のライブラリやインフラを使うようになる。そのライブラリやインフラを少数の責任者が更新・管理する。望ましいセキュリティ特性を維持するために、できるだけサービスをシンプルにすることを保証するための制約が必要になる(※本書の Chapter 6 で取り上げられていた話題)。

Example: Google’s frontend design

Google のフロントエンドデザインは、回復性と多層防御(defense in depth)を実現するためにマイクロサービスを用いている。

Google Front End (GFE) は、Google サービスに対するフロントエンド層を提供する。ほとんどの Google サービスはバックエンド層のマイクロサービスとして実装されていて、インターネットに直接公開されていない。

GFE は外部からの通信を終端し、DDoS対策や、ルーティングや、ロードバランシングを提供する。また、個々の開発チームが苦労することなく、新しいセキュリティ機能を導入できる。例えば、Google セキュリティチームが Application Layer Transport Security (ALTS) ライブラリを、自社の RPC ライブラリに組み込むことで、ALTS サポートを迅速に広範囲へ適用できた。

また、バックエンド層とフロントエンド層は、その内部にまた複数のレイヤーを持つことができる。そのそれぞれのレイヤーで負荷分散できる。このようなアーキテクチャにより、それぞれのレイヤーへの変更が適用しやすくなっている。

Different Changes: Different Speeds, Different Timelines

すべての変更が同じタイムライン、または同じ速度で発生するわけではない。以下に示す複数の要素が、変更をどれくらいの速さで更新する必要があるかに影響する。

  • Severity (重大性)
  • Dependent systems and teams
    • 他の人の作業を待つ必要があるケース(ベンダーのパッチ提供、顧客側のクライアントへのパッチ適用など)
  • Sensitivity (※何と訳すべき? 感度?)
    • ※Severity との違いがよくわからなかった。セール期間のような重要なイベント中は変更を後回しにしたいと思うかもしれない、という話が書かれているので、客観的な重大性ではなくて、自分たちの事情と比較した上での緊急性という意味?
  • Deadline
    • 法規制などによる、明確な期限がある場合
    • 脆弱性情報の報道規制(embargo)の期間が終わるまでにパッチを当てなければいけない場合

以下の節では、タイムラインが異なる事例を3つ紹介する。

  • 短期の変更:新しいセキュリティ脆弱性への対応
  • 中期の変更:新しいプロダクトの段階的な適用
  • 長期の変更:規制上の理由による変更(その変更を実装するために Google は新しいシステムを作る必要があったもの)

Short-Term Change: Zero-Day Vulnerability

Shellshock(bash の脆弱性)の事例。

Shellshock に対して、Googleのインシデントレスポンスチームはこの例外的な脆弱性を解決するために Black Swan protocol (?) を開始し、以下を行うための大規模な対応を調整した。

  • Identify the systems in scope and the relative risk level for each
  • Communicate internally to all teams that were potentially affected
  • Patch all vulnerable systems as quickly as possible
  • Communicate our actions, including remediation plans, externally to partners and customers

この対応と並行して、インシデントレスポンスチームは、Google のネットワーク境界内にある脆弱なシステムを検知するためのソフトウェアを開発した。そして、このソフトウェアを、残りのパッチ作業を完了させるために用いるだけでなく、Google の標準セキュリティ監視の機能に追加した。

この対応中に実施したこと(およびしなかったこと)から、他のチームや組織にも適用できるいくつかの教訓が得られた:

  • Standardize software distribution to the greatest extent possible (ソフトウェアの配布を可能な限り標準化する)
  • Use public distribution standards (?)
    • ※読んでも、何を指しているのかわからなかった
  • Ensure that you can accelerate your mechanism to push changes for emergency changes (緊急時の変更のために、自分たちの変更適用メカニズムを高速化できるかどうか確認する)
    • 例えば、validation をスキップするなどして高速化できるか?
  • Make sure that you have monitoring to track the progress of your rollout, that you identify unpatched systems, and that you identify where you’re still vulnerable (ロールアウトの進捗を監視し、パッチが当てられていない=まだ脆弱性のあるシステムを特定できるようにする)
  • Prepare external communications as early in your response efforts as possible (脆弱性対応のできるだけ早い段階で、外部とのコミュニケーションを準備する)
  • Draft a reusable incident or vulnerability response plan (再利用可能なインシデントや脆弱性対応計画の作成)
  • Know which systems are nonstandard or need special attention (どのシステムが標準に従っていないか、または特別な対応を必要とするかを知る)

Medium-Term Change: Improvement to Security Posture

FIDO セキュリティキーを用いた二要素認証の事例。

変更のロールアウトに関しては、2つの対立する哲学がある。

  • Start with the easiest use case, where you’ll get the most traction and prove value.
  • Start with the hardest use case, where you’ll find the most bugs and edge cases.

組織内での賛同をまだ十分に得られていないなら、最も簡単なユースケースから始めることに意味がある。組織の上層部からすでに十分な支持が得られているなら、最も難しいユースケースから始めて、バグや課題を早期に発見したほうがいい。

Phishing 対策として、Google 社内では二要素認証の調査およびテストを2011年に開始した。さまざまな選択肢を考慮したのち、Google は FIDO セキュリティキーを共同設計した。そして、OTP の代わりに FIDO セキュリティキーを導入する活動を2013年から開始した。

社内への展開はセルフサービス的に行った。最初は、志願してこの機能をオプトインしたユーザーから導入した。このデバイスは、各オフィスにある TechStop location(ヘルプデスク)から入手しやすくした。

それと同時に、セキュリティチームは FIDO 対応が必要なアプリケーションの長いリストに対応した。2015年にロールアウトの完了と、OTP サービスの廃止に集中し始め、2015年内に対応を完了した。

この経験から得られた教訓は、セキュリティ及び信頼性に関する文化を作ることに関係するものだった。

  • Make sure the solution you choose works for all users. (そのソリューションを、すべてのユーザーが利用できるものにする)
  • Make the change easy to learn and as effortless as possible. (その変更内容を、学びやすく、できるだけ苦労せずに済むものにする)
  • Make the change self-service in order to minimize the burden on a central IT team. (中央集中的な IT チームの負荷を最小化するために、その変更を、セルフサービス可能なものにする)
  • Give the user tangible proof that the solution works and is in their best interest. (そのソリューションが動作し、自分たちの利益になることを、実際に触れて確認できる証拠を提供する)
  • Make the feedback loop on policy noncompliance as fast as possible. (ポリシーに準拠していないことを、できるだけ早く気付けるようなフィードバックループを作る)
  • Track progress and determine how to address the long tail. (進捗を計測し、ロングテールをどのように解決するかを決定する)

Long-Term Change: External Demand

Google Chrome チーム、Let's Encrypt およびその他の組織による努力により、HTTPS の利用率が増加した事例。

長期の変更では、きちんとしたドキュメンテーションと、進捗の計測&可視化の自動化が必要。長期に渡るプロジェクトでは、担当者が途中で会社を去る可能性がある。

作業にはロングテール(変更が行き届かない部分)がある。全体の80〜90%に適用できたら、それはセキュリティリスクの削減に対する計測可能なインパクトを持つため、成功したとみなされるべきだろう。

ここから得られた教訓:

  • Understand the ecosystem before committing to a strategy.
  • Overcommunicate to maximize reach.
  • Tie security changes to business incentives.
  • Build an industry consensus.

Complications: When Plans Change

変更計画を変更して、変更を加速させたり、遅らせなければいけなくなる潜在的な理由:

  • You might need to delay a change based on external factors.
  • You might need to speed up a change based on a public announcement.
  • You might not be severely impacted.
  • You might be dependent on external parties.

Heartbleed(OpenSSL の脆弱性)は、キャッチーなロゴと名前で、予想外にメディアの衆目を集めた脆弱性だった。そのため、当初の計画より、パッチの適用を加速させる必要が生じた。

Heartbleed の事例からは、いくつかの重要な教訓が得られた:

  • Plan for the worst-case scenario of the embargo breaking or being lifted early.
  • Prepare for rapid deployment at scale.
  • Regularly rotate your encryption keys and other secrets.
    • SRE workbook の Chapter 9 も参照
  • Make sure you have a communication channel to your end users—both internal and external.

ここまでの感想

次にセキュリティ変更を行うタイミングがあったら、読み返して、計画立案の参考にしたいと思える内容でした。

セキュリティ変更を行う際には事前に計画を行い、それが短期〜長期のいずれに該当するかによって適切なアプローチが変わる、ということを事例とともに説明していて説得力がありました。また、変更がロールアウトされた範囲を自動的に検出可能にする(検出のために人手を挟まないようにする)ことで、各開発チームが自律的にセキュリティ変更を進められるようにするというのは、良いプラクティスだと思いました。今後うまく適用できそうな機会があれば、是非試して見たいです。

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.

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

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

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