Skip to content
arrow-up-right

GitHub Action

Authenticates a Merge Request

v1.0.1 Latest version

Authenticates a Merge Request

arrow-up-right

Authenticates a Merge Request

Authenticates a merge request by checking that the commits are authorized by the repository's signing policy.

Installation

Copy and paste the following snippet into your .yml file.

              

- name: Authenticates a Merge Request

uses: sequoia-pgp/[email protected]

Learn more about this action in sequoia-pgp/authenticate-commits

Choose a version

Authenticate Commits

This repository contains a GitHub action that authenticates commits according to a signing policy. The policy is saved in the repository, which allows it to evolve just like the rest of the repository's content. The action is powered by Sequoia git, which specifies a set of semantics, defines a policy language, and provides a set of tools to manage a policy file, and authenticate commits.

Screenshot of a video demonstrating the action

Introduction

A version control system like git doesn't just track changes, it also provides a record of who made those changes. This information can be used to check that commits are authorized, which can improve software supply chain security. In particular, checking a change's provenance can be used to remove intermediaries like forges, and package registries from a user's trusted computing base. But, authorship information can easily be forged.

Screenshot of a blog post about impersonating Linus Torvalds

An obvious solution to prevent forgeries would be to require that commits are digitally signed. But by itself a valid digital signature doesn't prevent forgeries. The certificate that was used to make the signature could claim to be one of the project's maintainers. What is needed is not only a list of entities who are allowed to modify the repository, but also the keys they use to sign the commits. In other words, to authenticate a commit we need a signing policy, which says what keys are authorized to make changes.

Creating a policy isn't complicated. A project's maintainers could curate a list of entities who are allowed to add commits, and enumerate the certificates they use to sign them. The tricky part is applying the policy. There are a number of edge cases that need to be handled like how merge changes from external contributions, who is allowed to change the policy, and how to deal with compromised keys.

Sequoia git is a project that specifies a set of semantics, defines a policy language, and provides a set of tools to manage a policy file, and authenticate commits.

Using Sequoia git is relatively straightforward. You start by adding a policy file, openpgp-policy.toml, to your project's repository. The policy is maintained in band to allow it to evolve, just like the rest of the project. The openpgp-policy.toml file is basically a list of OpenPGP certificates and the type of changes they are authorized to make. sq-git can help you create it.

Then, before you merge a pull request, you check that commits are authorized by the policy. Locally, this is done by running sq-git log on the range of commits that you want to push. A commit is considered authorized if the commit has a valid signature, and at least one immediate parent's policy allows the signer to make that type of change. Projects hosted on GitHub can use this action to automatically check that a pull request is authorized when it is opened, or updated.

Downstream users can use Sequoia git to check that there is a chain of trust from an older, known-good version of the software to a new version. This helps prevent the use of versions that include modifications that weren't authorized by the project's maintainers.

See the project's documentation for more details.

The authenticate-commits Action

To use Sequoia git, you'll need to sign all of your commits. You can configure git to always sign your commits as follows:

$ git config --global user.signingkey AACB3243630052D9
$ git config --global commit.gpgsign true

See also GitHub's guide.

You then add a policy file (openpgp-policy.toml) to the root of your git repository The policy file authorizes OpenPGP certificates to make different types of changes. For example:

$ sq-git policy authorize  --project-maintainer "Neal H. Walfield <[email protected]>" AACB3243630052D9
$ git add openpgp-policy.toml
$ git commit -m 'Add signing policy.'
$ git push origin main:signing-policy
$ # Merge via a pull request.

A committer (--committer) is authorized to add changes to the repository; a release manager (--release-manager) is also authorized to sign tags and archives; and, a project maintainer (--project-maintainer) can authorize and retire users, and good list commits.

Before you merge a change you run sq-git log to make sure it is authorized. Add this workflow to .github/workflows/authenticate-commits.yml to run sq-git whenever a pull request is opened or updated:

name: authenticate-commits
on:
  pull_request:
    types: [opened, reopened, synchronize]
jobs:
  authenticate-commits:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      pull-requests: write
      issues: write

    steps:
      - name: Authenticating commits
        uses: sequoia-pgp/authenticate-commits@v1
        with:
          # To reduce the workflow's verbosity, use 'on-error'
          # to only post a comment when an error occurs, or 'never' to
          # never post a comment.  (In all cases the information is
          # still available in the step's summary.)
          comment: always

The workflow's results are posted as a comment on the pull request.

Merging Pull Requests

GitHub's web UX provides three different strategies for merging a pull request.

Screenshot of GitHub's Merge pull request options: "Create a merge commit", "Squash and merge", and "Rebase and merge"

Unfortunately, none of them are compatible with Sequoia git, because all three merge strategies modify the pull request in some way. In particular, even Rebase and merge unconditionally rewrites the commits by changing each commit's committer field. That is, it does the equivalent of git rebase --no-ff. This results in the commits having a different hash, and destroys any signatures.

With a bit of work, it is possible to prevent GitHub from modifying the commits. Specifically, it is possible to push changes from a pull request directly to the target branch after any checks have passed. Consider:

$ # We can't directly push to main, because it is protected.
$ git push origin
...
remote: error: GH006: Protected branch update failed for refs/heads/main.
...
$ # We can create a PR, wait for the CI checks to pass, then push directly to main.
$ git push origin HEAD:workwork
$ git push origin

But, this approach isn't very convenient.

The sequoia-pgp/fast-forward action improves the situation a bit by making it possible to fast forward directly from the web UX by posting a comment containing /fast-forward to the pull request.

Add .github/workflows/pull_request.yml to your repository to check that whenever a pull request is opened or updated it can be fast forwarded:

name: pull-request
on:
  pull_request:
    types: [opened, reopened, synchronize]
jobs:
  check-fast-forward:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      # We appear to need write permission for both pull-requests and
      # issues in order to post a comment to a pull request.
      pull-requests: write
      issues: write

    steps:
      - name: Checking if fast forwarding is possible
        uses: sequoia-pgp/fast-forward@v1
        with:
          merge: false
          # To reduce the workflow's verbosity, use 'on-error'
          # to only post a comment when an error occurs, or 'never' to
          # never post a comment.  (In all cases the information is
          # still available in the step's summary.)
          comment: always

And, to actually fast-forward a branch from the GitHub UX, add .github/workflows/fast-forward.yml to your repository with the following contents:

name: fast-forward
on:
  issue_comment:
    types: [created, edited]
jobs:
  fast-forward:
    # Only run if the comment contains the /fast-forward command.
    if: ${{ contains(github.event.comment.body, '/fast-forward')
            && github.event.issue.pull_request }}
    runs-on: ubuntu-latest

    permissions:
      contents: write
      pull-requests: write
      issues: write

    steps:
      - name: Fast forwarding
        uses: sequoia-pgp/fast-forward@v1
        with:
          merge: true
          # To reduce the workflow's verbosity, use 'on-error'
          # to only post a comment when an error occurs, or 'never' to
          # never post a comment.  (In all cases the information is
          # still available in the step's summary.)
          comment: always

This workflow is run when a comment that includes /fast-forward is added to the pull request. The workflow is careful to check that the user who triggered the workflow is actually authorized to push to the repository.