はじめに #
『GitHub』への不正アクセス発生に関するお知らせとお詫び(第一報)
これを見た時、「自分の GitHub のアカウントで最悪どこまで実行可能なのか」というところと、じゃあそれを防げますか?というのが気になった。
フィッシングとかを省くと 2 種類の PAT のどちらかが盗られたのだと思うんですが、どちらの PAT であっても、自分の認証情報を使ってアクセスできるリポジトリにアクセスできてしまうのは防ぎようがない。 なので「リポジトリには最悪漏れてもいい情報だけ含めよう」が基本になるはず。
とは言いつつも、リポジトリが漏れること自体を仕組みでなるべく防ぐとしたらこういう方法がありそう。
- 業務用のリポジトリはちゃんと SSO が必須な organization の中に作成して、SSO を通していない Classic PAT などでアクセスできないようにする。(個人用のアカウントを業務利用もしている場合)
- そもそも会社として PAT を許可しない。(これはちょっと難しいかな……)
- なるべく PAT は使わない。使わないといけない時は出来る限り Fine-grained PAT を使ったり、有効期間を短く設定したりする。
- internal repo を多用せず、必要な人間にだけ権限を付与する(個人的にはこれめっちゃ嫌)
個人的には最後のやつは開発効率だったり、知見みたいなところが広がりづらくなったりするので賛成したくない。 gitleaks などのツールを導入して、誤ってシークレットなどをリポジトリへ含めないようにしておくのが丸いと思います。
攻撃の想定 #
攻撃者が私のアカウントでリポジトリへアクセスできる状態で、本番環境のデータを取ったり、シークレットを抜いたり出来ないかを考える。
パッと思いつくところだと、「サーバーのリポジトリから CI 経由で脆弱性を含んだサーバーアプリケーションを本番環境へデプロイする」や「インフラのリポジトリから CI 経由で terraform apply を実行する」が可能だと攻撃が成功しそう。これらを防ぐ方法を考えてみる。
1. キーなどを GHA Secrets に置かない #
想定では攻撃者がリポジトリにプッシュできる状態であるので、新しい GHA ワークフローを作ることも、起動させることも可能です。この状況ではシークレットはシークレットではなくなるので、そもそも使うのを避けた方が良いですね。
AWS などのクラウドへのアクセス権の取得には当然ですが OIDC を使う。
まぁ、流石にみんなやってると思う。AWS Action が OIDC を推奨になってからもう数年経ってるんじゃないかな。
調べてみたら非推奨の警告が出たのが2023年10月だったので、2年半くらい
ただし、OIDC に変えただけでは「どのリポジトリからでも assume role できる」状態なので、次の対策が必要。
2. OIDC の trust policy で制限をかける #
OIDC による assume role を実行するとき、ロール側に条件を設定できる。AWS だとこういうやつ。
例: YOUR_ORG/YOUR_REPO で AWS 向けに発行された id-token を許可する条件
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/YOUR_REPO",
}
}
}この token.actions.githubusercontent.com は GitHub が生成した id-token なので、そこに含まれている情報を使ってより細かい制限をかけることが可能。
主要なクレーム:
| クレーム | 内容 |
|---|---|
sub |
owner, repo, environment |
ref |
ブランチ, タグ |
actor |
ワークフローをトリガーしたユーザー |
他にも結構多くの情報が含まれてた。
詳しくは GitHub 公式ドキュメント を参照。
sub に GitHub Environment の環境が入るの初知りだった。これが使えるなら結構安全に取り扱えそう。
3. GitHub Environments と OIDC の trust policy の組み合わせで保護をかける #
2で他のリポジトリからの assume role を防げるようになったが、相変わらず攻撃者がリポジトリにプッシュできる前提を置いているので、次は同じリポジトリの保護されていないブランチからの起動を防ぐ必要がある。
方法はいくつか思いついたのですが、それぞれ運用の大変さが違うので assume role する権限の強さによって変えるのが良いと思う。
| 方法 | メリット | デメリット |
|---|---|---|
trust policy で StringEquals ref を使い、指定したブランチやタグだけで assume role できるようにする |
シンプル | 指定した1つの ref でしか実行できない |
上記と同じだが、StringEquals の代わりに prefix で判定する |
攻撃者は prefix の条件が分からないため少し防げる | やはり制限が強い |
| GitHub Environments のデプロイメントブランチとタグを使い、指定したブランチやタグだけで assume role できるようにする | StringEquals ref より複数の ref を指定しやすい |
— |
| 上記と同じだが、デプロイメントブランチとタグの指定にワイルドカードを使う | 攻撃者はワイルドカードの条件が分からないため少し防げる | — |
| GitHub Environments の必須のレビュー担当者を使い、自分以外の承認を必須とする | 確実性が高い | 承認を貰う手間が発生する |
個人的な感覚だと、本番環境へのイメージのプッシュやタスク定義の生成までは特に気にしないで、最後のタスクの入れ替えは必須のレビュー担当者で厳しく制限するのが良さそう。
4. CI/CD の内容ごとにロールを分ける #
例えば、
- Terraform plan と apply
- ECS タスク定義の作成とデプロイ
これらに同じロールを使ったり、複数のワークフローでロールを使い回したりすると脆弱になる。 特に Terraform は apply で非常に強い権限を使うし、逆に plan はプルリクエストのタイミングなど割と幅広く実行したいですし。
ロールを分けておくことで、それぞれ3のどの方法を使って保護するのか・保護するべきなのかが選べるようになるので良い。
最小権限の原則だったかな? やっぱりサボっちゃダメってことで。
まとめ #
GitHub の認証情報が漏れた場合を想定して、本番環境を守るための設定を考えた。
- キーなどを GHA Secrets に置かない — リポジトリが取られた時点で Secrets も漏れるので、最初から置かない
- OIDC の trust policy で制限をかける —
subやrefを使って、どのリポジトリ・ブランチ・環境から assume role できるかを制限する - GitHub Environments と OIDC の trust policy の組み合わせで保護をかける — デプロイメントブランチの制限、必須レビュー担当者の設定などで、1 人で本番デプロイを実行できないようにする
- CI/CD の内容ごとにロールを分ける — 最小権限の原則には大人しく従う
結局のところ、「通常の運用であっても1人で本番環境を変更できないようにする」ことが大切そう。