こんにちは。セキュリティチームでソフトウェアエンジニアをしてる@sota1235です。
この記事は10Xアドベントカレンダーの13日目の記事です。
昨日の記事はSuzuki Ryotaさんのお届けチームでオーナーシップを持っていくぞでした!
今回はGitHubの監査ログを定期的にexportし、保存する仕組みを作った話をします。
監査ログとは
監査ログとは、各種サービスやシステム等で発生したイベント等を時系列で記録したものです。
その名の通り、用途としては定期的な監査や有事の際の証跡として利用されるものです。
セキュリティ対策において、監査ログを取得することで以下の2点が実現できます。
- 監査ログを定期的に分析することで異常を検知することができる
- SIEMなんかの活用がよくある話かなと思います
- 有事の際の証跡として利用できる
- 監査ログがないとある事実があったのかどうかを客観的に評価することができなくなります
GitHubの監査ログ
世の中の企業向けサービスの多くは監査ログを提供しています。また、提供していなくても内部的には保持してることもあります。
GitHubも監査ログを提供しており、OrganizationのOwnerであればアクセスすることができます。
10Xでは開発やCI/CDにGitHubを活用しており、ソースコードはもちろん本番環境へのデプロイやデータへのアクセス等、多くのことができるようになっているためセキュリティ観点では最優先で守るべき領域の1つになっています。
そもそも何も起きないよう、さまざまな予防策も進めていますが一方で万が一何かあった際に検知や後追いでの調査を行うために監査ログを適切に管理する必要があると考えました。
GitHubの監査ログは永久には保存されない
GitHubに限らずですが、監査ログは必ずしも永久的に保存されるわけではありません。
GitHubの場合はGitイベントは7日間、そのほかのイベントは180日間保存され、それ以降のログにはアクセスできなくなります。
今後もGitHubの利用を拡大していくことを見込むと、180日間はともかくGitイベントが7日分しか保持されないのではセキュリティ対策に活用できる情報が減ってしまうため、10Xでは監査ログを定期的にexportし、自社で保管しておくことにしました。
監査ログの出力方法
GitHubの監査ログの出力方法は主に3つです。
- GUIからCSVをダウンロードする
- APIからアクセスし、取得する
- ログのストリーミングを設定する
基本的には3つ目の方法が一番コスパ良く監査ログのexportが実現できます。
しかしこの方法はEnterprise Cloudプランでないと利用することができず、10Xの場合は利用することができませんでした。
この記事の本題
というわけで、今回は「GitHubで、Enterprise Cloudプランじゃない場合に監査ログをいい感じにexportして保存する方法」を紹介していきたいと思います。
監査ログ出力の仕組み
ざっくり要件
以下を要件として、実装検討を進めました。
- ログはREST APIを通じて取得する
- ログの保存場所はBigQuery dataset
- ログのexport、保存処理はGitHub Actions上で行う
- 差分更新の仕組みを作る
技術選定
ログの保存場所
10XではGCPを利用してるため、主な候補としてはGoogle Cloud Storage(GCS)かBigQueryです。
保存や価格面を考えるとGCSもいい選択肢ですが、以下の理由からBigQueryを選択しました。
- 必要になった際にすぐに分析ができる
取得済みの最新のログ
をQueryで簡単に取得できる- 後述するログ取得時のロジックで必要になります
BigQueryにする以上はschemaをどうするか考える必要がありますが、その辺りは追って解説します。
ログの取得処理
今回はログの保存を自動化したいので前述した手段の2つ目であるAPIでの取得を行います。
調べるとGitHubが出してるNode.js製のCLIがあるんですが、今回は以下の理由から導入を見送りました。
- 動作するが、メンテナンスされていない
- コード上での呼び出しを想定して作られていない、ログの取得順序(古いのを取るか、新しいのを取るか)の制御ができない
実装を見るとわかるのですがやってることはREST APIを叩いてるだけなので一部コードは参考にしつつ自前でNode.jsで実装することにしました。
実装に際しては@octokit/restを利用してます。
ログの取得・保存処理はGitHub Actionsで行う
ログの取得・保存処理の実行は環境準備の手間が少ないGitHub Actionsで行います。
scheduleを利用することで定期実行が簡単に実装可能であり、他の選択肢(例えばCloud Run等)と比べても利点が多いと判断したためです。
考えるべき懸念としては実行されるworkflowが配置されるGitHub Repositoryへの権限管理ですが、10Xではセキュリティチームのみが権限を持つ運用のためのRepositoryがあります。
そこでログの取得・保存処理を実装することで故意過失に関わらず、セキュリティチーム以外がworkflowを改変したり、ログを取得するための認証情報を取得できないようにします。
長期的に運用していくことで課題が出てくるかもしれませんが、初期的な実装としてはこれで十分だと考えています。
全体像
仕組みのざっくりとした全体図が以下です。
この仕組みはhourlyで動作させます。
今回、10Xで実装した際には問題になりませんでしたがGitHub APIにはrate limitがあるので監査ログの量によっては同じ仕組みで回らないかもしれません。
図にあるAudit log
は監査ログ
と読み替えてください。
順に説明します。
1. BigQueryに最新データを取得しに行く
差分更新を行うため、まずは保存してある最新のログを確認します。
REST APIではbefore
パラメータを指定することで指定したパラメータよりも新しいデータを取得することができます。
このbefore
だと新しいデータを、after
だと古いデータを取りに行くのは少し直感に反していて混乱しやすいので注意です(時系列データの性質を考えれば納得できます)。
2. 監査ログを取得する
1のステップで取得したログをもとに、それ以降 or それ以前のログを取得します。
監査ログはAPI経由で取得しますが、その際の認証情報にはあらかじめ必要な権限を付与したGitHub Appsを利用して期限付きAccess Tokenを発行します。
後述しますが、ここで使用するGitHub Appsへアクセスするためのprivate keyの管理は厳重に行う必要があります。
実装面に関しては、基本的には存在するデータを取り切るまでループでAPIにアクセスするだけなので特筆すべきことはありませんが、いくつかポイントを挙げます。
- optionalなパラメータのdefault値はきちんと変えておく
- 取得件数は最大の100にする
include
をall
にし、Gitイベントも取得するようにする
cursor
は最新レコードの@timestamp
,_document_id
から生成する- 地味にundocumentedな仕様ですが、cursorは
${@timestamp}|${_document_id}
をbase64エンコードしたものを利用します - この仕様はghec-audit-log-cliの実装を読むことで知ることができました
- 地味にundocumentedな仕様ですが、cursorは
- yieldを利用して100件取得 → 100件BigQueryに保存するようにしてメモリを使いすぎないようにする
- ループで取る時はレスポンスに含まれるLinkヘッダーを解釈して次ページがあるかどうかを確認する
最後のLinkヘッダーのparseに関してはparse-link-headerというメジャーなnpm libraryがあるのですが保守されておらずTypeScript対応もされてなかったのでcloneした上でTypeScript対応するよう変更したものを利用してます。
3. 監査ログを保存する
基本的には公式のnpm libraryを利用してBigQueryのあらかじめ用意したdatasetに送るだけです。
その際、考えなければいけないのはデータのschemaです。
GitHubの監査ログはJSON形式で提供されます。そのpayloadはイベントによって異なるため、それらを吸収できるschemaを定義する必要があります。
JSON形式のカラムは便利な一方、Queryを書く際に複雑になってしまうため、今回は検索性をなるべく損なわないよう共通カラムはそれぞれカラムを用意し、イベント毎に異なりうるカラムはJSON形式のカラムに入れることにしました。
これを実現するにあたって必要なのは全イベントに共通して存在するカラムの特定
ですが、公式のドキュメントではその情報が見つけられなかったので今回は最新のログ5000件程度を取得し、すべてのログに存在するfieldを調べて設定しています。
もしかしたら今後、そのカラムに対して破壊的変更が入る可能性はありますがデータの性質とGitHubへの信頼からその可能性は低く見積もっています。
また、もし発生した際はimport scriptがコケるためすぐに気づいて対応することができます。
権限管理
監査ログを自動で取得する際に考えなければならないこととして、監査ログデータへの権限管理をどうすべきかということです。
今回の実装を行うことで新たに管理すべき資産として以下があり、これらに対しての権限をどうすべきかを目指したい状態から逆算して考える必要があります。
- API経由で監査ログを取得する際に利用するAccess Tokenを生成するためのGitHub Appsのprivate key
- ログの取得、保存処理が行われる実行されるGitHub Repository
- BigQueryのdatasetに保存された監査ログ
監査ログに含まれるデータについて考える
監査ログのデータには実際のソースコードやアプリケーションのデータ等が含まれてるわけではありません。
一方でGitHub Organization内のすべてのactivityをBot等も含めて記録されています。
このログにはアクセス元IPアドレス等も含まれており、社員自身の一種の行動ログとして捉えることができるため、社内メンバーであってもいつでも自由に閲覧すべきデータではないと考えました。
また、通常の業務時には利用する必要もないため10Xでは通常の業務時にはセキュリティチームも含めて閲覧できないようにする状態にすることにしました。
具体的にどこに制限をつけるか
基本的には先ほど挙げた各資産に対して、必要最小限のメンバーのみがアクセスできるようにしました。
- API経由で監査ログを取得する際に利用するAccess Tokenを生成するためのGitHub Appsのprivate key
- GitHub Owner、もしくはセキュリティチームのみ
- ログの取得、保存処理が行われる実行されるGitHub Repository
- セキュリティチームのみ
- ※ Ownerもその性質上、権限を持つ
- BigQueryのdatasetに保存された監査ログ
- 通常時は誰もデータを閲覧できない
GitHubのOwnerに関してはかなり強い権限を持っており、監査ログのみ閲覧を絞るというような制御はできないためそこは許容しています。
また、datasetに保存されたデータは通常時は見ることができませんが必要な際はterraformを通じた権限付与をセキュリティチームがapproveすることで閲覧が可能です。
今後の活用方法
これでひとまずGitHubの監査ログを欠損することなく継続的に保存する仕組みができています。
大事なのはこれを今後、どう活用していくかということですが10Xでは以下を検討しています。
- 有事の際の証跡として利用
- 今回の取り組みで達成
- 定期的な監査ログのチェックによる異常検出
- 自前実装、サービスの利用等選択肢は多い
- SIEM等によるリアルタイム検知の整備
- 対応するための体制構築もセットで必要
理想論としては全部やれたらいいのですがリソースも時間も限られているのでいつ、2点目3点目に踏み込んでいくべきかは見定める必要があると思っています。
この辺りにどう踏み込むかはUbieのmizutani-sanが直近にポストした記事がとても共感できる考え方だったので興味がある方は読んでみてください。
最後に
今回は監査ログを取得し、保存する、という話をしました。
大きな組織、セキュリティに適切に投資する組織からすればやって当たり前、というトピックかもしれませんが10Xではこのような下地を整えるための課題がまだ多くあります。
今回の取り組みは限られたリソースと時間の中で全てを掴み取れない中で、それでも優先すべきと判断してやり切ったものです。
事業や組織がこれから成長していく、という中でセキュリティにおける下地を整えつつ先手を打つことで安心・安全な事業を届けられる組織を作っていきたいというのがセキュリティチームの願いです。
この願いや取り組みに共感する方がいれば下記の職種を募集しているのでぜひ募集要項を覗いてみてください。
セキュリティエンジニア(Product Security) / 株式会社10X
明日は
明日のアドベントカレンダーはtenjinbayashiさんの品質と約束、そして夫婦の家事分担の話です!
お楽しみに!