署名付きコミットを行う GitHub Actions を org 内の複数リポジトリで共有する
背景
リリースワークフローでは、バージョン番号を更新してブランチにコミットする処理を CI が自動で行っています。このコミットは git commit でその場で作るだけなので署名が付かず、人間が手元で作業するコミットとは違って Unverified のまま残ります。GPG 鍵を Actions のシークレットに埋め込む方法もありますが、鍵の管理は避けたいところです。
鍵を持たずに Verified バッジを付ける方法がないか探していたところ、GitHub Actions で署名付きコミットを行う という記事に仕組みが解説されていました。実際に手元でも試してみて、composite action として切り出し、org 内の複数リポジトリから使い回してみました。
GitHub API 経由なら鍵を持たずに署名できる
参考記事はこの仕組みを解説しています。GitHub API の POST /repos/{owner}/{repo}/git/commits を使うと、ローカルで秘密鍵を扱わなくても、コミットの作成自体を GitHub 側に任せられます。この API はローカルの git commit を経由しません。差分の中身をあらかじめ一時的な ref として push しておき、その tree オブジェクトを指定してコミットを作り直します。
この action を org 内の複数リポジトリから参照する構成にすると、次のような形になります。
action の実装
この手順を composite action にまとめ、専用のリポジトリ直下に create-verified-commit/action.yml として配置しました。
name: Create verified commit
description: staged 済みの変更から署名付きコミットを作成し、指定ブランチに反映する
inputs:
commit-message:
description: コミットメッセージ
required: true
branch:
description: コミットを反映するブランチ名
required: true
force:
description: 強制上書きするか
required: false
default: 'false'
outputs:
commit-sha:
description: 作成した署名付きコミットの SHA
value: ${{ steps.create.outputs.commit_sha }}
runs:
using: composite
steps:
- id: create
shell: bash
env:
GH_TOKEN: ${{ github.token }}
COMMIT_MESSAGE: ${{ inputs.commit-message }}
BRANCH_NAME: ${{ inputs.branch }}
FORCE: ${{ inputs.force }}
GIT_AUTHOR_NAME: github-actions[bot]
GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
run: |
BASE_SHA=$(git rev-parse @)
git commit -m "${COMMIT_MESSAGE}"
TREE_SHA=$(git rev-parse @^{tree})
TEMP_REF="refs/create-verified-commit/temp-${GITHUB_RUN_ID}"
git update-ref "${TEMP_REF}" @
git push origin "${TEMP_REF}" --force
NEW_COMMIT_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/git/commits" \
-f message="${COMMIT_MESSAGE}" \
-f tree="${TREE_SHA}" \
-F parents[]="${BASE_SHA}" --jq '.sha')
if gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${BRANCH_NAME}" >/dev/null 2>&1; then
gh api "repos/${GITHUB_REPOSITORY}/git/refs/heads/${BRANCH_NAME}" \
-X PATCH \
-f sha="${NEW_COMMIT_SHA}" \
-F force="${FORCE}" >/dev/null
else
gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/heads/${BRANCH_NAME}" \
-f sha="${NEW_COMMIT_SHA}" >/dev/null
fi
git push origin --delete "${TEMP_REF}"
echo "commit_sha=${NEW_COMMIT_SHA}" >> "$GITHUB_OUTPUT"
複数リポジトリで参照する
この action を単体のリポジトリに置いたままだと、他のリポジトリからは使えません。composite action を専用のリポジトリに切り出しておけば、呼び出し側は通常の uses で参照するだけで済みます。
実際に動くかどうかは、設定を読んだだけでは分かりません。workflow_dispatch で手動実行できる検証用ワークフローを別リポジトリに用意し、ダミーファイルを 1 行書き換えてコミットするだけの最小構成で試しました。
name: Verify Signed Commit
on:
workflow_dispatch:
jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Update file
run: |
date -u +"%Y-%m-%dT%H:%M:%SZ" > version.txt
git add version.txt
- name: Create verified commit
uses: <owner>/<action-repo>/create-verified-commit@main
with:
commit-message: "chore: verify signed commit"
branch: ${{ github.ref_name }}
複数リポジトリから同じ action を参照する構成と、GitHub API 経由の署名付きコミットの両方を確認できました。
まとめ
CI が作るコミットに署名を求められる場面は、リリース作業に限りません。自動整形やドキュメント生成でも意外とよく出てきます。鍵の管理に頭を悩ませる前に、同じような状況に出会ったら試してみてください。