Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Deno で作る快適な “as Code” プラットフォーム – TSKaigi 2024

Deno で作る快適な “as Code” プラットフォーム – TSKaigi 2024

アーカイブ動画

TSKaigi 2024 での登壇資料です。
プログラマブルなセキュリティ SaaS「Shisho Cloud」における、「クラウドの設定値検査ルールをTypeScriptで書ける」という機能について、その背景や Deno をベースにした実行基盤の作り方などを発表しました。

参考: リリースノート

pizzacat83

May 11, 2024
Tweet

More Decks by pizzacat83

Other Decks in Technology

Transcript

  1. $ whoami pizzacat 8 3 
 Flatt Security ソフトウェアエンジニア セキュリティエンジニアとして

    Flatt Security に⼊社し、Web‧ Firebase‧クラウドのセキュリティ診断を担当しながら、 
 診断を管理する社内システムの内製にも従事。 
 現在はソフトウェアエンジニアとしてセキュリティ SaaS 
 「Shisho Cloud」の開発に従事し、フロントエンド、バックエンド、 CLI、TypeScript‧Rego コード実⾏基盤などを⼿掛ける。 2
  2. Deno で作る快適な “as Code” プラットフォーム セキュリティ SaaS「Shisho Cloud」における 
 「クラウドの設定値検査ルールを

    TypeScript で書ける」機能の裏側をご紹介 
 3 Policy as Code function allows_ssh_public_access(rule: FirewallRule): boolean { const SSH_PORT = 22; return rule.direction === "INGRESS" && rule.sourceRanges.some(range => range === "0.0.0.0/0") && rule.allowed.some(({ ipProtocol, ports }) => (ipProtocol === "all" || ipProtocol === "tcp" || ipProtocol === "sctp") && ports.some(({ from_, to }) => from_ <= SSH_PORT && SSH_PORT <= to) ) }
  3. Deno で作る快適な “as Code” プラットフォーム セキュリティ SaaS「Shisho Cloud」における 
 「クラウドの設定値検査ルールを

    TypeScript で書ける」機能の裏側をご紹介 4 function allows_ssh_public_access(rule: FirewallRule): boolean { const SSH_PORT = 22; return rule.direction === "INGRESS" && rule.sourceRanges.some(range => range === "0.0.0.0/0") && rule.allowed.some(({ ipProtocol, ports }) => (ipProtocol === "all" || ipProtocol === "tcp" || ipProtocol === "sctp") && ports.some(({ from_, to }) => from_ <= SSH_PORT && SSH_PORT <= to) ) }
  4. const ok = !is_publicly_exposed(bucket) 
 Shisho Cloud なら、検査コードのカスタマイズでアラートの過不⾜を解決 9 公開バケットの社内ルール:

    バケット名を “public-XXX” にする || bucket.name.startsWith("public-") 真に対応すべきアラートに 
 集中できる ❗
  5. セキュリティ検査ルールの素朴な “as Code” for (const project of googleCloudProjects) { const

    client = new Storage({ credentials: /* ... */, }) const [buckets] = await client.getBuckets() for (const bucket of buckets) { const is_publicly_exposed = bucket.acl.some(rule => /* ... */) if (is_publicly_exposed) { await slack.chat.postMessage({ text: `${bucket.name} is public` // ... 1 1 認証 設定値取得 合否判定 通知 コードを定期実⾏するインフラ クラウドや Slack の認証情報管理
  6. セキュリティ検査ルールのスマートな “as Code” — 1 2 query { googleCloud {

    projects { cloudStorage { buckets { name acl { entity role } 「どんな情報を検査するか」 
 as GraphQL query 実⾏インフラ‧認証情報管理などは 
 Shisho Cloud にお任せ export default (data: QueryResponse) => data.googleCloud .projects ./* ... */ .buckets .map(bucket => ({ ok: !bucket.acl.some(rule => /* ... */) })) 「どのように検査するか」 
 as TypeScript code ❗ 宣⾔的なコード化
  7. 検査結果 Shisho Cloud における⾃動検査の全体像 1 3 引数: GraphQLの結果 返り値: OK/NG

    判定 GraphQL 
 サーバー TS 実⾏基盤 通知システム ①設定値取得 ②TS コード実⾏ ③アラート通知 設定値 データ
  8. 「クラウドの設定値検査ルールを TypeScript で書ける」 • セキュリティ検査ルールを as Code すれば、柔軟なカスタマイズが可能に • Shisho

    Cloud では、検査ルールを 
 GraphQL クエリと TypeScript コードでコード化 → 柔軟性は保ちつつ、宣⾔的で簡潔に書ける 1 4
  9. 検査結果 Shisho Cloud における⾃動検査の全体像 1 6 引数: GraphQLの結果 返り値: OK/NG

    判定 GraphQL 
 サーバー TS 実⾏基盤 通知システム ①設定値取得 ②TS コード実⾏ ③アラート通知 設定値 データ Shisho Cloud サーバーサイド
  10. 検査ルール as Code を⽀えるシステム 1 7 ユーザーのローカル環境 GraphQL クエリ まとめて

    
 アップロード } TS 型定義 TS 検査ロジック 依存モジュール 型定義 
 コード⽣成 bundler
  11. 検査ルール as Code を⽀えるシステム • クラウド設定値を取得する GraphQL サーバー • TS

    コードを実⾏する TS 実⾏基盤 • GraphQL クエリ→ TS 型定義コード⽣成器 • アップロード時に依存モジュールをまとめる bundler 1 8
  12. セキュリティ検査を、宣⾔的に書けるコンポーネントに分解 2 0 query { googleCloud { projects { cloudStorage

    { buckets { name acl { entity role } 「どんな情報を検査するか」 
 as GraphQL query export default (data: QueryResponse) => data.googleCloud .projects ./* ... */ .buckets .map(bucket => ({ ok: !bucket.acl.some(rule => /* ... */) })) 「どのように検査するか」 
 as TypeScript code
  13. 型システムの恩恵: 考慮漏れのない検査ロジック 「どのような設定値がありうるか」が GraphQL スキーマからわかる 2 1 enum GoogleCloudStorageBucketACLRole {

    OWNER READER WRITER } 例:「任意のリクエスト元が書き込み可能」 
   な GCS バケットを禁⽌したい ❗ WRITER だけでなく 
 OWNER の ACL もチェックすべき
  14. TS の魅⼒ — もう⼀つのポリシー記述⾔語 Rego と⽐べて • 多くのエンジニアがすでに慣れている • 「どのように検査したいか」

    
 考えをスムーズにコード化できる • 便利な型推論‧型検査 2 3 is_exposed_publicly(acl) { rule := acl[_] rule.entity == "allUsers" } else { rule := acl[_] rule.entity == "allAuthenticatedUsers" } else = false Rego のコード例
  15. TS の魅⼒ — 他の汎⽤プログラミング⾔語と⽐べて ネストしたオブジェクトの型定義が⾒やすい 2 4 export type Input

    = { googleCloud: { projects: Array<{ cloudStorage: { buckets: Array<{ name: string, acl: Array<{ entity: string, role: | "OWNER" | "READER" | "WRITER", }>, type Input struct { GoogleCloud GoogleCloud } type GoogleCloud struct { Projects []GoogleCloudProject } type GoogleCloudProject struct { CloudStorage GoogleCloudProjectCloudStorage } type GoogleCloudProjectCloudStorage struct { Buckets []GoogleCloudProjectCloudStorageBuckets } type GoogleCloudProjectCloudStorageBuckets struct { Name string ACL []GoogleCloudProjectCloudStorageBucketsACL IamPolicy GoogleCloudProjectCloudStorageBucketsIamPolicy } type GoogleCloudProjectCloudStorageBucketsACL struct { Entity string Role GoogleCloudProjectCloudStorageBucketsACLRole } type GoogleCloudProjectCloudStorageBucketsACLRole string Go
  16. TS の魅⼒ — 他の汎⽤プログラミング⾔語と⽐べて null ハンドリングを簡潔に書ける 2 5 const x

    = data?.method?.(0)?.field ?? false x := false if data != nil && data.Method != nil { res := (*data.Method)(0) if res != nil { x = res.Field } } Go
  17. GraphQL + TS の魅⼒ — コード実⾏基盤の提供者視点から V 8 で信頼できない JS

    を軽量に実⾏ …… ⻑くて数秒程度のコード実⾏に適する cf. Cloud fl are Workers (workerd) もコンテナ‧仮想マシンではなく 
  V 8 の隔離機構を⽤いてコード実⾏ 
  →起動オーバーヘッド‧使⽤リソースの削減 2 6
  18. GraphQL + TS の魅⼒ — コード実⾏基盤の提供者視点から 2 7 素朴な as

    Code → 外界とのやり取り → attack surface が多い 外界との能動的やり取りがない GraphQL 
 の結果 検査結果 → 関数引数 返り値 データ加⼯のみ 😈 😶
  19. 検査ルール as Code を⽀えるシステム (再掲) • クラウド設定値を取得する GraphQL サーバー •

    TS コードを実⾏する TS 実⾏基盤 • GraphQL クエリ→ TS 型定義コード⽣成器 • アップロード時に依存モジュールをまとめる bundler 2 9
  20. TS 実⾏基盤 Deno ベースの Rust 製サーバー 3 0 Deno CLI

    deno_core deno_ast deno_fs … … deno_core deno_ast TS 実⾏ 基盤 Rust ライブラリ
  21. なぜ Deno ベースなのか • セキュリティを意識した設計 • Deno CLI を⼊れるだけで TS

    対応‧test‧linter‧formatter など⼀式揃う • Rust ライブラリを使って実装できる • コピーをなるべく避けつつも、メモリバグを⽣みにくい 3 1
  22. Deno 以外の⾯⽩そうなランタイム • workerd (Cloud fl are Workers) • V

    8 Isolate による隔離実⾏ • LLRT (AWS Lambda) • JIT のない軽量エンジン QuickJS を採⽤し、オーバーヘッドやシステムの複雑性を 回避 • WinterJS • WASM 上で JS ランタイムが動く 3 2
  23. $ deno run をそのまま TS 実⾏基盤に使うと実は脆弱 3 3 import *

    as remoteData from 'https://metadata.internal/token.json'; import * as localFile from './foo.json'; export default function() { return { remoteData, localFile }; } 😈 import ⽂で内部ネットワーク‧ローカルファイルを盗み出せる
  24. Shisho Cloud の TS 実⾏基盤と $ deno run との主な違い 機能を削ぎ落とし、検査ルール実⾏に特化したシンプルな実⾏基盤へ

    • Ops (外界とのやり取り⼿段) のほとんどを外す • ファイルアクセスや fetch など不可 • module loader はメモリ上のモジュール群のみからロードする • import を悪⽤したファイル‧ネットワークアクセスを回避 Deno の Rust ライブラリは割と疎結合で、機能を削ぎ落としやすい 3 4
  25. import のあるコードの扱い 3 5 // main.ts import { foo }

    from './local_module.ts' import { bar } from ‘https://remote.example/module.ts' export default function(data: QueryResult) { // ... } main.ts を Shisho Cloud 上で実⾏するには、 
 依存モジュール local_module.ts のアップロードも必要 → 全ての依存モジュールを bundler でまとめてアップロード
  26. bundler ⾃作のこだわり: あえて単⼀ JS ファイルにまとめない esbuild のように単⼀の JS ファイルにまとめるのではなく、 


    全ての依存モジュールをそのまま Map<URL, SourceCode> にした YAML ⽣成 … Deno 内部ライブラリを⽤いて⾃社開発 3 6 modules: file:///main.ts: | import { foo } from './local_module.ts' import { bar } from 'https://remote.example/m export default function(data: QueryResult) { // ... } file:///local_module.ts: | export function foo() {/* ... */}
  27. bundler ⾃作のこだわり: あえて単⼀ JS ファイルにまとめない — 理由 Shisho Cloud の

    Web 画⾯上で、検査コードを軽く確認‧少し書き換える際に 
 読み書きしやすいコードであるべき 3 7 modules: file:///main.ts: | import { foo } from './local_module.ts' import { bar } from 'https://remote.example/m export default function(data: QueryResult) { // ... } file:///local_module.ts: | export function foo() {/* ... */} function foo() { ... } function bar() { ... } function main_default(data) { ... } export { main_default as default }; 型やコメントが消える 従来の bundler bundler for Shisho Cloud 書いたコードをそのままアップ
  28. TS 実⾏基盤‧bundler づくりの悩みどころ • rusty_v 8 (V 8 の Rust

    バインディング) のドキュメントもっと充実してほしい • Deno の Rust ライブラリはまだ安定せず、アプデでたまに破壊的変更 • 使いたい機能が Deno CLI に密結合している場合もある • MIT ライセンスなのでコピペしても良いが、メンテが⼤変 … Deno がまだ若い技術ゆえのペイン 3 8
  29. GraphQL → TS 型定義⽣成器 Rust 向け型定義⽣成器 graphql_client を 
 fork

    して TS 向けに改変 …将来の多⾔語対応を⾒据え、 
  複数⾔語の型定義を⽣成できる 
  単⼀の Rust 製ツールを⽬指す 3 9 export type Input = { /** All data from Google Cloud integr googleCloud: { /** Projects in Google Cloud */ projects: Array<{ /** Data on Google Cloud Storage cloudStorage: { /** All Google Cloud Storage bu buckets: Array<{ /** The bucket name */ name: string, /** The list of access contro acl: Array<{ entity: string, role: | "OWNER" | "READER" | "WRITER", }>, /** The IAM policy of the Goo iamPolicy: { 型定義 
 コード⽣成 Rego 他⾔語
  30. 型定義⽣成のこだわり: 読みやすさ第⼀ 4 0 export type Input = { /**

    All data from Google Cloud integr googleCloud: { /** Projects in Google Cloud */ projects: Array<{ /** Data on Google Cloud Storage cloudStorage: { /** All Google Cloud Storage bu buckets: Array<{ /** The bucket name */ name: string, /** The list of access contro acl: Array<{ entity: string, role: | "OWNER" | "READER" | "WRITER", }>, /** The IAM policy of the Goo iamPolicy: { • 単⼀の⼤きなネストした型定義を⽣成 • ⼊⼒データの全体構造が⼀⽬でわかる • 型だけでなく doc comment も⽣成 • GraphQL クエリ‧スキーマを⾒返す必要なし
  31. 型定義⽣成のこだわり: 読みやすさ第⼀ 4 1 export type Input = { /**

    All data from Google Cloud integr googleCloud: { /** Projects in Google Cloud */ projects: Array<{ /** Data on Google Cloud Storage cloudStorage: { /** All Google Cloud Storage bu buckets: Array<{ /** The bucket name */ name: string, /** The list of access contro acl: Array<{ entity: string, role: | "OWNER" | "READER" | "WRITER", }>, /** The IAM policy of the Goo iamPolicy: { • 情報後出しを避け、上から下に読めるように • T[] ではなく Array<T> • T | null ではなく Nullable<T>
  32. まとめ • Shisho Cloud ではセキュリティ検査を GraphQL + TS でコード化 •

    ユーザーは検査ルールを柔軟に‧快適にカスタマイズできる • as Code 基盤のために、いろいろ⾃社開発した • ⾃社開発なら、細部にまでこだわれる • JS/TS の深い領域に携われて楽しい! 4 3 まだまだ語り⾜りないので 
 懇親会でぜひお話ししましょう!