GPGキーでCommitに署名する:Gitにおける成りすましからコードを守る

Git tutorial - IT technology blog
Git tutorial - IT technology blog

パスワードや認証なしに、誰でもあなたの名前とメールアドレスでコードをcommitできることをご存知でしょうか?これは新しく発見された脆弱性ではありません——Gitが最初からそのような設計になっており、多くの開発者は自分自身に起きるまでこのことに気づきません。

実際の問題:GitはCommitする人物のIDを検証しない

Gitはデフォルトではcommitする人物の身元を検証しません。たった2つのシンプルなコマンドだけで:

git config user.email "[email protected]"
git config user.name "Nguyen Van A"

これだけで完了です——これ以降、その端末からのすべてのcommitが「Nguyen Van A」の名前で記録されます。たとえ実際に操作した人物が本人でなくても。GitHubやGitLabはメールアドレスに基づいてアバターを表示しますが、commit自体には認証メカニズムがまったくありません。

このような状況は、思っている以上に頻繁に発生します:

  • チームメンバーが新しいPCのgit configに誤ったメールアドレスを設定してしまう
  • CI/CDマシンに誤った身元情報が設定されている
  • 悪意ある第三者がオープンソースプロジェクトのcommit履歴を偽造しようとする

なぜGitはなりすましに対してこれほど脆弱なのか?

Gitは分散アーキテクチャで設計されており、身元を認証する中央サーバーが存在しません。git configのメールアドレスと名前は単なるメタデータに過ぎず、紙に名前を書くのと何ら変わりません。

Gitはそのメールアドレスが本物かどうかを確認するためにどのサーバーにも問い合わせません。また、ユーザーがそのメールアドレスの名義でcommitする権限を持っているかどうかも確認しません。これは意図的なトレードオフです:Gitはスピードとオフライン性を優先し、身元のセキュリティはホスティングプラットフォーム、CI/CD、またはチームの規約といった上位レイヤーの責任とされています。

3つの対処法——本当に正しいのは一つだけ

方法1:プラットフォームを信頼する(非推奨)

GitHubとGitLabはcommitのメールアドレスを登録アカウントと照合します。しかし、誰かがあなたのメールアドレスを知っていてgit configでそのメールアドレスを使用すれば、commitした人物があなたでなくても、プラットフォームはあなたのアバターを表示してしまいます。

方法2:pushにSSHキーを使う(的外れな保護)

SSHキーはリポジトリへのpush権限を認証しますが、各commitを作成したのが誰かは認証しません。この2つはまったく別のことです。

方法3:GPG署名付きCommit(正しい方法)

GPG(GNU Privacy Guard)を使うと、各commitにデジタル署名を付けることができます。この暗号署名はあなたのprivate keyでのみ生成でき、誰でもpublic keyで検証できます。これが、commitが正しい人物によって作成されたことを暗号学的に保証できる唯一の方法です。

正しいやり方:GPG署名付きCommitのセットアップ手順

ステップ1:GPGのインストール

Ubuntu/Debian:

sudo apt install gnupg

macOS:

brew install gnupg

インストールの確認:

gpg --version

ステップ2:GPG key pairの作成

gpg --full-generate-key

質問に対して以下を選択します:

  • Key type:RSA and RSA(オプション1)
  • Key size:4096ビット(2048より高いセキュリティ)
  • Expiry1y ——有効期限を設定することを推奨、永久設定は避ける
  • Name とemailgit config user.nameuser.emailと完全に一致している必要がある

ステップ3:Key IDの取得

gpg --list-secret-keys --keyid-format=long

出力は次のようになります:

sec   rsa4096/3AA5C34371567BD2 2024-01-15 [SC] [expires: 2025-01-15]
      1234567890ABCDEF1234567890ABCDEF12345678
uid                 [ultimate] Nguyen Van A <[email protected]>

Key IDは/の後の部分——ここでは3AA5C34371567BD2です。次のステップで使用するためコピーしておきましょう。

ステップ4:GPGキーを使うようGitを設定する

git config --global user.signingkey 3AA5C34371567BD2
git config --global commit.gpgsign true

commit.gpgsign trueオプションはすべてのcommitへの自動署名を有効にします——毎回commitするたびに-Sフラグを付ける必要がなくなります。

