RailroadsプラグインのGitHubリポジトリにて、セキュリティ設定を見直しつつ、Immutable Releaseへ対応してみた

先日、JetBrains IDE向けの railroads プラグインの 0.5.1 をリリースしました。ありがたいことに、多くの方々に使っていただけているようです。
https://plugins.jetbrains.com/plugin/24076-railroads/

一方で、 railroads プラグインのリポジトリのセキュリティ設定がおろそかになっていたことから、設定を見直すことにしました。

 
どのような設定にするのが良いか調べてみたところ、次のページが参考になりました。ありがとうございました。

 
そこで、今回どんな設定を行ったかをメモとして残します。

 
目次

 

環境

  • mac

 

GitのメールアドレスをGitHubが提供しているnoreplyにする

GitHubの公式ドキュメントに従い設定します。
コミットメールアドレスを設定する - GitHub ドキュメント

% git config --global user.email "1299734+thinkAmi@users.noreply.github.com"

 

SSH鍵でコミットに署名する

Gitでは、いくつかの方法でコミットへ署名できます。

今回はGitHubでも対応しているSSH鍵をコミット署名に使います。また、既存のSSH鍵を流用せず、新しくSSH鍵を作成し、署名専用として使うことにします。
SSH コミット署名の検証 | コミット署名の検証について - GitHub ドキュメント

 
なお、Gitの初期設定はこんな感じだとします。

% git config --list                                          
credential.helper=osxkeychain
user.name=thinkAmi
user.email=1299734+thinkAmi@users.noreply.github.com

 

署名で使うSSH鍵を生成し、ssh-add --apple-load-keychain できるようにする

ドキュメントに従って生成します。今回はパスフレーズ付きで生成します。
新しい SSH キーを生成して ssh-agent に追加する - GitHub ドキュメント

% ssh-keygen -t ed25519 -C "1299734+thinkAmi@users.noreply.github.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/thinkami/.ssh/id_ed25519): /Users/thinkami/.ssh/id_github_thinkami
Enter passphrase for "/Users/thinkami/.ssh/id_github_thinkami" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/thinkami/.ssh/id_github_thinkami
Your public key has been saved in /Users/thinkami/.ssh/id_github_thinkami.pub
The key fingerprint is:
SHA256:ClXy4N6R80jCjpLcQgEyX25K6xtnCMnEokC8V8o353Q
1299734+thinkAmi@users.noreply.github.com
The key's randomart image is:

 
次に、作成したパスフレーズ付きでSSH鍵を ssh-agent へ追加します。
Adding your SSH key to the ssh-agent | 新しい SSH キーを生成して ssh-agent に追加する - GitHub ドキュメント

% ssh-add --apple-use-keychain ~/.ssh/id_github_thinkami
Enter passphrase for /Users/thinkami/.ssh/id_github_thinkami: 
Identity added: /Users/thinkami/.ssh/id_github_thinkami

 
署名の都度パスフレーズを入力するのは手間なこと、OSを起動するたびに ssh-add --apple-use-keychain するのは手間なので、設定を行います。

接続用のSSH鍵と共用する場合は .ssh/config へエントリを追加すれば良さそうですが、今回はGitのコミット署名用のSSH鍵なので、その方法が使えません。

man ssh-add すると

--apple-load-keychain
     Add identities to the agent using any passphrase stored in the user's keychain.

との記載がありました。また、以下のブログでも --apple-load-keychain が使えそうな記載があります。
Macのssh-agent | 2022年4月 – Mad Overload

 
そこで、 .zprofile へ --apple-load-keychain を使ってキーチェーンからロードするよう記載します。

なお、ssh agentを使える環境で実行したい、かつ、追加が失敗してもスルーしたいことから、if を入れたり /dev/null へ捨てたりしています。

if [[ -n "$SSH_AUTH_SOCK" ]]; then
  /usr/bin/ssh-add --apple-load-keychain ~/.ssh/id_github_thinkami >/dev/null 2>&1 || true
fi

 

SSH鍵を署名だけで使うようGitHubへ設定する

続いて、SSH鍵をGitHubへと設定します。
GitHub アカウントへの新しい SSH キーの追加 - GitHub ドキュメント

 
SSH鍵の公開鍵をクリップボードへコピーします。

% pbcopy < ~/.ssh/id_github_thinkami.pub

 
GitHubのSSH Keysに Signing keys として保存します。

 

Gitの署名でSSH鍵を使う設定にする

こちらもGitHubの公式ドキュメントに従って設定します。
Git に SSH キーについて知らせる | Git へ署名キーを伝える - GitHub ドキュメント

% git config --global gpg.format ssh

% git config --global user.signingkey ~/.ssh/id_github_thinkami.pub

 
設定できているか確認します。

% git config --list
# ...
user.signingkey=/Users/thinkami/.ssh/id_github_thinkami.pub
# ...
gpg.format=ssh

 
設定できたので、実際に試してみます。
コミットに署名する - GitHub ドキュメント

# 署名なし
% git commit --allow-empty -m "test"        
[tmp/signing a1d6a91] test

