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.
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:
- 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.
- 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 18.104.22.168.4.1.57264.1.6: refs/heads/main 22.214.171.124.4.1.57264.1.3: f6ad30f71c1719e4e9446cdee3e72bc3136c8492 126.96.36.199.4.1.57264.1.1: https://token.actions.githubusercontent.com 188.8.131.52.4.1.57264.1.2: push 184.108.40.206.4.1.57264.1.4: .github/workflows/main.yml 220.127.116.11.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:
|18.104.22.168.4.1.57264.1.1||Issuer||https://token.actions.githubusercontent.com||The OIDC Token Issuer|
|22.214.171.124.4.1.57264.1.2||Github Workflow Trigger||push||GitHub event that triggered the action.|
|126.96.36.199.4.1.57264.1.3||Github Workflow SHA||f6ad30f71c1719e4e9446cdee3e72bc3136c8492||The commit SHA that the workflow ran at.|
|188.8.131.52.4.1.57264.1.4||Github Workflow Name||.github/workflows/main.yml||The workflow that ran|
|184.108.40.206.4.1.57264.1.5||Github Workflow Repository||wlynch/gitsign-action||The repo the workflow belongs to.|
|220.127.116.11.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: firstname.lastname@example.org 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.
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!