ステップ5:GitHubまたはGitLabにpublic keyを追加する

public keyをテキスト形式でエクスポートします:

gpg --armor --export 3AA5C34371567BD2

-----BEGIN PGP PUBLIC KEY BLOCK-----から-----END PGP PUBLIC KEY BLOCK-----までの出力全体をコピーします。

GitHubの場合:Settings → SSH and GPG keys → New GPG keyに移動し、貼り付けて保存します。

ステップ6:テストと検証

通常通りcommitします。何も追加する必要はありません:

git commit -m "feat: add user authentication"

作成したcommitの署名を検証します:

git log --show-signature -1

署名が成功した場合の出力:

commit a1b2c3d4e5f6...
gpg: Signature made Mon 15 Jan 2024 10:30:00 JST
gpg:                using RSA key 3AA5C34371567BD2
gpg: Good signature from "Nguyen Van A <[email protected]>" [ultimate]
Author: Nguyen Van A <[email protected]>
Date:   Mon Jan 15 10:30:00 2024 +0900

    feat: add user authentication

有効な署名付きcommitはGitHub上でcommitメッセージの横に緑色のVerifiedバッジが表示されます。

よくあるエラーの対処法

「gpg failed to sign the data」エラー

GPGはpassphraseの入力プロンプトを表示するためにターミナルを認識する必要があります。~/.bashrcまたは~/.zshrcに以下を追加してください:

export GPG_TTY=$(tty)

その後リロードします:

source ~/.bashrc

macOSでのpinentryの設定

macOSではターミナルの代わりにポップアップでpassphraseを入力できるpinentry-macの使用を推奨します——はるかに便利です:

brew install pinentry-mac
echo "pinentry-program /opt/homebrew/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent

チーム全体にGPG署名を強制する

チーム全体に適用したいですか?GitHub → Repository Settings → Branches → Branch protection rules → 「Require signed commits」を有効化に移動します。これにより、保護されたブランチにマージされるすべてのcommitに有効なGPG署名が必要になります——署名がなければマージできません。

CI/CDパイプラインでの検証:

git verify-commit HEAD

終了コード0 = 有効な署名、0以外 = 署名なしまたは無効な署名。

GPGキーのバックアップ——よく見落とされる重要なステップ

以前、誤ったブランチへのforce pushで重要なコードを失ったことがあります——それ以来、取り消せないgit操作にはいつも慎重になっています。GPGキーも同じです。private keyを失うと、そのキーで新しいcommitに署名する能力も失います。さらに悪いことに:バックアップなしにキーが期限切れになると、新しいキーを最初から作り直す必要があり——過去のすべてのcommitがGitHub上でUnverifiedと表示されるようになります。

キー作成直後にバックアップを取りましょう:

# private keyとpublic keyの両方をエクスポート
gpg --export-secret-keys 3AA5C34371567BD2 > my-gpg-private-key.asc
gpg --export 3AA5C34371567BD2 > my-gpg-public-key.asc

.ascファイルをUSBドライブまたはパスワードマネージャー(Bitwarden、1Passwordはどちらもファイル添付に対応)に保存しましょう。通常のクラウドストレージへのアップロードは避けてください。

新しいPCでリストアが必要な場合:

gpg --import my-gpg-private-key.asc

覚えておくべきコマンドまとめ

# 新しいkey pairを作成
gpg --full-generate-key

# 既存のキー一覧を表示
gpg --list-secret-keys --keyid-format=long

# GitHubに追加するためpublic keyをエクスポート
gpg --armor --export KEY_ID

# gitでキーを使う設定
git config --global user.signingkey KEY_ID
git config --global commit.gpgsign true

# 署名済みcommitを検証
git log --show-signature -1

# private keyのバックアップ
gpg --export-secret-keys KEY_ID > backup-private.asc

セットアップには約15分かかります。その代わり、あなたのすべてのcommitが暗号署名を持つことになります——誰でも検証でき、誰も偽造できません。curlやKubernetesのような大規模なオープンソースプロジェクトにcontributeする場合、多くのmaintainerはコードをレビューする前にVerifiedバッジを確認します。それは暗黙のシグナルです:この人は自分が何をしているかわかっている、というメッセージです。

Share: