httpプロトコルでもgit認証情報を保持する (nixos設定も)

本記事は[クラスター Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cluster)（2ページ目）の6日目の記事になります。
前回は [@Swanman](https://qiita.com/Swanman) さんの「[# Slackで同じワークフローのショートカットを複数チャンネルに設置する](https://qiita.com/Swanman/items/c7ebc94f7ee5e2418d00)」でした。
SwanmanさんのCSとは思えない技術力とユニークな着眼点はいつも楽しませてもらってます。

さて1日目に引き続き僕です。
本記事はnixosにおけるhttp protocolのcredential helper設定で詰まってしまったので、その解決方法になります。

## httpsプロトコルでの認証

httpsプロトコルで`git push`をすると毎回usernameとpasswordを聞かれて面倒。という問題がまれによくあります。

```
Username for 'https://github.com':
```

このエラーで検索すると「sshプロトコルに変える」という解決策をよく見受けられます。
しかし、githubでは公式にhttpsを勧めているようでsshにはしたくありません。

ちなみにgithubがhttpsを勧める理由はsshはfirewallの対象になることが多いためだそうです。
(またgitとブラウザで使用するurlが1つに統一されるためシンプルになる。という記載もあった記憶があります)
firewallが不要な状況下ではsshプロトコルは選択肢として入れてよいかと思います。

さて、httpsでusername, passwordを保存するとなると大きく2つの方法に別れます
認証ツールを利用するか[ssh over httpsを利用する方法](https://docs.github.com/ja/authentication/troubleshooting-ssh/using-ssh-over-the-https-port)です

本記事では認証ツール利用する方法を説明します。

## 認証ツールを使用する

公式では[gh](https://cli.github.com/)と[GMC](https://github.com/GitCredentialManager/git-credential-manager)を推しています。
ここではghのインストール方法を説明します

```
$ brew install gh
```

`gh auth login` はオプションを指定しなければインタラクティブに指定ができます。

```
$ gh auth login
? What account do you want to log into? GitHub.com
? You're already logged into github.com. Do you want to re-authenticate? Yes
? What is your preferred protocol for Git operations? HTTPS
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: XXXX-XXXX
Press Enter to open github.com in your browser...
```

最後エンターを押してデフォルトブラウザを起動後、生成されたワンタイムパスワードを挿入したらログイン完了です。

認証状況を確認します。
```
$ gh auth status
 ✓ Logged in to github.com as myoan (oauth_token)
 ✓ Git operations for github.com configured to use https protocol.
 ✓ Token: *******************
```

この状態で`git push` をするとusername, passwordは聞かれないと思います。
聞かれば場合は、下記コマンドを実行します。

```
$ gh auth setup-git
```

## git-credential の仕組み

gitの認証はconfigの`git.credential.helper`によって設定されます

>-   デフォルトでは、なにもキャッシュされません。 接続するたび、ユーザー名とパスワードを尋ねられます。
>- “cache” モードにすると、認証情報が一定の間だけメモリーに記憶されます。 パスワードはディスクには保存されません。15分経つとメモリーから除去されます。
>- “store” モードにすると、認証情報がテキストファイルでディスクに保存されます。有効期限はありません。 ということは、パスワードを変更するまで、認証情報を入力しなくて済むのです。 ただし、パスワードが暗号化なしのテキストファイルでホームディレクトリに保存される、というデメリットがあります。
>-   Mac を使っているなら、Git の “osxkeychain” モードが使えます。これを使うと、OS のキーチェーン（システムアカウントと紐づく）に認証情報がキャッシュされます。 このモードでも認証情報がディスクに保存され、有効期限切れもありません。ただし先ほどとは違い、保存内容は暗号化（HTTPS 証明書や Safari の自動入力の暗号化と同じ仕組み）されます。

[## 認証情報の保存](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BF%9D%E5%AD%98)

認証機能はgitには実装されておらず、外部コマンドに任されています。
たとえば`credential.helper = store`の場合 `git credential-store`を実行します
これによってghやGCMといった外部ツールが認証をすることを可能にしています。

ちなみに`gh auth setup-git` はgitの設定を更新するコマンドで下記のようにhelperを更新します。
```
$ cat ~/.gitconfig
...
[credential "https://github.com"]
  helper = "!gh auth git-credential"
...
```

ここで特徴的なのはコマンドの前に`!`がついていることです。
ドキュメントによると

> `!` 以降のコードがシェルで評価される

とあります。したがって`gh auth git-credential`が実行されているのです
(!がなければ git credential-gh を実行しようとする)

## nixosでghを使った認証を行う

nixosで`gh auth setup-git`を実行してもread-onlyのため書き込みに失敗します。
そこでhome-managerに直接helperの設定を書き出すことで解決できます。

```
programs.git = {
  ...
  extraConfig = {
    credential."https://github.com".helper = "!gh auth git-credential";
  }
}
```

上記設定はnixosによって下記のように展開されます
```
[credential "https://github.com"]
  helper = "!gh auth git-credential"
```

サブセクションにURLがはいるのが肝です。

## まとめ

「!以降のコードがシェルで実行される」がわからず時間を消費してしまいました。
(shebangの方言かと思ったり...)
結局いつものドキュメントをちゃんと読め案件でした。
しかしおかげでgitの認証の仕組みやhome-managerの書き方に詳しくなれました。