git-secrets導入してみた

:::note info
この記事は、[ラクス Advent Calendar 2024](https://qiita.com/advent-calendar/2024/rakus)の16日目の記事です。
昨日は@bosshawkさんの記事でした。
:::

## はじめに

こんにちは、[@rs_tukki](https://twitter.com/rs_tukki) です。  

この記事を読んでいる皆さんなら、公私問わず一度はGitHubを使ったことがあるかと思います。  
私自身も趣味で作ったChrome拡張なんかはGitHubで管理しています。

今回は、そんなGitHubを利用中に起こりがちなミスをなくすためのツールを導入した話をします。

## うっかりcommitをなくしたい

GitHubで公開しているリポジトリは誰でも閲覧できる状態なので、
例えば認証情報やAPIのアクセスキーなど、センシティブな情報はリポジトリにはcommitせず別途管理しておく必要があります。

とはいえ、commitしないようにと意識していても、どうしてもうっかりでcommitに含めてしまう可能性はありますので、
できることならツールで自動的に不用意なcommitを防止したいところです。

### .gitignoreではダメなのか

ここまで聞いて、「.gitignoreでいいじゃん」と思うかもしれません。
勿論大抵の機微情報のcommitはそれで防げるかと思いますが、それでも100%防げるかというとそうでもありません。
例えば以下のようなケースが当てはまるかと思います。

* 現新比較のためにファイルを二つ用意し、`_old`などにリネームしながら検証していた
* 新規にkeyを参照するためのファイルを追加した

このケースではファイル指定による.gitignoreの除外はすり抜けてしまうため、別途自動で検知してくれる仕組みを用意する必要があるわけです。

### ツールの選定

さて、そのように自動検知のツールがないか調べてみたところ、主に**git-secrets**と**gitleaks**の二つのツールが見つかりました。
二つのツールの比較は以下の通りです。

|| git-secrets | gitleaks |
|:-:|:-:|:-:|
|リポジトリ|https://github.com/awslabs/git-secrets|https://github.com/gitleaks/gitleaks|
|構築方法|Homebrew|Homebrew|
|初期ルール|未設定|特定パターンが設定済み|
|ルールの追加|〇|△[^1]|
|commitをrejectする機能|〇|〇|
|特定の行のみ対象外にする|〇|〇|
|特定のファイルのみ対象外にする|〇|△[^2]|
|特定のファイル名のパターンを対象外にする|×|×|
|バイナルファイルを検知する|×|×|

大体出来ることは同じですが、git-secretsはルールの追加が容易な点、
gitleaksは初期状態で既にルールが構築されている点で勝ります。

実際に私のリポジトリで動作を確認してみたところ、gitleaksでは検知してくれない情報があったため、今回はgit-secretsを使い手動でルールを追加する形を選択しました。

## git-secretsの導入

さて、というわけで早速git-secretsを導入してみます。
とりあえず単純に、`password`という文字列がcommit対象になっていたらrejectするようにしてみましょう。

### Homebrewでインストール

まずはgit-secretsをインストールします。
Mac環境であればHomebrewが使えるので、導入は一瞬で完了します。

```bash
brew install git-secrets
```

Linux環境やWindows環境であればインストール用のMakefileや.psファイルが用意されているので、そちらを利用することになります。
詳細は[readme](https://github.com/awslabs/git-secrets?tab=readme-ov-file#installing-git-secrets)をご確認ください。

### リポジトリへの適用

git-secretsはインストールしただけだとまだ機能しないので、続いて適用したいリポジトリに都度適用していく必要があります。
これもコマンド一発。

```bash
# リポジトリのルートディレクトリに移動
cd /path/to/my/repo
# スキャンを有効にする
git secrets --install
```

### ルールの追加

これでスキャンは有効になりましたが、先ほど比較したようにgit-secretsはgitleaksと異なり、**初期状態では何のルールも適用されていません。**
つまり、どんなパスワードやキーが含まれていてもコミットが通ってしまいます。

そのため、ユーザ自身で任意のルールを追加していくことになります。

```bash
# 特定の文字列パターンをルールに追加する
git secrets --add '[A-Z0-9]{20}'
# 全てのgitリポジトリにルールを追加する
git secrets --add --global '[A-Z0-9]{20}'
# メタ文字をエスケープし、表記通りの文字列をルールに追加する
git secrets --add --literal 'foo+bar'
# 特定のパターンを許可する
git secrets --add -a 'allowed pattern'
```

なお、git-secretsは元々AWSが提供しているツールなので、AWSに関連したルールセットはあらかじめ用意されています。

```bash
# AWSに関連したsecretsのパターンをルールに追加
git secrets --register-aws
```

### 実際に試してみる

これで導入は完了です。
試しに、適当なパスワードを含むファイルをコミットしてみましょう。

```bash
rs_tukki@tukki-Mac test-project % git commit -m 'test'
testProject/sensitive.json:1:      "password": [

[ERROR] Matched one or more prohibited patterns

Possible mitigations:
- Mark false positives as allowed using: git config --add secrets.allowed ...
- Mark false positives as allowed by adding regular expressions to .gitallowed at repository's root directory
- List your configured patterns: git config --get-all secrets.patterns
- List your configured allowed patterns: git config --get-all secrets.allowed
- List your configured allowed patterns in .gitallowed at repository's root directory
- Use --no-verify if this is a one-time false positive
rs_tukki@tukki-Mac test-project % 
```

このように、commitが弾かれているのが分かると思います。

## チームへの展開

さて、このgit-secrets、
折角の便利なツールですから同じプロジェクトのチームメンバーにも展開したいところです。
実際の導入手順は以下の通りでした。

1. Homebrewでインストール
1. git-secretsコマンドでリポジトリに適用
1. commitしてしまいそうな値をルールとして追加

これ、Mac環境なら**Homebrewさえ入ってればshellで自動化できるのでは？**
...という訳で作ったのが以下。

```shell
#!/bin/bash

brew install git-secrets
git secrets --install -f

# add rule here
git secrets --add key=.*
```

このコマンド自体にはセンシティブな情報は含まれていないので、
リポジトリのルートにこのシェルを置いておき、「cloneしたときにこのシェル実行してね！」とreadmeに書いておくだけで展開完了です。
Homebrewが入っていないユーザやLinux/Windowsユーザには別途フォローが必要ですが、それでもわずかな手間で事故のリスクは格段に減ると思います。

## まとめ

今回は、リポジトリへの意図しないcommitを防ぐためのgit-secretsの導入手順について解説しました。
.gitignoreだけでは防げないこともあるかと思いますので、皆さんの管理するリポジトリでも導入してみてはいかがでしょうか。

明日は@y-hirakawさんです！

[^1]:標準機能としては非搭載
[^2]:実験的機能