GitHub CLI(gh)の認証情報を1Passwordで管理するため、GitHubのPersonal Access Token(PAT)を1Passwordに保存し、1Password Shell Plugin経由で利用できるように設定しました。
この記事では、私のMac環境でGhosttyからghを使えるようにするまでの手順と、その途中で理解した仕組みについてまとめます。
単なる設定手順だけでなく、
- PATとは何か
- 1Password Shell Pluginとは何か
-
op plugin init ghは何をしているのか - なぜ
ghを実行すると1Passwordを経由するのか -
~/.zshrcに追加したコードは何をしているのか -
type ghやgh api user --jq .loginは何を確認しているのか
といった部分も整理していきます。
最終的な構成は、ざっくり次のようになります。
Ghostty
↓
zsh
↓
1Password Shell Plugin
↓
1Passwordに保存したPAT
↓
GH_TOKENとしてGitHub CLIへ渡す
↓
GitHub CLI(gh)
↓
GitHub API
使用した環境
今回使用したものは以下です。
- macOS
- Ghostty
- zsh
- GitHub CLI(
gh) - 1Passwordアプリ
- 1Password CLI(
op) - GitHubで発行したfine-grained Personal Access Token
Ghosttyはターミナル画面を提供するアプリで、今回の認証設定そのものは主にzshと1Password Shell Plugin側で行っています。
そのため、Ghostty独自の設定ファイルを変更するのではなく、1Passwordのプラグイン設定と~/.zshrcを設定していきます。
1. GitHubでPATを作成する
まず、GitHubでPersonal Access Tokenを作成しました。
PATはPersonal Access Tokenの略で、GitHub CLIやAPIなどからGitHubへアクセスするときに利用できる認証情報です。
イメージとしては、
GitHub CLI
↓
PATで認証
↓
GitHub API
という流れです。
PATには、「何を許可するか」という権限を設定できます。
たとえば、
- リポジトリのコードを読む
- コードを書き込む
- Issueを操作する
- Pull Requestを操作する
- GitHub ActionsのWorkflowを操作する
といった権限です。
今回はfine-grained PATを使用しました。
fine-grained PATでは、対象となるリポジトリや操作権限を細かく制限できます。
たとえば、
特定のリポジトリだけ
Contents → Read and write
Issues → Read-only
といった設定が可能です。
今回は自分の複数リポジトリから利用するため、Repository accessには「All repositories」を選択しました。
ただし、この設定では現在所有しているリポジトリだけでなく、今後作成するリポジトリも対象になります。
特定のリポジトリだけで利用する場合は、「Only select repositories」を選択したほうが権限を絞れます。
トークンの有効期限は1年間に設定しました。
Repository permissionsについては、以下を設定しています。
- Contents:Read and write
- Issues:Read and write
- Pull requests:Read and write
- Workflows:Read and write
- Metadata:Read-only
これはあくまで私の利用用途に合わせた権限設定です。
すべてのghコマンドでこれらの権限が必要というわけではありません。PATには、実際に必要な操作に合わせて必要最小限の権限を付与するのが安全です。
また、今回は有効期限を1年間にしているため、期限が来ればGitHub側でPATは失効します。
その際は、新しいPATを発行して更新する必要があります。
GitHub Docs - Managing your personal access tokens
2. PATを1Passwordに保存する
GitHubで発行したPATは、1Passwordの
GitHub Personal Access Token (Develop)
というアイテムのtokenフィールドに保存しました。
PATを.zshrcへ直接書いたり、シェルのコマンドへ埋め込んだりすることは避けます。
たとえば、
export GH_TOKEN="github_pat_xxxxx"
のように自分で環境変数へ設定することもできますが、この方法ではPATを自分で安全に管理する必要があります。
今回はその代わりに、
1PasswordにPATを保存
↓
必要なときだけShell Pluginが取得
↓
GitHub CLIへ渡す
という構成にします。
続いて、1Passwordアプリ側でCLIとの連携を有効にしました。
1Passwordの
設定 → 開発者 →「1Password CLIと連携」
を有効にし、1Passwordアプリのロックも解除しておきます。
1Password - GitHub CLI Shell Plugin
3. 1Password Shell Pluginとは
1Password Shell Pluginは、CLIツールを実行するときに、1Passwordに保存してある認証情報を必要な形で渡してくれる仕組みです。
通常、
export GH_TOKEN="..."
gh api user
のように、自分で環境変数へPATを設定する方法もあります。
1Password Shell Pluginを使うと、
1Password
│
│ PATを保存
▼
Shell Plugin
│
│ ghの実行時にPATを渡す
▼
GitHub CLI
│
▼
GitHub API
という流れになります。
GitHub用のShell Pluginでは、github.com向けのPATがGH_TOKENやGITHUB_TOKENとしてGitHub CLIへ渡されます。
そのため、PATそのものを.zshrcへ直接書く必要がありません。
1Password - GitHub CLI Shell Plugin
4. GitHub CLI用のShell Pluginを設定する
Ghosttyで次のコマンドを実行しました。
op plugin init gh
このコマンドを分解すると、
op
↓
1Password CLI
plugin
↓
Shell Pluginを操作
init
↓
初期設定
gh
↓
GitHub CLI用のプラグイン
という意味になります。
つまり、
op plugin init gh
は、
GitHub CLIで利用する1Password Shell Pluginを初期設定する
ためのコマンドです。
ここで指定しているghは、単に任意のコマンド名を渡しているわけではありません。
1Password側にGitHub CLI用のShell Pluginがあらかじめ用意されています。
利用できるShell Pluginは、
op plugin list
で確認できます。
op plugin init ghを実行すると、設定中に
Locate your GitHub Personal Access Token
と表示されました。
今回はPATをすでに1Passwordへ保存していたため、
Search in 1Password
を選択しました。
その後、先ほど保存したGitHub用PATのアイテムを指定します。
利用範囲については、
Use as global default on my system
を選択しました。
これは、
このGitHub用Shell Pluginを利用するとき、選択した1Passwordアイテムをデフォルトの認証情報として利用する
という設定です。
1Password公式ドキュメントでは、この設定を選択すると、ターミナルのセッションやディレクトリをまたいでデフォルトの認証情報として利用されます。
PATそのものが無期限になるわけではありません。
今回の場合、GitHub側で設定した1年間の有効期限はそのまま残ります。
1Password - GitHub CLI Shell Plugin
5. zsh起動時にShell Pluginを読み込む
続いて、zshを起動したときに1Password Shell Pluginの設定も読み込まれるようにします。
私の環境では、~/.zshrcへ以下を追加しました。
# 1Password shell plugins (GitHub CLI)
if [[ -f "$HOME/.config/op/plugins.sh" ]]; then
source "$HOME/.config/op/plugins.sh"
fi
1Password公式ドキュメントでも、Shell Pluginを利用するにはplugins.shをsourceするよう案内されています。
また、初回設定時にop plugin initが正しいファイルパスを表示してくれるため、実際にはそのパスに合わせて設定します。
1Password - GitHub CLI Shell Plugin
このコード全体の意味は、
もし ~/.config/op/plugins.sh が存在するなら
plugins.shを現在のzshへ読み込む
です。
シェルスクリプト自体の意味も確認しておきます。
このシェルスクリプトを詳しく見る
if
if
は、「もし〜なら」という条件分岐です。
Rubyでいう、
if 条件
処理
end
に近いものです。
[[ -f ... ]]
[[ -f "$HOME/.config/op/plugins.sh" ]]
の-fは、
指定した場所に通常のファイルが存在するか
を確認しています。
つまり、
~/.config/op/plugins.shというファイルがありますか?
という条件です。
$HOME
$HOME
は、現在のユーザーのホームディレクトリを表す環境変数です。
Macであれば、
/Users/ユーザー名
のような値になります。
そのため、
"$HOME/.config/op/plugins.sh"
は、
/Users/ユーザー名/.config/op/plugins.sh
のようなパスを表します。
source
source "$HOME/.config/op/plugins.sh"
のsourceは、指定したシェルスクリプトを現在のシェルの中で読み込んで実行するコマンドです。
今回の場合、plugins.shに定義されているShell Plugin用の設定を、現在のzshへ読み込んでいます。
fi
最後の、
fi
はif文の終了を表します。
ifを逆から書いてfiになっています。
つまり全体としては、
もし ~/.config/op/plugins.sh が存在するなら
そのファイルを現在のzshに読み込む
ここまで
という意味になります。
if [[ -f ... ]]で存在確認をしているため、plugins.shが存在しない場合はsourceを実行しません。
Ghosttyを開くたびに自動で読み込まれる仕組み
この設定を~/.zshrcへ追加したことで、新しい対話的なzshを起動したときにShell Pluginの設定が読み込まれるようになりました。
正確には、Ghosttyそのものが1Password Shell Pluginを読み込んでいるわけではありません。
次のような流れです。
Ghosttyを起動
↓
zshが起動
↓
zshが ~/.zshrc を読み込む
↓
plugins.shが存在するか確認
↓
存在する
↓
source plugins.sh
↓
1Password Shell Pluginの設定を
現在のzshへ読み込む
つまり、Ghosttyはターミナルとしてzshを起動し、実際にShell Pluginの設定を読み込んでいるのはzshです。
そのため、これはGhostty専用の設定ではありません。
~/.zshrcを読み込むzsh環境であれば、別のターミナルから起動した場合にも基本的には同じ設定が利用されます。
設定後、現在開いているシェルにも反映するため、
exec zsh
を実行しました。
exec zshを実行すると、現在のシェルが新しいzshに置き換わり、~/.zshrcが再び読み込まれます。
6. なぜghを実行すると1Passwordを経由するのか
ここが今回、一番気になった部分でした。
通常、
gh
と入力すると、zshはghという名前が何を指しているのかを調べます。
たとえばHomebrewでGitHub CLIをインストールしている場合、
gh
↓
zshがghを探す
↓
/opt/homebrew/bin/gh
↓
GitHub CLIを実行
という形になります。
しかし、1Password Shell Pluginのplugins.shを読み込んだ今回の環境では、ghがシェル関数として登録されていました。
1PasswordのShell Plugins公式リポジトリでは、bash / zsh用の関数は次のような形で示されています。
gh() {
op plugin run -- gh "$@"
}
つまり、ghを入力したときに直接GitHub CLI本体を実行するのではなく、
gh
↓
Shell Plugin用のgh関数
↓
op plugin run -- gh
↓
1Passwordから認証情報を取得
↓
本物のGitHub CLIを実行
という一段階が入ります。
1Password Shell Plugins - GitHub
今回の環境をイメージすると、
gh pr list
↓
zshがghを探す
↓
Shell Plugin用のgh関数を発見
↓
op plugin run -- gh pr list
↓
1PasswordからPATを取得
↓
GH_TOKENなどとして渡す
↓
本物のGitHub CLIを実行
↓
GitHub API
という流れです。
GitHub CLIそのものに、
1Passwordがインストールされていたら、自動的に1Passwordを使う
という機能があるわけではありません。
zsh側でghという入口をShell Pluginが用意し、その入口から1Password経由で本物のGitHub CLIを実行しているという仕組みです。
また、必要に応じて、このタイミングでTouch IDなどによる1Password側の認証確認が表示されます。
これは、
GitHubへログインしてよいか
という確認ではなく、
1Passwordに保存しているPATを、このコマンドで利用してよいか
を確認するための認証です。
7. type ghでShell Pluginが読み込まれているか確認する
Shell Pluginが正しく読み込まれているか確認するため、
type gh
を実行しました。
typeは、
この名前を入力したとき、シェルが何を実行するのか
を確認するためのコマンドです。
たとえば、GitHub CLI本体を直接参照していれば、
gh is /opt/homebrew/bin/gh
のように表示されます。
これは、
gh
↓
/opt/homebrew/bin/gh
を直接実行するという意味です。
一方、今回の環境ではghがShell Plugin由来のシェル関数として表示されました。
つまり、
gh
↓
Shell Plugin用のgh関数
↓
1Password
↓
本物のgh
という経路になっていることを確認できます。
そのため、今回のtype ghは、
今の
ghがGitHub CLI本体を直接呼び出す状態なのか、それとも1Password Shell Plugin経由になっているのか
を確認するために利用しました。
8. gh api user --jq .loginで実際の認証を確認する
続いて、
gh api user --jq .login
を実行しました。
このコマンドも分解すると理解しやすくなります。
gh
gh
はGitHub CLIです。
api
gh api
は、GitHub CLIからGitHub APIを呼び出すためのコマンドです。
user
gh api user
では、現在認証しているユーザーの情報を取得します。
GitHub APIの/userエンドポイントへアクセスしています。
--jqを付けずに、
gh api user
とすると、ユーザー情報がJSON形式で返ってきます。
イメージとしては、
{
"login": "taichocop",
"id": 123456,
"name": "Taichi"
}
のようなデータです。
--jq .login
最後の、
--jq .login
は、返ってきたJSONからloginフィールドだけを取り出します。
そのため、
gh api user --jq .login
を実行すると、
taichocop
のようにGitHubのログイン名だけが表示されます。
今回の構成では、
gh api user --jq .login
↓
Shell Plugin用のgh関数
↓
1Password Shell Plugin
↓
PATをGitHub CLIへ渡す
↓
GitHub API /user
↓
認証成功
↓
loginフィールドだけ取得
という処理になります。
つまり、
type gh
では、
Shell Plugin用の
ghが読み込まれているか
を確認し、
gh api user --jq .login
では、
実際にGitHub APIへ認証してアクセスできるか
を確認しています。
今回、この2つが正常に動作することを確認できました。
gh api user --jq .loginで確認できるのは、認証された状態でGitHub APIへアクセスできることです。
gh pr createなど、個別の書き込み操作に必要な権限まで保証されるわけではありません。利用する操作に応じて、PATに必要な権限が付与されているか別途確認する必要があります。
9. gh auth statusで少し混乱したところ
私のMacには、以前gh auth loginを利用したときにmacOSのキーチェーンへ保存された、別のGitHub認証情報も残っていました。
今回行った設定は、1Passwordに保存したPATをmacOSのキーチェーンへコピーするものではありません。
そのため、
gh auth status
を確認した際に、以前保存した認証情報が「Keyring」として表示されても、
現在実行している
ghが1Password Shell Pluginを経由していない
と、それだけで判断することはできません。
GitHub CLIでは、GH_TOKENまたはGITHUB_TOKENが設定されている場合、それらの認証情報が以前保存した認証情報より優先されます。
GitHub CLI - Environment variables
一方、1PasswordのGitHub Shell Pluginは、github.com向けのPATをGH_TOKENとGITHUB_TOKENへ渡します。
1Password - GitHub CLI Shell Plugin
つまり今回の構成では、
1Password
↓
PAT
↓
GH_TOKEN / GITHUB_TOKEN
↓
GitHub CLI
という形で認証情報が渡されます。
そのため私は、
type gh
でShell Plugin用のghが読み込まれていることを確認し、
gh api user --jq .login
で実際にGitHub APIへアクセスできることを確認しました。
また、動作確認のためにPATそのものを画面へ表示する必要はありません。
認証情報を確認するときも、トークンの値そのものを表示するコマンドやオプションはできるだけ使わないようにしています。
10. 設定を保存してもPATが無期限になるわけではない
今回の設定によって、
毎回PATをコピーして環境変数へ設定しなくても、ghを1Password Shell Plugin経由で利用できる状態
になりました。
具体的には、
- GitHub CLI用Shell Pluginの設定
- 利用する1Passwordアイテム
plugins.sh-
~/.zshrcからplugins.shを読み込む設定
などが保存されています。
そのため、新しいzshを起動しても同じ設定を利用できます。
一方で、状況によっては1Passwordアプリ側でTouch IDなどによる認証確認が表示されます。
また、GitHub側で設定したPATの有効期限も変わりません。
今回の場合は1年間なので、期限が近づいたらGitHub側で新しいPATを発行し、1Passwordに保存しているアイテムを更新する必要があります。
まとめ
今回の設定によって、GhosttyからGitHub CLIを利用するときに、1Passwordに保存したPATをShell Plugin経由で利用できるようになりました。
大まかな流れは以下です。
- GitHubでfine-grained PATを発行する
- PATを1Passwordへ保存する
-
op plugin init ghでGitHub CLI用のShell Pluginを設定する -
~/.zshrcからplugins.shを読み込む - zsh起動時にShell Pluginの設定が読み込まれる
-
ghを実行するとShell Plugin用のghが呼ばれる - 1PasswordからPATが
GH_TOKENなどとしてGitHub CLIへ渡される - GitHub CLIがGitHub APIへアクセスする
-
type ghとgh api user --jq .loginで動作を確認する
今回設定していて特に理解が深まったのは、
gh
↓
直接GitHub CLI本体
ではなく、
gh
↓
Shell Plugin用のgh
↓
1Password Shell Plugin
↓
PATをGH_TOKENなどとして渡す
↓
GitHub CLI本体
↓
GitHub API
という仕組みになっている点です。
GitHub CLI自体が1Passwordを認識しているのではなく、シェル側でghの入口を用意し、1Passwordを経由して本物のGitHub CLIを実行していると理解すると、Shell Pluginの仕組みがかなり分かりやすくなりました。
PATを.zshrcなどへ直接保存する必要がなく、普段の開発環境でも扱いやすい構成になったと思います。