git-secretsはじめました

# はじめに

サーバのID/Passwordをソースにべた書きして開発することは誰しもあるかと思います。しかし先日話題になっていた「[AWSから120万円の高額請求が来た話](https://qiita.com/axeralater/items/e6bb56a47138141f74b1)」というQiitaエントリのように、public gitリポジトリにAWSのアクセス情報を置こうものならばあっという間に悪用されてしまいます。

そんな他人に知られると悪用されかねない情報は「他人が容易にアクセス可能な場所に置かないこと」が鉄則なのですが、心掛けだけではどうやってもヒューマンエラーを避けることができません。そこで本エントリでは、そういったヒューマンエラーが起きた際の水際防止策として「gitリポジトリへのアクセス情報(ID/Passwordなど)の含まれたファイルのコミット」を機械的にリジェクトする[git-secrets](https://github.com/awslabs/git-secrets)の使い方を簡単に紹介します。[^1]

[^1]: [git-secret](https://github.com/sobolevn/git-secret)という似たプロジェクともありますが、こちらはgitリポジトリそのものをgpgで暗号化してアクセス制御しちゃおう、というものです。秘密情報が入ったファイルは`.gitsecret`というディレクトリに暗号化されて記録されていきます。こちらについても別エントリで紹介予定です。

# Q: git-secretsは何ができる？

A: gitのcommit/commit message等をスキャンし、その中に事前設定した秘密情報が含まれていたら、そのcommitをリジェクトします。

より正確には、

- `git commit`時にそのcommitの中に、
- 任意のタイミングで過去のgit historyの中に、
- あるいは任意のタイミングで任意のファイルの中に、

事前設定された正規表現にマッチする情報が存在するか否かを検査します。awslabsが中心で開発しているだけあって、何よりもまず、AWSのcredentialの含まれたcommitを弾くのに非常に向いています。

# 準備

まずは[マニュアル](https://github.com/awslabs/git-secrets)通りにインストールします。macOS Xならhomebrewが簡単です。

```shell
brew install git-secrets
```

そうでない場合は、`make install`です。

```shell
$ git clone https://github.com/awslabs/git-secrets.git
$ cd git-secrets
$ make install
```

# 基本的な使い方

導入として、最も単純な例として、`git commit`実行時、そのcommitの中にAWS向けの秘密情報が含まれているか否かを検査する方法を紹介しましょう。

はじめに、新規に立ち上げたgitリポジトリ、あるいは既存のgitリポジトリに対して、`git secrets --install`を行うことで、当該のリポジトリをgit-secretsに対応させます。

```shell
$ git init
$ git secrets --install
✓ Installed commit-msg hook to .git/hooks/commit-msg
✓ Installed pre-commit hook to .git/hooks/pre-commit
✓ Installed prepare-commit-msg hook to .git/hooks/prepare-commit-msg
```

続いて、当該リポジトリのgit-secretsの設定を行います。git-secretsでは、予め用意されているコマンドオプション`git secrets --register-aws`を実行することで、自力で細かな設定をすることなくAWS向けのcommit検査設定を導入することができます。

```shell
$ git secrets --register-aws
```

試しにAWSのアクセスキー(ダミー)を入れたファイルを作成、`git add`してcommitしてみます。

```shell
$ touch secret.txt
$ echo "aws_secret_access_key = ABcDe1F2hIjkl3nop45sTUv6XYz7aBcDEFghIJKL" > secret.txt
$ git commit -a
secret.txt:1:aws_secret_access_key = ABcDe1F2hIjkl3nop45sTUv6XYz7aBcDEFghIJKL

[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

no changes added to commit
```

無事、リジェクトされました。

なお、上記のエラーメッセージにあるように、一時的に検査を除外してcommitしたい場合には`git commit`コマンドに対して`--no-verify`オプションを付与します。また、既存のリポジトリにあとからgit-secretsを対応させた場合、過去のcommit履歴を検査したいことがあるでしょう。その場合は、`git secrets --scan-history`を行うことで、git historyをスキャンして検査することができます。まとめて以下に例示します。

```shell
git commit -a --no-verify
[master 61d9545] added credential with --no-verify
 1 file changed, 1 insertion(+), 1 deletion(-)

 $ git secrets --scan-history
61d954586149283a4f9ab70355320c15b846aa75:secret.txt:1:aws_secret_access_key = ABcDe1F2hIjkl3nop45sTUv6XYz7aBcDEFghIJKL

[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
```

無理矢理追加したcommitが正しく検出されました。

# 検査設定のカスタマイズ

AWS向けの秘密情報の検査設定については便利なショートカットオプションが用意されていますが、他の情報については自力で設定が必要です。設定方法には

- 正規表現で検出パターンを定義する方法、
- 秘密情報のファイル(`~/.aws/credentials`など)を読み込み、その内容の各行のexact-matchの検出パターンとして設定する方法、

の2種類があります。

## 正規表現での検出パターン定義

git-secretsによりAWS向けの秘密情報「以外」を検出したい場合、commitから除外したい秘密情報のフォーマットに応じた正規表現を、ひとつひとつ登録していきます。ここでは、

```text
password="top secret"
```

のように与えられるパスワードを検出パターンの正規表現 `"password\s*=\s*\".+\"$"`として登録します。

```shell
$ git secrets --add "password\s*=\s*\".+\""

$ echo "password=\"ok my password\"" > password.txt
$ git add password.txt
$ git commit -a
password.txt:1:password="ok my 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
```

実際に検出できました。

## 秘密情報ファイルを読み込んだ検出パターン設定

秘密情報ファイルなどを読み込んで、その各行をexact-matchでの検出パターンとして設定する方法も存在します。

```shell
$ git secrets --add-provider -- <command to show secret info>
$ git secrets --add-provider -- cat ~/.my.password.list.txt
```

パスワードが1行ごとに羅列されているようなテキストファイルが存在、あるいは秘密情報を列挙するようなコマンドが存在するようなフレームワーク向けとして有効な手段です。（が、そういった条件に当たったことがなく、あまり利用したことはありません。）

## サンプルパスワードなどの例外パターン設定

モックテストなどを行う際、決め打ちのサンプルパスワードを利用することは多いかと思います。本番のパスワードは検査で発見・除外してほしいけど、そういったパスワードまで除外されるちょっと困ります。なので、そういったサンプルパスワードは、検出されてもリジェクトされない例外パターンとして設定します。

前述の例では、正規表現`"password\s*=\s*\".+\"$"`を検出パターンとして設定していました。このとき、このパターンに引っかかる`password="my example password"`を例外として設定しましょう。

```shell
$ git secrets --add --allowed --literal "password=\"my example password\""

$ echo "my example password" > password.txt
$ git add password.txt
$ git commit -a
[master (root-commit) aa9098a] example password
 1 file changed, 1 insertion(+)
 create mode 100644 passowrd.txt
```

無事commitできました。なお、`--add --allowed`とする(`--literal`を抜く)と、例外パターンを正規表現で登録できます。**が、例外はあくまで例外ですので、false negative [^2] を避けるためにも正規表現などは使わずexact-matchで登録するべきです。** 

[^2]: git-secretsによる検査を通過してしまうが実際は本当のパスワードだったりする場合。


## 設定状況一覧の表示

現在のgit-secretsの設定状況は`git secrets --list`を実行することで一覧できます。参考として、`--register-aws`オプションで設定される検出パターンは以下の通りです。

```shell
$ git secrets --list
secrets.providers git secrets --aws-provider
secrets.patterns [A-Z0-9]{20}
secrets.patterns ("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')?
secrets.patterns ("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')?
secrets.allowed AKIAIOSFODNN7EXAMPLE
secrets.allowed wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```

# その他のTIPSとか

## ファイルの検査

git-secretsはcommitではなくてファイル実体が除外設定に触れていないか否かの検査をすることも可能です。

```shell
$ git secrets --scan secrets.txt // ファイルの検査
$ git secrets --scan secretDir // ディレクトリの検査 **git ls-filesで出てくるものが対象**
$ git secrets --scan -r secretDir // サブディレクトリ内部も含めて検査
```

## 端末全体の保護

`git secrets --register-aws`, `--add`, `--add-provider`コマンドに対して、`--global`オプションを追加することで、global設定として`~/.gitconfig`にgit-secretsの除外設定が書き込まれることになります。以降、既存のものも含み、`git secrets --install`を実行したgitリポジトリに対して、当該のglobal設定がデフォルトで有効化されます。

## `git secrets --install`の省略

`git init`後の`git secret --install`を忘れないために、[こちらの記事](https://qiita.com/pottava/items/4c602c97aacf10c058f1#新規プロジェクトで有効にする)の通り、

```shell
$ git secrets --install ~/.git-templates/git-secrets
$ git config --global init.templatedir '~/.git-templates/git-secrets'
```

を実行し、`git init`時のテンプレートを作成しておきましょう。これで`git init`実行時に`git secrets --install`を実行したことと同じになります。

# まとめ

AWS以外の場合、自分で検出パターンを指定するのがちょっとめんどくさいですが、「自分固有の秘密情報を不意に公開しない」という目的[^3]のためにはgit-secretsは非常に有用です。特にAWSの設定については各リポジトリごとではなく、`--register-aws --global`で端末全体のglobal設定で保険を掛けるのがいい手かと思います。

ただし、**git-secretsさえ使っていれば安心というわけではなく、そもそもそういったファイルをgitリポジトリに追加しないようにすることが最も重要**です。githubのREADME.mdにも「git-secretsの`--register-aws`では、AWSの秘密情報のパターンは**ほとんど網羅してるけど全部とは限らないのでgit-secretsはあくまで追加の保険として利用しましょう**」と明示的に記載されています。冒頭でも書きましたが、git-secretsのようなツールは、ヒューマンエラーが起きた際の水際防止策と考えておきましょう。

[^3]: 逆に、秘密情報を他人と共有して利用している場合には、別ルートでその秘密情報の受け渡しが必要となるのでgit-secretsは向いていません。秘密情報入りのgitリポジトリを複数人で共有したい、という場合には [git-secret](https://github.com/sobolevn/git-secret) (sなし) が良い手かと思います。