GitHub Actionsにおける脅威と対策まとめ
はじめに
こんにちは、サイボウズ24卒の@yuasaです。
サイボウズでは開発・運用系チームに所属する予定の新卒社員が研修の一環として、2週間を1タームとして3チームの体験に行きます。新卒社員の私が生産性向上チームの体験に行った際に、チーム内でGitHub Actionsを利用する際の脅威と対策について調査を行い、ドキュメント化した上で社内への共有を行いました。本記事では、そのドキュメントの一部を公開します。
対象読者
本記事の主な対象読者としては、以下のような方を想定しています。
- GitHub Actionsを組織で利用しているが、特にセキュリティ対策を実施していない方
- GitHub Actionsを組織で利用しており、部分的にセキュリティ対策を実施しているが、対策が十分かどうか分からない方
本記事がGitHub Actionsのセキュリティ対策を検討する上で参考になれば幸いです。
本記事の概要
本記事は、「CI/CDを構成する要素」と「個別の脅威」と対策の2部構成からなります。
- 「CI/CDを構成する要素」では、CI/CDパイプラインの要素について説明します。
- 「個別の脅威と対策」では、CI/CDの脅威マトリクスを用いて起こりうる脅威を分類し、その後個別に脅威と対策を説明します。
CI/CDを構成する要素
CI/CDの構成要素
GitHub ActionsはCI/CDのプラットフォームですが、そもそもCI/CDの構成要素としては以下のものがあります。「デバイス」に関する脅威は多岐にわたるため本記事では対象外とします。
https://github.com/rung/threat-matrix-cicd より引用
名前 | ツール |
---|---|
デバイス | Mac/Windows/クラウドベース |
Gitリポジトリサービス | GitHub, GitLab |
CI | CI/CDサービス(CircleCI, Cloud Build, Codebuild, GitHub Actions など) |
CD | CI/CDサービス(CircleCI, Cloud Build, Codebuild, GitHub Actions など) CDサービス(Spinnaker, ArgoCD など) |
シークレット管理 | シークレット管理サービス (AWS Secret Manager, GCP Secret Manager, HashiCorp Vault など) |
本番環境 | クラウドサービス(AWS, Google Cloud, Microsoft Azure) その他のリソース(Container Registry, Linux Server, Kubernetes) |
GitHub Actionsの概要
GitHub Actionsはビルド、テスト、デプロイを自動化できるCI/CDプラットフォームです(参考)。
リポジトリに対する全てのpull requestをビルドしてテスト、マージされたpull requestを運用環境にデプロイしたりするワークフローを作成できます。
また、ワークフローの実行にはGitHubが用意した仮想マシン(Linux、Windows、macOS)が使用できます。そして、自社のオンプレ環境やクラウド環境で独自のセルフホストランナーを用いて実行することも可能です。
加えて、サードパーティランナーの利用も可能(AWS CodeBuild、Namespaceなど)です。サードパーティランナーとは、セルフホストランナーの仕組みを使ってランナーを提供するプロバイダです。(参考)
GitHub Actionsの構成要素
GitHub Actionsの構成要素について以下でまとめます(参考)。
- ワークフロー
- 1つ以上のジョブを実行する構成可能な自動化プロセス
- リポジトリ内のイベントによってトリガーされた時に実行される
- 手動やスケジュール、リポジトリ外部のイベントによるトリガーも可能
- ワークフローは
.github/workflows
ディレクトリで定義される - リポジトリには複数のワークフローを含めることができる
- ワークフローの例:
- pull requestの作成をトリガーとしてビルドとテストを実行
- releaseがpublishされたことをトリガーにしてアプリケーションをデプロイ
- イベント
- ワークフロー実行をトリガーするリポジトリ内の特定アクティビティ
- 例:
- pull request が作成された時
- リポジトリにコミットがpushされた時
- ワークフローをトリガーするイベントの一覧([参考](https://docs.github.com/ja/enterprise-cloud@latest/actions/using-workflows/events-that-trigger-workflows))
- ジョブ
- 同じランナーで実行されるワークフロー内の一連のステップ
- 各ステップは実行されるシェルスクリプトか実行されるアクション
- デフォルトではジョブに依存関係はなく並列で実行される
- ジョブと他のジョブの依存関係を構成できる
- ジョブAがジョブBに依存する場合、ジョブAはジョブBの完了後に実行される
- ジョブの詳細(参考)
- アクション
- ジョブを作成し、ワークフローをカスタマイズするために組み合わせが可能な個々のタスク
- 繰り返し記述されるコードの量を削減できる
- アクションの例
- actions/checkout: リポジトリをチェックアウトするのに使用できるアクション
- actions/setup-node: ワークフロー内で特定バージョンのNode.jsをダウンロードするのに使用できるアクション
- アクションの詳細(参考)
- ランナー
- ワークフローがトリガーされるとワークフローを実行するサーバ
- 各ランナーでは一度に一つのジョブを実行できる
- ランナーにはGitHub-hosted runners(GitHubのランナー)と、Self-hosted runners(セルフホストランナー)の2種類がある
- GitHubのランナーはGitHubが用意した仮想マシンを用いてワークフローを実行する
- セルフホストランナーは独自に用意した環境を用いてワークフローを実行する(参考)
個別の脅威と対策
CI/CDの脅威マトリクス
CI/CDの脅威マトリクスはCI/CD環境での攻撃と対策について、ATT&CKに基づく脅威マトリクスにまとめたものです。
https://github.com/rung/threat-matrix-cicd より引用
横軸は戦術(Tactics)であり、攻撃者が攻撃手法(Technique)を実行する理由を示します。また、攻撃手法は戦術に紐づく形で示され、各攻撃手法に対応する対策(Mitigation)が示されます(参考)。
この脅威マトリクスを活用することで、多数存在する脅威とその対策を網羅的に整理することができます。また、GitHub ActionsなどのCI/CDサービスでの具体的な脅威を検討する際に、その脅威がCI/CDのどの箇所で生じるか、どの箇所に影響を与えるかといったことを整理するのに役立てることができます。
脅威
GitHub Actionsで生じる可能性のある脅威を脅威マトリクスに当てはめた図が以下です。ここではGitHubのランナーに加え、セルフホストランナーも利用している状況を考えます。
GitHub Actionsの脅威マトリクス
この脅威マトリクスでは、各攻撃プロセスごとに脅威が分類されている点が重要です。脅威のうち、攻撃手法を赤色、攻撃による影響を黄色で示しています。以下では、それぞれの脅威について説明します。
攻撃手法
GitHub Actionsにおける攻撃手法を説明します。各攻撃の成立条件としては、「完全なる外部の権限でネットワーク越しに攻撃可能」、「対象レポジトリを管理する組織に属する人間と同等の権限を持つ」の2つの場合を考えます。また、前提条件として、リポジトリはプライベートリポジトリとして運用されており、forkはなされないものとします。
インジェクション攻撃
攻撃者は対象のワークフローにインジェクションの脆弱性がある場合に、脆弱性を悪用することで悪意のあるコードをランナー上で実行することができます。この攻撃が成立するのは攻撃者が「対象レポジトリを管理する組織に属する人間と同等の権限を持つ」場合です。
例えば、攻撃者が制御できるパラメータ(event.comment.body
、github.event.pull_request.title
など)の値をシェルスクリプト内部の式 ${{ }}
に挿入する場合に、攻撃者は任意のコードを実行することができます。また、ワークフロー内で攻撃者が書き換えることのできるリポジトリ内のシェルスクリプトやテストコードを実行する場合にもコード実行が可能です。
シェルスクリプト内部の式にgithub.event.pull_request.title
が挿入される例
- name: Check PR title
run: |
title="${{ github.event.pull_request.title }}"
攻撃の影響として、「攻撃による影響」で示す機密情報の漏洩やリソースの不正利用が生じる可能性があります。
プルリクエストを介した攻撃
攻撃者はプルリクエスト契機に起動するトリガーを利用して攻撃を行うことがあります。この攻撃が成立するのは攻撃者が「対象レポジトリを管理する組織に属する人間と同等の権限を持つ」場合です。
プルリクエストを介した攻撃はトリガーを書き換えられるかどうかがポイントとなります。攻撃者が.github/workflow
以下を改ざん可能でない場合、以下のことが可能です。
- pull_requestイベントをトリガーとして、悪意のあるコードをランナー上で実行する。
- テストコードの改変によってジョブで実行されるコードを書き換え、悪意のあるコードをランナー上で実行する。
しかし、デプロイのトリガーを書き換えられないため、本番環境への影響は及びにくくなります。
一方、攻撃者が.github/workflow
以下を改ざん可能である場合、デプロイのワークフローを書き換えたり、新規にワークフローを作成することができ、本番環境にも影響を及ぼすことができます。
攻撃の影響として、「攻撃による影響」で示す機密情報の漏洩やリソースの不正利用が生じる可能性があります。
依存関係の悪用
攻撃者は対象ワークフローが使用する依存関係(アクション、パッケージ、バイナリなど)の一つをコントロールしている場合に、悪意のあるコードをランナー上で実行できます。この攻撃が成立するのは攻撃者が「完全なる外部の権限でネットワーク越しに攻撃可能」な場合です。
実例として、CI/CDで利用されるテストカバレッジ計測ツールであるCodecovにCI/CD環境の環境変数を窃取するコードが含められていたケースが過去にあります(参考)。
攻撃の影響として、「攻撃による影響」で示す機密情報の漏洩やリソースの不正利用が生じる可能性があります。
攻撃による影響
インジェクション攻撃、プルリクエストを介した攻撃、依存関係の悪用により、以下の影響が生じる可能性があります。
機密情報の漏洩
以下のような機密情報が漏洩する可能性があります。
GITHUB_TOKEN
- Actions Secret
- Deployment Key、アクセストークン
- ソースコード
- GitHubのIDプロバイダが発行したIDトークン(後述)
また、攻撃者はこれらの機密情報を用いた横展開を行う場合があります。例えば、GITHUB_TOKEN
に packages: write
権限がある場合、 パッケージを削除することができます。また、Actions Secretに含まれるクラウドサービスのシークレットキーやIDトークンを用いて、リソース(AWSであればSecret Manager、S3など)へのアクセスを行うことができます。そして、ソースコード内の脆弱性を用いてシステムを侵害するなどが考えられます。
リソースの不正利用
以下のようなリソースの不正利用が行われる可能性があります。
- ランナーの計算リソースの不正利用
- ワークフローの中断
セルフホストランナーにおいてランナーエスケープが可能な場合、攻撃者はランナーが動作する環境を用いた横展開を行う場合があります。ランナーエスケープはコンテナ上でワークフローが実行される場合にコンテナのbreakoutによって他のジョブに影響を与えられます。例えば、テストのジョブと本番環境へのデプロイを行うジョブが同じマシンの別コンテナで動いている場合、テストのジョブに侵入できるとランナーエスケープを通じて本番環境へのデプロイを行うジョブを停止することが可能になります。
対策
目的別にGitHub Actionsのセキュリティ対策をまとめます(参考)
インジェクションの防止
ワークフローファイルの静的チェックツールである actionlint の導入を検討します。
信頼されない式の入力値を中間環境変数に設定します。これにより式の値はスクリプトの生成に関与せず、メモリに保存される値として扱われるためコードのインジェクションは難しくなります(参考)。
- name: print title
env:
TITLE: ${{ github.event.issue.title }} # 中間環境変数としてTITLEを設定
run: echo "$TITLE"
ワークフローの改ざん・不正実行防止
push ruleset、CODEOWNERSの使用
push rulesetで.github/workflow
ディレクトリ以下に対するプッシュによる変更を防ぎます。
CODEOWNERS
ファイルで.github
ディレクトリ以下に対してCode Ownerを指定します。これにより、指定されたファイルへの変更には指定されたCode Ownerの承認が必須になります(参考)。
ワークフロートリガーの見直し
コードプッシュをトリガーにする場合、pull_request
か pull_request_target
イベントをトリガーにします。
pull_request
イベントを使用する場合、プルリクエストのfeature branchの最新コミットでワークフローを実行するため、対象レポジトリを管理する組織に属する人間と同等の権限を持つ攻撃者はプルリクエストによって任意のワークフローを実行することができます(参考)。
対して、pull_request_target
はプルリクエストのbase branchの最新コミットでワークフローを実行します。そのため、攻撃者はプルリクエストによってワークフローを改ざんすることができません(参考)。
ただし、pull_request_target
を設定していても、featureブランチのスクリプトなどをワークフローから実行する場合、そのスクリプトを改ざんすることで任意のコードが実行可能となります。また、pull_request_target
を使用する場合、context や環境変数が異なることにも注意が必要です。
加えて、ワークフローの変更がプルリクエストのマージまで反映されないため、事前のテストが難しいという点にも留意する必要があります。
シークレット漏洩防止・漏洩による被害の最小化
GITHUB_TOKEN
のPermissions見直し
デフォルトでGITHUB_TOKEN
が持つ権限はReadのみにします(参考)。
また、可能であればAllow GitHub Actions to create and approve pull requests
を無効にします。これにより、GITHUB_TOKEN を用いてプルリクエストの作成、承認を行えないようにできます(参考)。
Permissionsはできる限りジョブ単位で管理します。また、ジョブを分けることで強い権限で実行されるステップが少なくなるのであればジョブの分割を検討します(参考)。
Permissionsを見直して権限が最小になっていることを確認します(参考)。
Actions Secretの見直し
前提として、Actions Secretは任意のワークフローから読み取ることができます。Actions Secretを読み取ることが可能なワークフローに制限をかける場合、Environment Secrets + Rulesets、もしくはOIDCによる制限をかけてAWS Secret manager等にSecretsを格納する2つの方法を取ることができます。
前提を踏まえた上で、気をつけるべきこととして以下のことがあります。
- 使っていないシークレットは削除する。
- 構造化データ(JSON、XML、YAMLなど)をシークレットに設定しない。
- ワークフロー内で使用されるすべてのシークレット、ログ出力されるべきでない値をマスクする。
- マスクの目的は、ログの閲覧者がワークフローをトリガーする権限がない場合や、ログをコピペしてSecretが流出することを防ぐことです。ネットワーク越しにシークレットが送信されるのを防ぐためのものではありません。
- マスク方法
- 例:
::add-mask::{value}、echo "::add-mask::Mona The Octocat"
-
actions/toolkit
を利用する場合もtoolkitの@actions/coreパッケージを利用してマスクを設定- 例:
core.setSecret('Mona The Octocat')
- 参考: https://github.com/actions/toolkit/tree/main/packages/core#setting-a-secret
- 例:
- 例:
- シークレットを使用してワークフロー内で作成した機密値もシークレットとして登録する。
- 定期的にシークレットをローテーションする。古いシークレットは無効化し、シークレットにTTL(Time To Live)を設定する。
- ローテーションに合わせてシークレットの権限が最小になっていることを確認する。
IDトークンの利用
ワークフローにおいて、クラウドプロバイダ(AWS、GCPなど)を利用するケースがあります。例えば、ワークフローで生成したファイルをAWSのS3などのオブジェクトストレージにアップロードするケースなどがあります。このようなケースでは、通常トークンなどの認証情報はGitHubのシークレットとして保存し、ワークフロー実行のたびにクラウドプロバイダにシークレットを提供する方法を取ります。
しかし、これを行うためにはクラウドプロバイダ側で発行したシークレットをGitHub側で複製してハードコードする必要があります。また、属性情報を用いた柔軟なアクセス制御なども不可能であるため、シークレットが強い権限を持つことが多々あります。
そこで、OpenID Connect(OIDC)を利用することで、GitHubによる署名が行われたIDトークン(JWT)を取得することができます。このIDトークンには有効期限や属性情報を持たせることができ、JWTであるため署名値を検証することで改ざんを検知することができます。GitHub ActionsにおけるIDトークンの発行フローはOIDCの正規フローとは異なるため、OIDCのプロトコル自体をベースに理解しようとすると不自然に思う点があるかもしれません(RPからIdPへの認証リクエストがない点、UserAgentを介在しない点など)。
利用するには、まずクラウドプロバイダ側でクラウドロールとGithubワークフロー間でOIDC Trustを構築します。その後、ジョブが実行されるたびに、ワークフローはGithubのIDプロバイダからIDトークンを取得し、クラウドプロバイダからIDトークンと引き換えにアクセストークンを取得します。
クラウドプロバイダ側でOIDC Trustを設定する際にはIDトークンのペイロード(subやaudなど)をどのように検証するかを設定します(参考)。
GitHub Actions側の設定では、ジョブのPermissionsにid_token: write
を付与することでIDトークンを取得可能になります(参考)。
依存関係の悪用防止
3rd party action利用時のチェック
3rd party actionを利用する前にソースコードを精査します。主に以下の点を確認します。
- リポジトリとシークレットの内容を適切に処理していること
- シークレットの外部送信やログへの意図しない記録が行われていないこと
利用する際には、full length commit hash(SHA)を指定することで、アクションを不変のものとして扱います。uses: owner/action-name@26968a09c0ea4f3e233fdddbafd1166051a095f6 # v1.0.0
のようにバージョンもコメントに記載します。
full length commit hashが使用されているかをチェックするのにactionlint が利用可能です。また、full length commit hashの指定を自動化するためにpinact を利用することができます。
また、リポジトリ内で使用している3rd party actionsのactions permissionをセキュリティ観点で見直すことも重要です。可能であれば、Allow OWNER, and select non-OWNER, actions and reusable workflows
を設定することで、ホワイトリスト形式で使用可能な3rd party actionsを指定できます(参考)。
artifact attestationsの利用
artifact attestations(以下、attestation)を利用することで、ソフトウェア成果物(バイナリ、コンテナイメージなど)について改ざん不可能な証明を作成し、完全性を担保することが可能になります。
ただし、attestationが保証するものは「ソースコードとビルド手順ログの紐付け」だけであり、ソフトウェア成果物自体が安全であることを保証するものではないことに注意が必要です。
attestationを作成する際には、署名付きクレームに以下の情報を含めます。
- ソフトウェア成果物に関連するワークフローへのリンク
- ソフトウェア成果物のリポジトリ、organization、environment、commit SHA、トリガーイベント
- 出所の証明に使用されるOIDCのIDトークンにおけるその他の情報
attestationを作成するには以下を満たす必要があります(参考)。
-
id-token: write
,contents: read
,attestations: write
のPermissionsをジョブに与える - actions/attest-build-provenance を使用するステップをジョブに含める
また、関連するSBOM(Software Bill of Materials、ソフトウェア部品表)を含むattestationも作成することができます(参考)。ビルドに使用される依存関係のリストを関連付けることで透明性を担保することができます。
attestationは現在、普及していく段階にありますが、いずれはchecksumを検証するようにattestationの検証を行うことが一般的になっていくことが考えられます。
ツール
上記で紹介したツールをまとめます。
actionlint
actionlintはGitHub Actions ワークフローファイルの静的チェックツールです。
主に以下を項目をチェックします。
- ワークフローファイルのシンタックスチェック
-
${{}}
の強力な型チェック - スクリプトインジェクション
- シェルスクリプト、Pythonスクリプトの静的チェック
チェック項目の一覧はこちらに記載されています(参考)。
ghalint
ghalintはGitHub Actions ワークフローファイルがセキュリティポリシーに準拠しているかをチェックするツールです。
ワークフロー関連のチェック事項
- すべてのジョブが Permissions を持つ
-
read-all
が使われていない -
write-all
が使われていない -
secrets: inherit
が使われていない - ワークフローが環境変数にシークレットをセットしない
- ジョブが環境変数にシークレットをセットしない
- ジョブのコンテナイメージタグが latest でない
- アクションのrefがfull length commit SHAで指定される
- GitHub Appsからアクセストークンを発行するアクションはリポジトリを制限する
- GitHub Appsからアクセストークンを発行するアクションはPermissionsを制限する
アクション関連のチェック事項
- アクションのrefがfull length commit SHAで指定される
- GitHub Appsからアクセストークンを発行するアクションはリポジトリを制限する
- GitHub Appsからアクセストークンを発行するアクションはPermissionsを制限する
-
run
が使用される際にはshell
が指定される
チェック項目の一覧はこちらに記載されています(参考)。
pinact
pinactは使用するアクションをバージョンやタグで指定している部分を、full length commit SHA + バージョン(コメント)に変換してくれるツールです。
Renovate
Renovateは依存関係の自動更新などに用いることができるツールです。
Dependabot
DependabotはRenovateと同様に依存関係の自動更新などに用いることができるツールです。
おわりに
本記事では、GitHub Actionsを利用する際の脅威と対策についてのドキュメントを一部公開しました。
本記事を作成する上では、メルカリさんのGitHub Actions セキュリティガイドライン、@rungさんのCommon Threat Matrix for CI/CD Pipeline、GitHub公式のSecurity hardening for GitHub Actionsを特に参考にさせていただきました。
GitHub Actionsは開発者の生産性を向上させるCI/CDツールとして広く利用されていますが、その利用方法によって攻撃者による標的にされてしまう場合が生じます。本記事の作成を通じて、GitHub Actionsのセキュリティベストプラクティスを実践することに加え、社内の事情も考慮した上で脅威と対策を検討しました。今後は、仮にインシデントが発生した際の対応フローについて、今回のプラクティスを踏まえた上で見直したいと思います。
Discussion