8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actionsで署名付きコミットを行う

Last updated at Posted at 2025-12-07

GitHub Actionsで署名付きコミットを行う

GitHubでコミットの横に表示される 「Verified(検証済み)」 バッジは、その変更が信頼できるソースから行われたものであることを保証するマークです。1

verified-commit.png

署名は他人による「なりすまし」を防ぎます。企業のセキュリティコンプライアンスにおいて重要であるだけでなく、個人のOSS活動においても、自分が書いたコードであることを証明し信頼性を高めるために役立ちます。一般的にはGPG鍵を用いて署名を行うことが多いでしょう。

しかし、バージョン更新や自動整形などのコミットを CI/CDで自動化したい場合、いくつかの壁に直面します。

自分でGPG鍵を管理してCIに埋め込むのは煩雑になりがちです。また、「署名付きコミットを必須(Require signed commits)」に設定しているリポジトリでは、CIからの自動コミットがブロックされてしまうという課題もあります。

そこでこの記事では、面倒な鍵管理を行わずに、GitHub APIとGitの仕様をうまく活用して、CI上で署名付きコミットを作成する方法を紹介します。

なぜ鍵なしで署名できるのか?

通常、ローカルでコミットに署名するには秘密鍵が必要です。しかし、GitHub API経由で作成されたコミットには、GitHubがシステム鍵で自動的に署名を行うという仕様があります。Webブラウザ上でファイルを編集したときにVerifiedになるのと同じ仕組みです。

この仕様を利用し、以下の2ステップに分けることで問題を解決します。

  1. データ転送: ファイルの実体(Blob)とディレクトリ構造(Tree)だけを先にGitHubへ送る。
  2. 署名再作成: データが揃った状態で、コミットの作成(署名)だけをGitHub APIに依頼する。

前提知識:Gitオブジェクトの構造

この仕組みを理解するために、Gitがデータをどのように管理しているかを簡単に見てみましょう。Gitのコミットは複数の「オブジェクト」で構成されており、さらにそれらを指し示す「参照(Ref)」が存在します。

  • Blob: ファイルの中身そのものです
  • Tree: ディレクトリ構造を表します
  • Commit: 「誰が、いつ、なぜ」変更したかという情報と、その時点のTreeおよび親コミットへのリンクを持ちます
  • Ref: ブランチやタグのことです。特定のCommitを指し示すポインタ(ラベル)の役割を果たします

今回の手法では、この構造を利用し、「BlobとTree(データそのもの)」と「Commit(署名の対象)」を分けて扱います。

実装のポイント:どうやってデータを送るか

ここで一つ技術的な課題があります。「署名付きコミット必須」のリポジトリでは、署名のないコミットを含むプッシュはブランチ保護ルールにより拒否されてしまいます。

