ABEJA Tech Blog

中の人の興味のある情報を発信していきます

trufflehog x pre-commit & GitHub Actions で GitHubのセキュリティを強化したってばよ

こちらは ABEJA アドベントカレンダー 12日目の記事です。

こんにちは。CTO室の村主です。セキュリティ強化も自組織の役割であるため、ABEJAのセキュリティ対策に関する内容を共有したいと思います。

はじめに

みなさんGitHubのセキュリティ高めてますか?GitHub Organizationのセキュリティベストプラクティス等は Flatt Security さんがすごく良いまとめを公開されているので、それに沿って対応されるのが良いと思います。

blog.flatt.tech

今回は、コード内にクレデンシャルを埋め込まないようにするためと、仮に埋め込まれてpushされたとしてもなんとか検知できるようなセキュリティ対策を紹介したいと思います。

特に弊社は1,000以上のリポジトリが存在しており、リポジトリごとにセキュリティチェックの設定を入れるのは骨が折れるので、包括的に設定できないか試行錯誤した結果も紹介します。

trufflehogトリュフホッグ

みなさん trufflehog って知ってますか?すごい便利なツールだったので紹介したいと思います。

Truffle Security社が開発しているオープンソースで、gitsecret 等と同様のクレデンシャルを検知するツールです。trufflehog は他と違って優れている部分は「credential detector」です。

For every potential credential that is detected, we've painstakingly implemented programatic verification against the API that we think it belongs to. Verification eliminates false positives. For example, the AWS credential detector performs a GetCallerIdentity API call against the AWS API to verify if an AWS credential is active.

コードにクレデンシャルが埋め込まれていることを検知するだけではなく、そのクレデンシャルが現在有効かどうか もチェックしてくれます。AWSの例だと GetCallerIdentity のAPIをコールして有効性をチェックしています。

つまり、現状でリスクが高いコード(クレデンシャル)だけを洗い出して効率的に対処することができます。 すごく筋が良いツールですね。

さらに、AWSだけではなく約700のcredential detectorsが実装されているようです。(2022/12現在)

弊社で利用しているSendgirdやAuth0、Datadogなども含まれていてすごく有難いです。

クレデンシャルの埋め込みに対する取り組み

この trufflehog を使って、クレデンシャルの埋め込みの対策を実施しました。

1. まず現在のリポジトリがクリーンな状態を担保するために、全リポジトリをスキャンしました(1,000以上…)

これで全リポジトリは問題ないことを担保できました。スキャン方法は至って単純で「全リポジトリリストを取得、リストしたリポジトリをスキャン」です。

実施方法は後述します。

2. 次に新しいコミットに対してクレデンシャルが埋め込まれないように全エンジニアに pre-commit に trufflehog を設定してもらいました

これで新しいコミットにもクレデンシャルが埋め込まれなくなりました。しかし、新しく入社した人が設定し忘れたりPC交換時に設定し忘れたりといったリスクは残っています。 設定が漏れた場合はリポジトリにクレデンシャルが埋め込まれてしまうので、リポジトリ側で検知できる仕組みが欲しいと思いました。