# 署名あり
% git commit --allow-empty -S -m "test sign"
[tmp/signing 6acd216] test sign

 
コミットをpushし、GitHubのコミット履歴を見ると、署名の有無で表示が異なっています。

 
ここで、ローカルでコミット時の署名を --show-signature を使って確認すると、エラーが出ていました。

% git log -2 --show-signature
error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification
commit 6acd2161feb4894ec546b6ac8862d8907f71820d (HEAD -> tmp/signing, origin/tmp/signing)
No signature

 
そこで、ローカルの検証用に allowedSignersFile を設定します。
gpg.ssh.allowedSignersFile | Git - git-config Documentation

 
allowed_signers ファイルの置き場所はどこでも良さそうなので、GitLabやBitbucketのドキュメントにならい、 .ssh ディレクトリの中に置きます。

 
最初はファイルが存在しないため、新規作成しつつ、エントリを追加します。

% echo "1299734+thinkAmi@users.noreply.github.com $(cat ~/.ssh/id_github_thinkami.pub)" > ~/.ssh/allowed_signers

 
続いて、Gitへ設定します。

git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

 
再度確認すると、署名がある方のみ Good が出ていました。

% git log -2 --show-signature --oneline
6acd216 (HEAD -> tmp/signing, origin/tmp/signing) Good "git" signature for *** with ED25519 key SHA256:***
test sign

a1d6a91 test

 
良さそうなので、常に署名するよう、Gitで設定します。

git config --global commit.gpgsign true

 
動作確認します。良さそうです。

% git log -3 --show-signature --oneline        
19a74d2 (HEAD -> tmp/signing) Good "git" signature for ***
without S option

6acd216 (origin/tmp/signing) Good "git" signature for ***
test sign

a1d6a91 test

 

GitHubのドキュメントに従った設定見直し

GitHubのクイックスタートページと照らし合わせながら設定を見直します。
リポジトリを保護するためのクイック スタート - GitHub ドキュメント

 

リポジトリへのアクセスの管理

元々publicなリポジトリなので、見直すことはありませんでした。

 

依存関係グラフの管理

有効化してあったため、そのままにしました。

 

Dependabot alertsの管理

Dependabotは有効化してあったので、そのままにしました。

 

依存関係レビューの管理

ドキュメントに従って設定項目を探しましたが、それらしい設定が見当たらなかったので、特に対応しませんでした。

 

Dependabot security updatesの管理

有効化しました。

また、Dependabotが作る脆弱性のプルリクはある程度まとまっていたほうが良さそうと感じたことから、 Grouped security updates も有効化しました。

 

Dependabot version updatesの管理

最新のものを適用すると変更が大きいことから、今回は見送りました。

 

Code Security の構成

CodeQL analysis を有効化し、 Default を設定しました。

 

Secret Protectionの構成

有効化しました。

また、 Push protection も有効化しました。

 

shibayu36さんのチェックリストに従った設定

shibayu36さんが公開されているGitHub OSSセキュリティ最低限セットに従い、次のことを追加で行いました。
自分のOSSリポジトリにGitHubのセキュリティ設定を入れ、自分用の手順書を作った - $shibayu36->blog;

  • Private vulnerability reporting / Security Advisories を有効化
  • rulesets への追加
    • main ブランチを保護
      • 元々 main ブランチは保護していた
      • main ブランチとバージョンタグを使ってプラグイン公開できるようなGitHub Actionsを設定しているため、より強固な設定を追加
      • 内容はshibayu36さん同様
    • バージョンtagの保護
      • main ブランチの保護と同様

なお、 SECURITY.md はいったん保留にしました。

 

lufiabbさんの記事に従った設定

なお、「Actionsのバージョンをハッシュで強制する」については、railroadsプラグインリポジトリの元となるテンプレート JetBrains/intellij-platform-plugin-template が対応していなかったため、現時点では見送りました。

 

Immutable release実現のため、GitHub Actionsを見直し

railroadsはGitHubのRelease機能を使ってJetBrainsマーケットプレイスへプラグインを公開しています。

もし、Immutable Releaseにすれば、リポジトリのReleaseにあるプラグインとマーケットプレイスへ公開したプラグインは同一になり、変更することもできません。

そのため、Immutable Releaseにしたいのですが、JetBrainsのプラグインテンプレートにあるGitHub Actionsが v2.4.0 の時点では、「プラグインを公開後に、GitHubのReleaseのアセットを gh release upload で更新する」という作りになっています。
https://github.com/JetBrains/intellij-platform-plugin-template/blob/2.4.0/.github/workflows/release.yml

これより、取れる方法としては次の2つでした。

  • JetBrainsのプラグインテンプレートとの互換性を重視し、Immutable Releaseは諦める
  • JetBrainsのプラグインテンプレートとの互換性は諦め、Immutable Releaseを導入する

 
JetBrainsのプラグインテンプレートへの追随を容易にするのを優先することを考えましたが、

  • 今のご時世的に、Immutable Releaseのほうが良さそう
  • AIもいるし、プラグインテンプレートへの追随はなんとかなりそう

