0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fine-grained PATをageで暗号化してgitのcredentialにする

0
Posted at

権限を抑制した Fine-grained PAT (Personal Access Token) をつかって HTTPS 経由で github に接続する環境で、平文の PAT をローカルに置かずに、age をつかって暗号化したファイルを参照させる構成案:

概要

~/.gitconfig に下記を書く:

[credential "https://github.com/myorg/"]
    helper = ""
    helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"

git で HTTPS リモート url = https://github.com/myorg/myrepot.git を使うとき、 store を credential helper にすると、~/.git-credentials などローカルのファイルに平文の PAT が置く必要があった。

代わりに helper に ! で始まるシェルコマンドを指定することで、git pull・push するときに都度、password (PAT) を復号化して参照してくれる。

age なら既存の SSH 鍵を流用して暗号化できるので、(既に SSH 鍵がある環境なら)GPG などの他の暗号化管理の仕組みを導入する必要がない。

暗号化した PAT は git 管理することもできるし、claude code さんがついついファイルの中身を Read してしまい、平文の PAT がログが流れるような事態も避けられて安心。

手順

Step 0: age をインストール

https://github.com/FiloSottile/age/#installation を参照

# Ubuntu の場合
sudo apt install age

# macOS の場合
brew install age

Step 1: PAT を取得

GitHub の Personal Access Tokens (Fine-grained) を発行する。
git pull・push するには、Contents: Read and write のPermissionsが必要。

Step 2: PAT を age で暗号化

ローカルユーザの SSH 公開鍵を -R (recipient) に指定して、PAT を暗号化したファイル myorg.pat.age を作成する

mkdir -p ~/.age

age -R ~/.ssh/id_ed25519.pub -o ~/.age/myorg.pat.age <<EOT
github_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOT

chmod 600 ~/.age/myorg.pat.age

~/.ssh/id_ed25519.pub (公開鍵) で暗号化して、
~/.ssh/id_ed25519 (秘密鍵) で復号化する。

Step 3: .gitconfig に credential helper を追加

[credential "https://github.com/myorg/"]
    helper = ""
    helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
  • myorg ⇒ 実際の GitHub の Organization 名
  • mybot ⇒ PAT を持つ GitHub アカウント名(bot アカウント名)
  • ~/.age/myorg.pat.age ⇒ Fine-grained PAT 暗号化ファイル

ユーザアカウント全体の PAT でなくて、Organization スコープの PAT なら、もしもの漏洩時の影響を当該 Organization だけに限定できる。仕組み的には、リポジトリを限定した PAT で、リポジトリごとに PAT を切り替える運用も可能。(後述)

Step 4: 動作確認

どの credential が使われるかは、リポジトリ URL ごとに git credential fill で確認できる。

echo "url=https://github.com/myorg/some-repo.git" | git credential fill | sed 's#^\(password=[a-z_]*\).*#\1********#'

期待する出力:

protocol=https
host=github.com
username=mybot
password=github_pat_********

確認ポイント:

  • username=mybot に Step 1 を発行した github ユーザが出てくる
  • password=github_pat_... に Step 1 で発行した PAT が出てくる
  • 末尾に ~/.git-credentials 由来の余計な行が混じっていない

通ったら実 push:

cd ~/some-repo  # myorg 配下の HTTPS clone 済 repo
git fetch
git push

.gitconfig を逐行で解説

[credential "https://github.com/myorg/"]
  • [credential "<URL>"]このセクションを「URL がこれにマッチするときだけ」発動にできる
  • 末尾の / が必要。末尾の / なしだと myorg-extra のような他の Organization にも適用されてしまう
  • https://github.com/ (ORG 指定なし) と書けば全 GitHub に適用、https://github.com/myorg/<repo> まで書けば 1 リポジトリだけに適用
    helper = ""
  • 継承された helper を一度クリアするためのおまじない
  • システム/グローバル設定で [credential] helper = manager-core (GCM) や osxkeychain が設定されていると、自分の helper の前にそちらが先に動いてしまう
  • 空文字列を指定するとその時点までの helper チェーンがリセットされ、以降のエントリだけが効く
    helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
  • 先頭 ! で「シェルコマンドとして実行」モード
  • git は sh -c "<helper> get" のように、末尾に get / store / erase を渡して呼ぶ。この引数を無視するため、シェル関数 f() でラップ → ; f で呼び出す。git が渡す getf $1 になって捨てられる

動作中の流れ

$ git push
  │
  └─ git の credential subsystem
      ├─ URL = https://github.com/myorg/myrepo にマッチする helper を探す
      ├─ helper = "" でチェーンリセット
      ├─ 自分の helper 関数 f を起動
      │   └─ age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age
      │       └─ stdout: github_pat_xxx...
      ├─ echo username=mybot; echo password=github_pat_xxx... を git に返す
      └─ git が HTTPS Basic 認証で github.com に push

PAT 平文は age の stdout に一瞬乗るだけで、ファイルにも環境変数にも残らない

メリット

  • PAT が平文でディスクに残らない~/.age/myorg.pat.age は age 暗号化済。
  • 追加デーモンなし。helper は git push のたびに起動・終了する
  • SSH 鍵を流用。鍵管理が一元化
  • ✅ rotate がファイル差し替えだけ (新 PAT を age -R ... -o ~/.age/myorg.pat.age で再暗号化)

デメリット・注意点

  • push のたびに age プロセスが起動する。レイテンシ +20-50ms 程度。普段の操作なら気にならないが、CI で大量に push するなら短期 cache helper との併用を検討
  • passphrase 付き SSH 鍵だと age が対話プロンプトを出す。ssh-agent からは取れないので、passphrase 付きにしたい場合は age の identity を別ファイルに分けるか agent 化する工夫が要る
  • age 復号鍵 (~/.ssh/id_ed25519) を奪われたら PAT も復号される。SSH 鍵の保護が PAT の保護を兼ねるので、SSH 鍵自体の管理 (chmod 600, パスフレーズ等) は重要

(参考)リポジトリ単位の限定 PAT で一部だけ上書きする

[credential "<URL>"] のマッチングは URL prefix の長さで決まる。これを使うと「普段は Organization PAT、特定の機密リポジトリだけは、限定 PAT を使う」という二層構成が組める。

GitHub の Fine-grained PAT は対象リポジトリを 1 個から選べるので、「最小権限の原則を .gitconfig のレベルで宣言する」運用が可能。

# 普段使い: Organization 全体に効く
[credential "https://github.com/myorg/"]
    helper = ""
    helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"

# 特定リポジトリだけ別系統 (より狭いスコープの PAT)
[credential "https://github.com/myorg/sensitive-repo"]
    helper = ""
    helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/sensitive-repo.pat.age); }; f"

sensitive-repo への push のときは より長い prefix がマッチする方が優先されるので、リポジトリ限定 PAT が使われる。それ以外のリポジトリは Organization PAT に fallback する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?