また、個々のリポジトリで「push時にGitHub Actionsが起動してtrufflehogでスキャンする」と言ったことは容易ですが、1,000個もGitHub Actionsを設定しまわるのは骨が折れるのと、リポジトリ作成時にスキャン設定が漏れると(以下略

3. そこで、GitHub Actions を利用して、1日1回、更新のあったリポジトリだけリスト化して、trufflehogでスキャンする仕組みを構築しました

これらの実装により以下が実現できました。やったね

1. 全リポジトリをスキャンして全リポジトリがクリーンな状態
2. pre-commit で新規コミット時もクリーンな状態
3. 仮にpre-commit が漏れたとしても1日1回、更新のあったリポジトリだけリスト化して、trufflehogでスキャンしてクリーンな状態を担保

それでは trufflehog の使い方や上記の実装方法を解説していきます。

trufflehog の使い方

インストールはこれだけです。

brew tap trufflesecurity/trufflehog
brew install trufflehog

trufflehog git https://github.com/abeja-inc/abeja-cli.git

実行方法は以下です。 --only-verified を付与するとクレデンシャルが有効な場合に検知し、付与しない場合はクレデンシャルが埋め込まれているだけで検知するようになります。

$ gitleaks-playground % trufflehog git file://. --only-verified
🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷

Found verified result 🐷🔑
Detector Type: AWS
Raw result: AKIAXXXXXXXXXXXXXXXXX
Repository: https://github.com/abeja-inc/xxxx-xxxx.git
Timestamp: 2022-08-23 17:12:35.030695 +0900 JST m=+0.086615668
Commit: unstaged
File: test.py
Email: unstaged

ちなみにクレデンシャルを1文字変更したら有効性を確認できなくなり検知しなくなりました。

$ vim test.py                            
$ trufflehog git file://. --only-verified
🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷

正しくは unverified で引っ掛かってるようで、 —only-verified にしてるから表示されていないだけです。

$ trufflehog git file://.                
🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷

Found unverified result 🐷🔑❓
Detector Type: AWS
Raw result: AKIAXXXXXXXXXXXXXXXXX
Timestamp: 2022-08-23 17:16:45.979598 +0900 JST m=+0.087489918
Commit: unstaged
File: test.py
Email: unstaged
Repository: https://github.com/abeja-inc/xxxx-xxxx.git

全リポジトリのスキャン

全リポジトリのスキャンは以下な感じで適当に実行できると思います。大したことないのでサンプル程度にご確認ください。 なお、git file://. みたいにファイルシステムを指定しなくても、GitHub上のコードを直接スキャンすることもできます。

#!/bin/bash

GITHUB_ORG="xxx"

i=1
REPO_LIST=$(gh repo list ${GITHUB_ORG} -L 2000 | awk '{print $1}')
REPO_COUNT=$(echo ${REPO_LIST} | grep -c "")

echo ${REPO_LIST}
for x in $(echo ${REPO_LIST})
do
    REPO="$x"
    REPODIR=$(echo ${REPO}|cut -d"/" -f2)

    echo "## Start (${i}/${REPO_COUNT}) ==>> $REPODIR"
    trufflehog git https://github.com/${REPO} --only-verified --json >> ${REPODIR}.json

    i=$(($i + 1))
done

pre-commit の設定

まずは社内のプライベートリポジトリを作成し、pre-commit の共通設定ファイル hooks/pre-commit を配置します。

#!/bin/bash

# Prioritize local pre-commit.
GIT_ROOT=$(git rev-parse --show-toplevel)
LOCAL_HOOK="${GIT_ROOT}/.git/hooks/pre-commit"

if [ -e $LOCAL_HOOK ]; then
  source $LOCAL_HOOK
fi

# The following is a pre-commit for additional global settings.
## Check for valid credentials by TruffleHog
trufflehog git --only-verified --fail file://.

次に各端末に trufflehog をインストールしてもらいます。

# pre-commit Install → https://pre-commit.com/
brew install pre-commit

# TruffleHog Install. 
# Click here for non-Mac users -> https://github.com/trufflesecurity/trufflehog
brew tap trufflesecurity/trufflehog
brew install trufflehog

そして、先ほど作成したプライベートリポジトリをcloneし、pre-commitの設定をしてもらいます。

mkdir -p ~/.config/
cd ~/.config/
git clone https://github.com/aaaaa/bbbb.git
git config --global core.hooksPath "~/.config/bbbb/hooks"

最後に、pre-commit の共通設定ファイルに更新があった場合に同期されるように以下を実行してもらいます。以上です。

# Sync
## bash
echo 'bash -c "cd ~/.config/agsg/hooks && git pull 1>/dev/null"' >> ~/.bashrc

## zsh
echo 'bash -c "cd ~/.config/agsg/hooks && git pull 1>/dev/null"' >> ~/.zshrc

なお、この方法では Husky などを利用中のお場合は core.hooksPath が干渉するのでご注意ください。

最後に、GitHub Actions で1日1回更新のあったリポジトリだけリスト化して trufflehog でスキャン

先ほど作成したプライベートリポジトリ内でも良いのですが、.github/workflows/security_check.yml というファイルを作成します。

name: TruffleHog periodic check
on:
  schedule:
    - cron: '0 1 * * *'
jobs:
  get_checkrepolist:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.value }}
    steps:
      - uses: actions/checkout@v3
      - name: Get list of repositories pushed 1 day ago
        id: matrix
        run: |
          YESTERDAY=$(date --date '1 day ago' "+%Y-%m-%d")
          LIST=$(gh repo list ${{ env.GH_ORG }} -L 100 --json name,pushedAt | jq --arg yesterday $YESTERDAY '.[] | select(.pushedAt >= $yesterday)' | jq .name -c | grep -v -f .ignore | jq -s)
          echo "::set-output name=value::$(echo $LIST)"
        env:
          GH_TOKEN: ${{ secrets.PAT }}
          GH_ORG: "XXXX"
  check_trufflehog:
    needs: get_checkrepolist
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        value: ${{ fromJson(needs.get_checkrepolist.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v2
      - name: TruffleHog Install 
        run: |
          mv files/trufflehog /usr/local/bin
          trufflehog --version
      - name: Exec TruffleHog
        run: |
          trufflehog git https://${{ env.GH_USER }}:${{ env.GH_TOKEN }}@github.com/${{ env.GH_ORG }}/${{ matrix.value }} --only-verified --fail
        env:
          GH_TOKEN: ${{ secrets.PAT }}
          GH_USER: "XXXX"
          GH_ORG: "XXXX"
      - name: Create Issue
        if: failure()
        uses: imjohnbo/issue-bot@ebb016264b7ca514eea6315872b0d768868fd8c1
        with:
          title: "[TruffleHog] ${{ matrix.value }} is Failure"
          labels: "trufflehog"
          body: |
            [${{ matrix.value }}](https://github.com/${{ env.GH_ORG }}/${{ matrix.value }}) is Failure.
            See [the action log](https://github.com/${{ env.GH_ORG }}/${{ matrix.value }}/actions/runs/${{ github.run_id }}) for more details.
            Assign a team administrator for the repository to be the resolver of this issue.
        env:
          GH_ORG: "XXXX"

補足としては、

  1. env に設定している環境変数(GH_ORGやGH_USER)は適時設定ください。
  2. env に設定している secrets.PAT は Actions secrets に PAT という名前で、全リポジトリを参照できるGitHubのパーソナルアクセストークンを設定ください。これがキモで、GitHub Actionsでデフォルトで設定されているGitHubのTokenは自リポジトリのみの参照権限のため、全リポジトリを参照できるパーソナルアクセストークンが必要になります。
  3. .ignore というファイルを作成すれば、除外するリポジトリを指定できるようになっています。
  4. 最後に、filesというディレクトリにtrufflehogのバイナリファイルを配置してください。trufflehog と imjohnbo/issue-bot は外部のツールになるため、脆弱な変更が加わらないようにバイナリやバージョンを固定化するようにしています。

以上です。

まとめ

  1. 1,000を超える全リポジトリでクレデンシャルの埋め込みをチェックしたい。trufflehog を使って現在も利用可能なクレデンシャルのみ検知するようにした。
  2. pre-commit を設定して、クレデンシャルを埋め込む前に検知できるようにした。(お作法)
  3. 仮に pre-commit の設定が漏れたとしても受け取り側のリポジトリで検知できるようにした。

採用メッセージ

ABEJAではエンジニア採用を積極的に行っております!ご興味があればご確認くださいませ。 careers.abejainc.com