そこで、Gitの「Ref(参照)」の仕組みを利用して回避します。2
通常、ブランチ保護ルールは refs/heads/*(ブランチ)に対して適用されます。裏を返せば、ブランチではない一時的なRefへのプッシュであれば、制限を受けずにGitオブジェクト(Commit, Tree, Blob)をアップロードできます。

全体のデータフローは以下のようになります。

一時的なRef(refs/tmp/foo)を経由させることで、ブランチ保護を回避して必要なデータをGitHub側に渡し、その後APIを使って「署名付きコミット」として作り直すアプローチです。3

実装コード

1. Composite Action の作成

再利用可能なアクションとして .github/actions/create-signed-commit/action.yml を作成します。

.github/actions/create-signed-commit/action.yml
name: 'Create Signed Commit'
description: '指定された参照を元に、GitHub APIを使用してVerifiedなコミットを作成し、新しいブランチを作成します'

inputs:
  branch_name:
    description: '作成するブランチ名 (例: feature/foo)'
    required: true
  github_token:
    description: 'GitHub API操作用のトークン'
    required: true
  ref:
    description: '対象とするローカルコミットの参照'
    required: false
    default: 'HEAD'

outputs:
  commit_sha:
    description: '作成された署名付きコミットのSHA'
    value: ${{ steps.api_commit.outputs.commit_sha }}

runs:
  using: "composite"
  steps:
    - name: Push commit objects via temporary ref
      id: push_temp_ref
      shell: bash
      env:
        BRANCH_NAME: ${{ inputs.branch_name }}
        TARGET_REF: ${{ inputs.ref }}
      run: |
        COMMIT_SHA=$(git rev-parse "${TARGET_REF}")

        # GitHub側でTreeオブジェクトを参照可能にするため、一時的なRefへプッシュ
        TEMP_REF="refs/create-verified-commit/branches/${BRANCH_NAME}"
        git update-ref "${TEMP_REF}" "${COMMIT_SHA}"
        git push origin "${TEMP_REF}" --force

        echo "temp_ref=${TEMP_REF}" >> "$GITHUB_OUTPUT"

    - name: Re-create commit and branch using gh API
      id: api_commit
      shell: bash
      env:
        GH_TOKEN: ${{ inputs.github_token }}
        GH_REPO: ${{ github.repository }}
        BRANCH_NAME: ${{ inputs.branch_name }}
        TARGET_REF: ${{ inputs.ref }}
        TEMP_REF: ${{ steps.push_temp_ref.outputs.temp_ref }}
      run: |
        # 必要な情報を抽出 (Tree, Parent, Message)
        TREE_SHA=$(git rev-parse "${TARGET_REF}^{tree}")
        PARENT_SHA=$(git rev-parse "${TARGET_REF}^")
        COMMIT_MESSAGE=$(git log -1 --pretty=%B "${TARGET_REF}")

        # GitHub APIで署名付きコミットを作成 (Verifiedになる)
        NEW_COMMIT_SHA=$(gh api "repos/${GH_REPO}/git/commits" \
          -f message="${COMMIT_MESSAGE}" \
          -f tree="${TREE_SHA}" \
          -F parents[]="${PARENT_SHA}" \
          --jq '.sha')

        # 新しいコミットを指すブランチを作成
        gh api "repos/${GH_REPO}/git/refs" \
          -f ref="refs/heads/${BRANCH_NAME}" \
          -f sha="${NEW_COMMIT_SHA}" >/dev/null

        # 後処理: 一時参照の削除
        git push origin --delete "${TEMP_REF}"

        echo "commit_sha=${NEW_COMMIT_SHA}" >> "$GITHUB_OUTPUT"

GitHub APIを呼び出すのにGitHub CLIを使用しています。Self-hosted runnersなどのGitHub CLIが標準的に備わっていない環境では actions/github-script を使用してもよいでしょう。

2. 利用例:ワークフローへの組み込み

実際にバージョンファイルを更新する利用例です。

.github/workflows/bump-version.yml
name: Bump Version Example

on:
  workflow_dispatch:

permissions:
  contents: write

jobs:
  bump-version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

      - name: Configure Git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      # 1. バージョン更新とローカルコミット(未署名)
      - name: Create local commit (Unsigned)
        run: |
          NEW_VERSION="1.2.3"
          BRANCH_NAME="feature/bump-version-${NEW_VERSION}"

          # バージョンファイルの更新 (例として上書き)
          echo "${NEW_VERSION}" > VERSION
          
          # ブランチを作成してコミット
          git checkout -b "$BRANCH_NAME"
          git add VERSION
          git commit -m "Bump version to ${NEW_VERSION}"

          # 後のステップで使うために環境変数へ設定
          echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV"

      # 2. 作成したアクションを呼び出し、署名付きコミットとしてプッシュする
      - name: Create signed commit and push branch
        uses: ./.github/actions/create-signed-commit
        with:
          branch_name: ${{ env.BRANCH_NAME }}
          github_token: ${{ github.token }}
          ref: ${{ env.BRANCH_NAME }}

実行結果

実行結果例です。Verifiedバッジが付きました。

result.png

まとめ

本記事では、鍵管理を行わずにGitHub Actionsから署名付きコミットを作成する方法を紹介しました。

コミット署名はOSSなどにおいてメンテナやBotによる変更の真正性を保証します。今回紹介した手法は特に「署名付きコミットが必須」という厳格なルール運用下でも、比較的容易にBotによる自動化を実現できる点が大きなメリットです。

バージョン更新、自動フォーマット、ドキュメント生成など、様々な自動化ワークフローでぜひ活用してみてください。

参考情報

  1. 画像はコミット署名の検証についてより引用

  2. Renovate の実装 をヒントにしました

  3. GraphQL API - A simpler API for authoring commits を使用する手もあります(より冗長)

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?