となり、互換性は諦めて Immutable Releaseを導入することにしました。

 

Immutable releaseを導入するための設計

lufiabbさんの記事では「Immutable Releasesでアセットを追加する」に導入方法が記載されています。
Immutable Releasesでアセットを追加する | GitHubでサプライチェーン攻撃を防ぐ設定 - Plan 9とGo言語のブログ

 
railroadsのGitHub Actionsでは次の作りとなっていました。

  • build.yml の最後で Draft releaseを作る
  • release.yml のプラグインを公開した後に、Releaseの成果物を入れ替える

 
そこで、次の流れにしようと考えました。

  • build.yml
    • build ステップで、ビルドした成果物をアップロードする
    • releaseDraft ステップで、ビルドした成果物を添付してDraftリリースを作る
  • release.yml
    • Releaseの成果物を入れ替えるステップ自体を削除する

 

GITHUB_TOKENによるgh pr createではCodeQLが動作しないので検討

これで良さそうとプルリクを眺めたところ、 release.yml の最後で作成する CHANGELOG.md を更新するプルリクエストに問題が出ました。

そのプルリクは gh pr create で作っているのですが、CodeQLが動作しませんでした。これにより、「 CodeQL analysis を有効」による「CodeQLによるチェックがパスすること」というプルリクのマージ可能条件が満たせず、マージできなくなっていました。

調べたところ、GitHubの仕様でした。

GITHUB_TOKEN を使ってプルリクを作る場合、GitHub Actionsによる操作は別のGitHub Actionsを実行できなくなっていることから、 CodeQLの pull_request トリガーが起動しなかったようです。
gh pr create don't trigger pullrequest: actions · cli/cli · Discussion #6575

 
ここで、そもそも CHANGELOG.md を更新するプルリクは不要かもしれないのではと考えました。

しかし、プラグインテンプレートでは「 CHANGELOG.md の内容を元に、JetBrains MarketplaceのWhat’s New へ表示される」という挙動を想定した作りとなっています。

この作りを変えるのはちょっと手間そうに感じたため、やはり CHANGELOG.md を更新するプルリク作成は残しておくことにしました。

 
さらに、次の記事にあるように GITHUB_TOKEN を使うのではなく、GitHub App Tokenを使ってプルリクを作ることを考えました。
GitHub Actions上でPRを作る時はsecrets.GITHUB_TOKENは使わない方がよさそう - くりにっき

しかし、「 CHANGELOG.md を更新する」は、プラグインの新しいバージョンをリリースする時くらいしか発生しません。

今のところrailroadsプラグインの機能充実は考えておらず、今後もAIエージェントの発展を考えると(残念ながら)機能充実は求められなさそうな気がします。

このような状況では、GitHub App Tokenという管理する秘匿情報を増やしてまで、運用を楽にする必要がなさそうでした。

 
そこで、「プラグインを公開したときに作られる CHANGELOG.md を更新するプルリクは、手動で空行などを加えてコミットすることで、CodeQLを実行させる」という運用でカバーすることにしました。

 

Draftリリースの作成・削除を調整

現在のワークフローでは gradle.properties にある pluginVersion に従って、Draftリリースのタグとバージョンを作成します。

しかし、次のような挙動が発生し得るのが気になりました。

  • 最新バージョンが 0.5.1 の時、 0.5.2 → 0.6.0 → 0.5.3 とプラグインの未公開バージョンを重ねた時、Draftリリースが残り続ける
  • 既存タグの削除、古いActionsの再実行、実行中に main ブランチが進んだときなどに、タグ先のコミットとDraftに添付した成果物の内容がズレる

 
今まではプラグインのテンプレートに準じた動きとしていましたが、今回テンプレートとの互換性を諦めることから、このあたりにも手を入れることにしました。

そこで、次の対応を含めることにしました。

  • Draftリリースを作る際、次を行う
    • 既存のDraftリリースはすべて削除する
      • 複数のバージョンのプラグインを同時にリリースする運用がないため
    • 次の状況の場合、Draftリリースを作るのを止める
      • JetBrainsマーケットプレイスへ公開済のバージョンでDraftリリースしようとした場合
      • Draftリリースに添付する成果物について、タグに紐づくSHAと異なっていた場合

 
この結果、 build.yml で使っているシェルがやや複雑になってしまいましたが、やむを得ないものとしました。

また、何をしたかったのかを忘れないよう docs/release-process.md へ内容を残しました。

 
ここまでの作業を行った結果、Immutable Releaseができるようになったと考えられたため、 Enable release immutability へチェックを入れました。

 

Approval for running fork pull request workflows from contributors の設定変更 (2026/5/17 追記)

サプライチェーン攻撃が流行し、GitHub Actionsのワークフローが悪用されることも目にしています。

このリポジトリでは基本的に自分しか機能追加を予定していないことから、 Approval for running fork pull request workflows from contributors の設定を変更することにしました。

変更先は Require approval for all external contributors とし、承認があるまではCIが走らないようにしています。

 

おわりに

以上が今回見直して対応した内容です。

今後も何かあれば適宜修正していこうと思います。