Keyless Git Commit Signing with Gitsign and GitHub Actions

Let's take a look at Sigstore's latest tool, Gitsign, and how we can use this tool to sign commits for CI/CD within GitHub Actions!

If you haven't already, we recommend reading Zero-friction “keyless signing” with Github Actions for container images first.

Why Gitsign?

Source code is an important piece of any software supply chain. It is the base that's used to build everything else: if you can't trust your source, how can you trust the binaries and images you build from it? Signing Git commits should be a goal for everyone, and will likely be important for many organizations to meet SLSA L3 Source - Verified History requirements.

Git commit signing has historically been done with GPG keys, with recent support being added for SSH and x509 certs. However, managing and securing long-lived signing secrets is challenging to do right over time. With Gitsign, Sigstore aims to make signing with Git easy by bringing keyless signing to commit signing, just like Cosign did for container signing! This is useful not only for signing your own Git commits, but also for CI/CD workflows that may create a commit or pull request (for example, GitOps flows, Dependabot, import flows, etc.), since Sigstore can enable tools to sign data with their own OIDC identity without the need to provision long-lived secrets.

How Gitsign works

Keyless signing isn't really keyless — it uses ephemeral keys with Sigstore's Fulcio to generate a certificate using OIDC credentials. This certificate is short-lived, and contains information about the identity that generated it.

With GitHub Actions, GitHub automatically provides short-lived OIDC tokens to Action Runners - Gitsign knows to detect these and automatically uses them to generate a signing certificate via Fulcio. With this we can generate ephemeral code-signing certificates without ever needing to provide GitHub with a long-lived secret. This helps secure your software supply chain by:

  1. Ensuring no long-lived secrets are in CI/CD workers — even if the key were to be exfiltrated it would only be valid for a short amount of time, severely limiting the window where it could be used. Commits can be verified even after certificate expiration, by using Sigstore's transparency log - Rekor.
  2. Providing authentication information about who/what signed the commit into the commit itself, including the exact action workflow identity that generated the signature.

For a more concrete example, let's take a look at a certificate was issued issued by Fulcio for a real GitHub Action. Commit signatures produced by Gitsign are CMS (Cryptographic Message Syntax) formatted signatures, containing an x509 certificate. Here are some of the interesting fields included in the certificate:

Certificate:
    Data:
        Issuer: O=sigstore.dev, CN=sigstore
        Validity
            Not Before: May 18 15:41:46 2022 GMT
            Not After : May 18 15:51:45 2022 GMT
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: 
                Code Signing
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Alternative Name: critical
                URI:https://github.com/wlynch/gitsign-action/.github/workflows/main.yml@refs/heads/main
            1.3.6.1.4.1.57264.1.6: 
                refs/heads/main
            1.3.6.1.4.1.57264.1.3: 
                f6ad30f71c1719e4e9446cdee3e72bc3136c8492
            1.3.6.1.4.1.57264.1.1: 
                https://token.actions.githubusercontent.com
            1.3.6.1.4.1.57264.1.2: 
                push
            1.3.6.1.4.1.57264.1.4: 
                .github/workflows/main.yml
            1.3.6.1.4.1.57264.1.5: 
                wlynch/gitsign-action

Interesting things to point out about this certificate:

  • It was issued by the public sigstore.dev instance
  • It is only valid for 10 minutes.
  • It includes several custom ASN.1 OIDs provided by Fulcio that give us useful identifiers as to what GitHub Actions workflow signed the commit:
OID Name Value Description
1.3.6.1.4.1.57264.1.1 Issuer https://token.actions.githubusercontent.com The OIDC Token Issuer
1.3.6.1.4.1.57264.1.2 Github Workflow Trigger push GitHub event that triggered the action.
1.3.6.1.4.1.57264.1.3 Github Workflow SHA f6ad30f71c1719e4e9446cdee3e72bc3136c8492 The commit SHA that the workflow ran at.
1.3.6.1.4.1.57264.1.4 Github Workflow Name .github/workflows/main.yml The workflow that ran
1.3.6.1.4.1.57264.1.5 Github Workflow Repository wlynch/gitsign-action The repo the workflow belongs to.
1.3.6.1.4.1.57264.1.6 Github Workflow Ref refs/heads/main The branch the workflow was targeting (e.g. pushed branch / PR target branch)

This lets us identify not only that it was automation that generated this commit, but it also tells us the exact workflow and event that caused this change. This is valuable for being able to trace back through the supply chain where a particular change came from, and gives us identifiers to define policies for what identities are allowed to submit code to the repo.

How to use Gitsign in GitHub Actions

To help you get started, we've created a GitHub Action that allows you to easily set up Gitsign with any other GitHub Action: chainguard-dev/actions/setup-gitsign. To use it within your own workflows requires minimal setup:

jobs:
  gitsign:
    permissions:
      id-token: write # Enable OIDC
    steps:
      - uses: chainguard-dev/actions/setup-gitsign@main
      - <your steps go here>

This Action sets the necessary config options to use Gitsign with the Git CLI in your environment - i.e. with this Gitsign will be compatible with almost all other Actions that use Git without any other changes needed!

This can be used to create signed commits using a GitHub Action Runner's identity without needing to provision secrets! Because the keys are derived from the Runner's OIDC credentials, it is much harder for an attacker to forge their own commits using an external key.

For example, if we wanted to open a pull request with a signed commit:

on:
  workflow_dispatch:
  schedule:
    # Every day
    - cron: "0 0 * * *"

jobs:
  new_pr:
    permissions:
      id-token: write # Enable OIDC
      pull-requests: write
      contents: write
    runs-on: ubuntu-latest
    name: Make a new pull request
    steps:
      - uses: actions/checkout@v3
        with:
          ref: main
      - uses: chainguard-dev/actions/setup-gitsign@main
      - name: Change files
        shell: bash
        run: |
          # Modify repo here
          date > date.txt
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v4.0.2
        with:
          title: "New PR!"

Here at Chainguard, we use this exact approach for several of our own Actions! This makes it really simple to have Actions sign the commits they generate – we hope this makes it easier to get started signing your own commits everywhere.

Things to watch out for

While Gitsign tries to limit the data that is uploaded to the Rekor transparency log, the authentication information embedded in the Fulcio certificate for GitHub Actions (for example, repo name, branch names, etc.) may be considered sensitive for your organization — if this is the case then the public Sigstore instance may not be appropriate for your needs and you may need to run your own.

Learn more

We hope this gives you some inspiration for how Gitsign can be used in your CI/CD workflows! Gitsign is something Chainguard is enthusiastic about, with many of us using Gitsign everywhere in our own development.

We have a few things cooking to help make using keyless signatures in your repos easy - stay tuned for more info!

If you're interested in learning more about Git commit signing, running your own Sigstore instance, or have any other questions about securing your software supply chain - reach out to us at https://www.chainguard.dev/contact!

Show Comments