本文へスキップ

署名付きコミットを行う 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 内の複数リポジトリから参照する構成にすると、次のような形になります。

org 内での署名付きコミット action 共有の構成図

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 }}
github-actions[bot] が作成したコミットに Verified バッジが付いている

複数リポジトリから同じ action を参照する構成と、GitHub API 経由の署名付きコミットの両方を確認できました。

まとめ

CI が作るコミットに署名を求められる場面は、リリース作業に限りません。自動整形やドキュメント生成でも意外とよく出てきます。鍵の管理に頭を悩ませる前に、同じような状況に出会ったら試してみてください。

参考リンク

この記事をシェア