Gitリポジトリ暗号化のススメ - git-secret -

# はじめに

先日、「[git-secretsはじめました](https://qiita.com/jqtype/items/9196e047eddb53d07a91)」というエントリを公開しました。[git-secrets](https://github.com/awslabs/git-secrets)は、APIキーなど秘密情報を含んだファイルがgitリポジトリへ誤ってcommitされないようにするためのツールです。一方で、非常に名前が似通った [git-secret](https://github.com/sobolevn/git-secret) (sなし) というものが存在します。このgit-secret (sなし) は、git-secrets (sあり) [^1] とは全く別のアプローチ、すなわち**gitリポジトリに安全に秘密情報をアップロードしちゃおう**という思想で開発が進められているbashツールです。本エントリではこの git-secret をざっくり紹介します。

[^1]: git-secretsとgit-secret、名前が非常にややこしいため、強調して区別したいときには「sなし」と「sあり」と記載します。

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

A: 指定ファイルを暗号化、許可した人のみが復号できるようにすることで、公開gitリポジトリを通じて秘密情報を共有できます。

そのためにgit-secretがやってることは割と単純で、以下に簡単にまとめます。

1. まず暗号化したいファイルを指定、そのファイル自身は`.gitignore`に追加することで2度とリポジトリにcommitされないようにする。
2. 暗号化の設定として、相手のGPG公開鍵をインポート。
3. GPGによる公開鍵暗号化。
4. 共有先の情報と一緒に暗号化済ファイルを`git add`してcommit/push。

「漏洩防止のための水際防止策」であるところのgit-secrets (sあり) とは異なり、「水際防止策を講じるよりも前に考慮すべき点を一歩突っ込んだ形で実現するためのツール」だということがなんとなくおわかりになるかと思います。すなわち、「gitに秘密情報の入ったファイルをcommitしない」ということを暗号化を用いて実現しています。

# 準備

実際に使ってみて動作を確かめるため、準備を進めます。ここではmacOS Xあるいは*nixを利用することとします。また、git-secretはbashを前提としたツールですので、bashの利用を仮定しています。

## GPGの導入

git-secretは [GnuPG (GPG)](https://gnupg.org/) を利用して暗号化・復号を行います。そのため、GPGが導入されていない場合は、その準備から必要になります。GPGそのものについての説明はここでは割愛します。QiitaにはGPGについての良エントリがありますので、そちらを参照してください。[^2]

[^2]: 例えば https://qiita.com/moutend/items/5c22d6e57a74845578f6

### GPG Suiteのインストール

macOS Xの場合は、[GPG Suite](https://gpgtools.org/) を導入しましょう。GPG Suiteの中に含まれるGPG Mailは不要であれば削除しても良いでしょう。homebrewでの導入も可能です。

```
$ brew install gnupg
```

*nixの場合は、お好きなパッケージ管理ツールないしソースからインストールしてください。Debian/Ubuntuの場合は、以下でインストール可能です。

```shell
$ sudo apt-get install gnupg
```

### GPG Keyの生成

続いて、自分のGPG公開鍵・秘密鍵ペアを生成します。コンソールを開き、`gpg --gen-key`コマンドを実行することで、以下のように対話型でGPG鍵ペアを生成できます。 **このとき指定するメールアドレスがgitの`user.email`に指定するものと同じものとなるようにしてください。** これは、git-secretにおいて、`user.email`がデフォルトでアクセス権限を与える相手 = 自分という設定となっているためです。GPG Suiteを導入したmacOS XではGPG Keychain.appからGUI経由でも生成できます。[^3] 

[^3]: New → 名前・パスワード入力 → Generate で生成。

```shell
$ gpg --gen-key
gpg (GnuPG/MacGPG2) 2.2.10; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: Test User
Email address: xxxx@xxxxxx.org
You selected this USER-ID:
    "Test User <xxxx@xxxxxx.org>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 028D26D9DF49446A marked as ultimately trusted
gpg: revocation certificate stored as '/Users/xxx/.gnupg/openpgp-revocs.d/041E87E64316B3685E352B0B028D26D9DF49446A.rev'
public and secret key created and signed.

pub   rsa2048 2018-10-25 [SC] [expires: 2020-10-24]
      041E87E64316B3685E352B0B028D26D9DF49446A
uid                      Test User <xxxx@xxxxxx.org>
sub   rsa2048 2018-10-25 [E] [expires: 2020-10-24]
```

`gpg --list-keys` で鍵が正しくインポートされているか確認してみましょう。

```shell
$ gpg --list-keys
/Users/xxx/.gnupg/pubring.kbx
-----------------------------
pub   rsa2048 2018-10-25 [SC] [expires: 2020-10-24]
      041E87E64316B3685E352B0B028D26D9DF49446A
uid                      Test User <xxxx@xxxxxx.org>
sub   rsa2048 2018-10-25 [E] [expires: 2020-10-24]
```

上記の状態になればひとまずGPGの準備完了です。

## git-secretのインストール

macOS Xの場合、homebrewからインストール可能です。

```shell
$ brew install git-secret
```

それ以外の場合、ソースからインストールが可能です。aptやrpmにも対応しています。[^4]

```shell
$ git clone https://github.com/sobolevn/git-secret.git
$ cd git-secret && make build
$ PREFIX="/usr/local" make install
```

[^4]: http://git-secret.io/installation

# 簡単な使い方

まずは、自分自身だけに秘密情報へのアクセスを許す例を使って、簡単に使い方を紹介しましょう。テスト用のディレクトリ`./git-secret`にgitリポジトリを作って説明します。

```shell
$ mkdir git-secret
$ cd git-secret
$ git init
```

まずは `git secret init` コマンドにより`.gitsecret`ディレクトリを生成します。

```shell
$  git secret init
'/Users/xxx/git-secret/.gitsecret/' created.
cleaning up...
```

`.gitsecret` ディレクトリは、暗号化対象ファイルの設定や、復号できる相手の公開鍵情報などが格納されます。これらをアクセスを許す相手と共有しないとデータが復号できないため、`git add`します。セキュリティ上、`.gitsecret`は公開しても実際に保護したいファイルのセキュリティに影響を与えませんが、**暗号化対象ファイルの名前は秘匿されない**ので注意しましょう。

```shell
$ git add .gitsecret
```

まずは必ず自分だけは復号できるよう、自分のメールアドレス (この例では`xxx@xxxxxx.org`) に対してアクセス権限を与えます。このとき、gitの`user.email`に自分のメールアドレスが正しく指定されていることを確認してください。

```shell
$ git secret tell -m
gpg: keybox '/Users/xxx/git-secret/.gitsecret/keys/pubring.kbx' created
gpg: /Users/xxx/git-secret/.gitsecret/keys/trustdb.gpg: trustdb created
done. xxx@xxxxxx.org added as someone who know(s) the secret.
cleaning up...
```

このリポジトリに設定されたgit-secretにおいて、誰がアクセスを許可されているのか調べるには`git secret whoknows`コマンドを実行します。

```shell
$ git secret whoknows
xxx@xxxxxx.org
```

自分だけがアクセス許可されていることがわかります。

続いて、秘密情報を入れたファイルを生成して、git-secretに秘匿するように設定します。`secret.txt`を生成して、`.gitignore`に追加、`git secret add`でgit-secretへ設定します。このとき、`.gitignore`に登録しないままだと、後の暗号化時にエラーが出ます。

```shell
$ echo "this is my secret" > secret.txt
$ echo "secret.txt" > .gitignore
$ git secret add secret.txt
1 item(s) added.
```

`git secret hide`コマンドを実行すると、`git secret add`コマンドで設定されたファイルが暗号化され、拡張子`.secret`のファイルが生成されます。

```shell
$ git secret hide
done. all 1 files are hidden.
$ ls
secret.txt        secret.txt.secret
```

このときgitのリモートリポジトリを通じて共有するのは、`.gitsecret`ディレクトリに加えて、暗号化されたファイル本体である拡張子`.secret`のファイルとなります。このため、`secret.txt.secret`を`git add`し、commitしてみます。あとはよしなにリモートリポジトリを設定してpushしてみてください。

```shell
$ git add secret.txt.secret .gitsecret
$ git commit -m 'initial secret commit'
$ git remote add origin https://github.com/xxxxxx/x.......git
$ git push -u origin master
```

今度は、pushしたリモートリポジトリから別のディレクトリへ`git clone`して、自分が`secret.txt`を得られるか、試してみましょう。`secret.txt.secret`は、あんまり確認したことにはならないですが、`cat`で中身を見ても元のテキストでない暗号文であることがわかります。

```shell
$ mkdir -p git-secret-clone
$ git clone https://github.com/xxxxxx/x.......git git-secret-clone
$ cd git-secret-clone
$ ls
.                 ..                .git              .gitignore        .gitsecret        secret.txt.secret
$ cat secret.txt.secret
```

`secret.txt`へ復号するには、`git secret reveal`コマンドを実行します。これにより、git-secretに設定されたすべての暗号化ファイルについて、自分のgpg公開鍵に対応する秘密鍵で復号処理が実行されます。

```shell
$ git secret reveal
done. all 1 files are revealed.
$ ls
.                 ..                .git              .gitignore        .gitsecret        secret.txt        secret.txt.secret
$ cat secret.txt
this is my secret
```

GPG鍵ペアを生成したときに設定したパスフレーズの入力を求められ、無事に復号できました。

続いて、せっかくのgitリポジトリですので秘密情報のファイルを更新してcommitしてみましょう。秘密情報ファイルを更新した場合、改めて`git secret hide`を実行して再暗号化、暗号化ファイルの方のアップデートを行ってからcommitをすることになります。

```shell
$ echo 'updated' >> secret.txt

$ cat secret.txt
this is my secret
updated

$ git commit -a -m 'updated'
[master 5631f69] updated
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite secret.txt.secret (100%)

$ git push
```

元のディレクトリに戻って`git pull`、`git secret reveal`をすることで、更新された秘密情報ファイルもリモートリポジトリ経由で取得できていることがわかります。

```
$ cd ../git-secret
$ git pull
remote: Counting objects: 5, done
remote: Finding sources: 100% (3/3)
remote: Getting sizes: 100% (3/3)
remote: Total 3 (delta 1), reused 3 (delta 1)
Unpacking objects: 100% (3/3), done.
From https://github.com/xxxxxx/x.......
   7dea230..5631f69  master     -> origin/master
Updating 7dea230..5631f69
Fast-forward
 secret.txt.secret | Bin 1409 -> 1416 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

$ git secret reveal
File '/Users/xxx/git-secret/secret.txt' exists. Overwrite? (y/N) y
done. all 1 files are revealed.

$ cat secret.txt
this is my secret
updated
```

gitリポジトリを介した秘密情報のやり取りについてイメージはつかめましたでしょうか？ 次の節では、この節で作った`git-secret`ディレクトリとリポジトリを複数ユーザで利用することを想定し、秘密情報へアクセスできるユーザを追加したり削除したりする、もうちょっと突っ込んだ使い方について紹介します。

# 複数ユーザでの秘密情報の共有

## アクセス可能ユーザの追加

アクセス権限を与えたい相手から、メールなどでその相手のGPG公開鍵を受け取ります。その鍵を適当な名前のファイルにして保存し、自分のローカルgpg鍵リポジトリ `~/.gnupg/` へインポートしましょう。追加したい相手全員に対して鍵を取得し、この作業を繰り返します。

```shell
$ gpg --import member_public_key.pub
```

`gpg --list-keys`を実行すると自分の鍵に加えてその相手の鍵が正しくインポートされていることがわかります。

```shell
$ gpg --list-keys
/Users/xxx/.gnupg/pubring.kbx
-----------------------------
pub   rsa2048 2018-10-25 [SC] [expires: 2020-10-24]
      041E87E64316B3685E352B0B028D26D9DF49446A
uid                      Test User <xxxx@xxxxxx.org>
sub   rsa2048 2018-10-25 [E] [expires: 2020-10-24]

pub   rsa4096 2018-10-25 [SC] [expires: 2022-10-25]
      DE22B0EC5E6A5B7B0E6D4FF871AF954BA86FDEED
uid           [ unknown] Test User2 <yyyy@yyyy.org>
sub   rsa4096 2018-10-25 [E] [expires: 2022-10-25]
```

続いて、git-secretを設定したgitリポジトリのディレクトリで、当該のユーザのメールアドレス (この例だとyyyy@yyyy.org) に対してアクセス権限を与えてみましょう。

```shell
$ git secret tell yyyy@yyyy.org
done. yyyy@yyyy.org added as someone who know(s) the secret.

$ git secret whoknows
xxxx@xxxxxx.org
yyyy@yyyy.org
```

上記のように、2人に対してアクセス権限が与えられたことがわかりました。このアクセス権限の追加処理を、許可したいユーザごとに繰り返します。

アクセス権限を持つユーザは追加できましたが、この段階では暗号化されたファイルの方はまだ更新されておらず、権限を持つユーザ全員がアクセス可能なものにはなっていません。そのため、改めて`git secret hide`コマンドを実行して再暗号化を行う必要があります。

```shell
$ git secret hide
done. all 1 files are hidden.

$ git commit -a -m 'added yyyy'
[master cf53b2d] added yyyy
 3 files changed, 0 insertions(+), 0 deletions(-)
 rewrite secret.txt.secret (100%)
$ git push
```

上記の処理で、yyyy@yyyy.orgさんもこのリモートリポジトリをcloneすることで`secret.txt`を復号して取得できるようになりました。

## ユーザの失効

今度はyyyy@yyyy.orgさんからアクセス権限を剥奪してみます。

```shell
$ git secret killperson yyyy@yyyy.org
removed keys.
now [yyyy@yyyy.org] do not have an access to the repository.
make sure to hide the existing secrets again.

$ git secret whoknows
xxxx@xxxxxx.org
```

この段階では、まだyyyy@yyyy.orgさんは暗号化ファイル`secret.txt.secret`の復号が可能です。なので、アクセス権限の付与のときと同様に、`git secret hide`コマンドで再暗号化し、リモートリポジトリのファイルも二度とアクセスできないようにします。

```
$ git secret hide
done. all 1 files are hidden.

$ git commit -a -m 'revoked yyyy'
[master ec49af1] revoked yyyy
 2 files changed, 0 insertions(+), 0 deletions(-)
 rewrite secret.txt.secret (100%)
$ git push
```

注意しなければいけないことは、過去のcommitには再暗号化前のファイルが入っているということです。このため、失効したユーザもcommitを遡ればファイルを復号することが可能です。このため、**ユーザを失効した後は秘密情報そのものも更新しておくことが重要**です。アクセスキーなどは再発行してファイルに上書きして更新しておきましょう。


# Q&A

### Q: git-secretの暗号化設定対象ファイル一覧はどう見るの？

A: `git secret list`コマンドで一覧できます。

```shell
$ git secret list
secret.txt
```

### Q: git-secretの暗号化設定対象からの解除はどうするのか？

A: `git secret remove <file name>`で可能です。

```shell
$ git secret remove secret.txt
secret.txt -> secret.txt -> /Users/xxxx/git-secret/secret.txt
removed from index.
ensure that files: [secret.txt] are now not ignored.
cleaning up...
```

removeした後に再暗号化は不要ですが、gitリポジトリで生ファイルを共有をするならば`.gitignore`の編集などを行いましょう。


### Q: [git-secrets](https://github.com/awslabs/git-secrets) (sあり) と併用できるの？

A: できます。git-secrets (sあり) の検査で引っかかるような秘密情報が入ったファイルは、必ず暗号化してから `git commit` するというようなルール付けをすると、ヒューマンエラーも減るしgitを通じた秘密情報の共有もできるし良いのでは。

# まとめ

**GPGがとてもめんどくさい**が何よりも気になるところではありますが、git-secretを利用することで、サーバのアクセスキーなどの秘密情報を「外に出さない」という運用から「安全に共有する」という運用に切り替えることができるようになります。アクセスキーの共有がし辛いなど、利便性が損なわれている場合には導入を検討してみてもいいと思います。

また、git-secretの現状バージョンでは

- `git secret tell`後などに必要となる`git secret hide`による再暗号化
- `git secret add`したファイルの`.gitignore`への追加

などの煩雑な処理が必要です。これら、gitのhooksを編集することで`git commit`実行時などに自動的に行われるように対応すればgit-secretのめんどくさい手順が大幅にが大きく向上するでしょう。このあたりはまずは自前で編集してしばらく運用してみるつもりです。

# 参考

- [How to keep your repository’s sensitive data secure using git-secret](https://medium.com/@GeorgiosGoniotakis/how-to-keep-your-repositorys-sensitive-data-secure-using-git-secret-c1ddc28cb985) (Medium)
- [git-secret](http://git-secret.io/) (official)

# おまけ：アクセス制御について

実際の使い方を紹介したところで、最後におまけとしてgit-secret (ひいてはGPG）の思想をなんとなく理解するために重要な、データのアクセス制御の概念を紹介しておきましょう。

情報セキュリティにおけるアクセス制御には、ざっくり2種類のアプローチがあります。1つは「セッションベースのアクセス制御」、もう1つは「暗号化ベースのアクセス制御」です。git-secretは、後者の「暗号化ベースのアクセス制御」の概念を利用したアプリケーションです。

## セッションベースのアクセス制御

セッションベースのアクセス制御は非常に理解しやすいと思います。これは、ID/Passwordなどによる「認証」を基礎としています。認証により、アクセスを許可する主体 (例えばデータのオーナー) が

- 認証を要求してきた相手は誰なのか
- その相手にどこまでアクセスを許していいのか

を確認した上で、相手と安全かつ一時的な「セッション」を構築します。そのセッションを通じた場合のみ、データへのアクセスが許されるという方法です。ユーザ管理が必要なWebサービスなどは、通常こちらの考えでアクセス制御がなされています。もっと言っちゃえば、SSL/TLSなんかはこの考えで作られています。

## 暗号化ベースのアクセス制御

こちらは、セッションベースのアクセス制御とは全く異なる思想の方法です。非常に端的に言えば、暗号化ベースのアクセス制御とは、アクセスを許可する主体が「**アクセスを許したい相手だけが復号できるように適切に暗号化した上で、暗号化データを配布してしまおう**」という方法です。これは言い換えれば、アクセス許可を与えられた人以外に復号されることがなければ、**暗号化データそのものは公開してしまっても構わない**という考え方です。データそのものがわかんなきゃいいでしょ、ってことですね。[^a1]

ご理解の通り、適切に暗号化を行った上でgitリポジトリで全世界に公開してしまおうというgit-secretは、完全にこの思想に則ったアプリケーションです。その他、メディアデータは暗号化してContent Delivery Network (CDN) で公開・配布してしまって、復号するための権限として復号鍵を購入するようなのDRMのサービスはこちらの考えで作られていると言ってもよいでしょう。

[^a1]: ただし、**法制度と技術は乖離している**ので、一概に適用していい考えかどうかはその利用形態に応じて変わります。例えば経済産業省の個人情報保護ガイドラインの解釈では、数年前まで個人情報は暗号化していても個人情報という扱いで、暗号化された個人情報データが外部に出ること＝個人情報漏洩とされていました。ちょっとあたまおかしい。

## それぞれのpros/cons
それぞれ非常に簡単に以下にまとめてみます。

### セッションベースのアクセス制御

- pros:
  * アクセスユーザの追加、失効、権限(アクセス範囲)変更が容易。
- cons:
  * アクセスユーザ管理、認証、セッション管理のインフラ整備が比較的大変。
  * アクセス制御するデータが1ファイルであったとしても、アクセスユーザ数分のセッション構築・帯域確保が必要。

### 暗号化ベースのアクセス制御

- pros:
  * 各ユーザの公開鍵を用いて、(適切な暗号アルゴリズムにより) データを暗号化するのみでアクセス制御が可能。
  * データを復号するために必要なのは、全ユーザで同じ暗号化データとなり、データ再利用性が高い。[^a2]
- cons:
  * 一度配信してしまったデータに対しては、アクセスユーザの追加、失効、権限変更が困難。[^a3]

[^a2]: ただし、暗号アルゴリズムや暗号の利用方法次第です。最悪の場合、各ユーザごとに別の暗号化データを用意することになります。
[^a3]: 前述したように、過去のcommitは失効したユーザでも依然アクセス可能なことなど、追加・失効・変更などが未来のデータに対してのみしか有効ではないことを意味します。

どちらがいい悪いというものではなく、一長一短なのは見てお分かりになる通りです。利用シーンやアクセス制御したいデータに応じて使い分けることが重要ですね。