Integrate Red Hat Trusted Artifact Signer with GitHub Actions

In this learning exercise, we'll learn how to automate the signing and verification of artifacts and commits. This will ensure that your team is verifying the integrity and authenticity of software artifacts in a way that doesn't impact productivity. To facilitate the installation, we'll leverage Red Hat Trusted Artifact Signer on OpenShift. By the end of this exercise, you'll be able to sign and verify your artifacts reliably across different environments within OpenShift.

Red Hat Trusted Artifact Signer

Prerequisites


Step-by-step guide

Please ensure you are logged into your OpenShift cluster and have the cosign and gitsign binaries installed and available in your $PATH, as per the first part of this learning exercise.

1. Workflow setup

GitHub Actions is a continuous integration and delivery (CI/CD) platform that allows you to execute custom workflows directly from your repository. A workflow can do any task you'd like, from securing your CI/CD pipeline to automating your tests.

You can also have multiple workflows. The key to setting up a workflow in your GitHub CI/CD pipeline is to create a YAML file for it. Workflow files must go in the .github/workflows directory in the root of your project. Since we don't have one, we'll have to create it.

1.1 Creating your first workflow

We'll create a simple workflow that builds, signs, and logs metadata to Rekor, and then verifies and deploys the software artifacts. This could be particularly helpful for developers who want to follow best secure software practices in their organization by signing their commits and ensuring they are verified prior to building the container image and deployment.

Following on from the previous learning exercise, let's navigate to the project previously created in part 1 of the learning exercise and create the directory for our GitHub workflows. Run the following commands in your terminal:

cd tas-demo
mkdir .github .github/workflows dist

We'll also go ahead and make our dummy artifact, a simple text file in this case, as an example of a build artifact:

echo "Example artifact" > dist/artifact.txt

Next, create a YAML file called verify-signatures.yml in the .github/workflows directory within your project.

Let's take a look at an example of a workflow that monitors your repo for pushes to the main branch, checks out your code, and builds the project.

name: Verify Signatures with Gitsign

on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        ref: main
    - name: Build project
      run: |
        rm -rf dist & mkdir -p dist
        echo "Example artifact" > dist/artifact.txt

Add the above to the verify-signatures.yml file.

1.2 Point to the cluster

Just as we set up our environment for signing locally, we need to do so for our workflow as well. With GitHub Actions, we can provide custom variables by adding the env key as follows.

name: Verify Signatures with Gitsign

on:
  push:
    branches:
      - main
env:
  IMAGE_REGISTRY: quay.io
  IMAGE_REPO: lucarval/festoji
  IMAGE_TAGS: latest
  APPS_DOMAIN: apps.rosa.p1.openshiftapps.com
  TUF_URL: https://tuf-openshift-operators.$APPS_DOMAIN
  OIDC_ISSUER_URL: https://token.actions.githubusercontent.com
  COSIGN_FULCIO_URL: https://fulcio-server-openshift-operators.$APPS_DOMAIN
  COSIGN_REKOR_URL: https://rekor-server-openshift-operators.$APPS_DOMAIN
  COSIGN_MIRROR: https://tuf-openshift-operators.$APPS_DOMAIN
  COSIGN_ROOT: https://tuf-openshift-operators.$APPS_DOMAIN/root.json
  COSIGN_OIDC_ISSUER: https://token.actions.githubusercontent.com
  COSIGN_CERTIFICATE_OIDC_ISSUER: https://token.actions.githubusercontent.com
  REKOR_REKOR_SERVER: https://rekor-server-openshift-operators.$APPS_DOMAIN
  GITSIGN_OIDC_ISSUER: https://keycloak-keycloak-system.$APPS_DOMAIN/auth/realms/trusted-artifact-signer
  TAS_IMAGE: quay.io/kahboom/test-image:latest
  COSIGN_YES: "true"
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        ref: main
    - name: Build project
      run: |
        rm -rf dist & mkdir -p dist
        echo "Example artifact" > dist/artifact.txt

Be sure to replace the initial APPS_DOMAIN declaration with your own cluster and TAS_IMAGE with your own container image.

Important: By default, variables are displayed unmasked in build outputs. You can use secrets (discussed later in this tutorial) to prevent exposing sensitive information by mistake.

1.3 Setup Gitsign

Before proceeding in the workflow, we'll want to verify the signature of the author proposing the changes, so we'll create a dedicated step to do just that. Generally, it's best practice to break up the flow into smaller chunks, where possible.

Because these workflows run on GitHub's servers, we don't have access to the Gitsign CLI, so we'll also need to add a step that downloads the CLI binary from our TAS cluster for verifying the signature, and for any future signing within this workflow.

Below the Build project job, we'll add another job to check the installation was successful and to configure our git settings for signing.

    - name: Install Gitsign
      shell: bash
      run: |
        set -ex
        curl -fsL https://cli-server-trusted-artifact-signer.$APPS_DOMAIN/clients/linux/gitsign-amd64.gz -o /usr/local/bin/gitsign-amd64.gz
        gunzip /usr/local/bin/gitsign-amd64.gz
        chmod +x /usr/local/bin/gitsign-amd64
        mv /usr/local/bin/gitsign-amd64 /usr/local/bin/gitsign
    - name: Verify Gitsign Installation
      run: |
        which gitsign
        ls -l /usr/local/bin/gitsign
        gitsign version
      shell: bash
    - name: Initialize Gitsign
      shell: bash
      run: gitsign initialize --mirror=$TUF_URL --root=$TUF_URL/root.json

1.4 Verify author signatures

With Gitsign installed and git configured, we can now add a step to verify the signature.

  - name: Verify Commit Signatures
    run: |
      gitsign verify --certificate-identity=${{ secrets.IDENTITY_EMAIL }} --certificate-oidc-issuer=$GITSIGN_OIDC_ISSUER  HEAD
    shell: bash

Note: For now, don't worry about variables prefixed with a $, as we will declare them as GitHub Secrets in the following section.

2. Set up GitHub Secrets

Until now, we've been entering values that include personal information directly into our commands. In real-world scenarios, it's better to use environment variables or similar methods to protect sensitive data. With GitHub Actions, we can use GitHub Secrets, which are variables you create that can be easily referenced from within a workflow.

Secrets can be added at the organization, environment, or repository level so that you don't have to create duplicate secrets. Note that if you are adding secrets at an organization level (i.e. an organization repository), you will need admin access.

Let's create the secrets we've been referring to in our workflow.

2.1 Certificate identity email secret

  • In your browser, navigate to the project repository on GitHub
  • Under the repository name, click on the "Settings" tab
  • In the Security section of the sidebar, click on Secrets and Variables
  • Click "Actions"
  • From the "Secrets" tab, press the "New repository secret" button 

    GitHub Actions secrets and variables
    Figure 1: GitHub Actions secrets and variables.
  • Fill in the name with and the secret with your identity email address
    • Secret name: "IDENTITY_EMAIL"
    • Secret value: Provide your identity email address
  • Click "Add secret"

2.2 OIDC, cluster, and container registry secret

Repeat the same process for the remaining secrets for accessing the cluster and container registry.

Note that while the name of the secret must remain the same, you should replace the value with your own:

Secret Name

Example Value

CI_REGISTRY

quay.io

CI_REGISTRY_USER

username

CI_REGISTRY_PASSWORD

password

TAS_IMAGE

test-image:latest

3. Security hardening with OpenID Connect

Because GitHub Actions does not have access to our cluster, the workflow we've created so far can only work if we create credentials beforehand and then duplicate them in GitHub as a secret.

With OpenID Connect (OIDC), we can configure the workflow to request a short-lived access token directly from the cloud provider, which must support OIDC. This eliminates the need to create and supply credentials.

In Part 1 of this learning exercise, we configured a trust relationship between Keycloak and GitHub that will allow our workflow to request the access token. Let's see how we can modify our workflow to reflect this change.

3.1 Permissions

The first thing we'll need to do is give special permissions to our workflow, so that it can request the required OIDC token and run the checkout action. We can do this by adding a permissions key under the build metadata near the top of our workflow file:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # required to request the OIDC token
      contents: read   # required for actions/checkout
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        ref: main

3.2 OIDC token steps

Below the Verify Commit Signatures job, we'll create a step to request the OIDC token from our cluster by specifying the request URL and token:

  - name: Request OIDC token
    id: oidc-token
    run: |
      echo "Requesting OIDC token..."
      echo "Token: ${{ steps.auth.outputs.id_token }}"
    env:
      ACTIONS_ID_TOKEN_REQUEST_URL: ${{ secrets.ACTIONS_ID_TOKEN_REQUEST_URL }}
      ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${{ secrets.ACTIONS_ID_TOKEN_REQUEST_TOKEN }}

And, finally, our last step is to use the token with TAS:

  - name: Use OIDC token with TAS
    run: |
      cosign login ${{ secrets.CI_REGISTRY }} -u ${{ secrets.CI_REGISTRY_USER }} -p ${{ secrets.CI_REGISTRY_PASSWORD }}
      cosign initialize --mirror=$TUF_URL --root=$TUF_URL/root.json
      cosign sign --oidc-issuer ${{ secrets.ACTIONS_ID_TOKEN_REQUEST_URL }} --identity-token ${{ steps.oidc-token.outputs.id_token }} $TAS_IMAGE
      cosign verify --certificate-oidc-issuer='https://token.actions.githubusercontent.com' --certificate-identity-regexp='https://github.com/kahboom' $TAS_IMAGE

Be sure to replace the value of –-certificate-identify-regexp with your own.

4. Workflow deployment

The hard work is done, and the only thing left is to deploy our workflow and let GitHub do the heavy lifting. Back in our terminal, we'll push up our code with a signed commit.

git add .
git commit -S -m "feat(ci): add github workflow"
git push

The push will then trigger a build on GitHub. Magic!

We can verify it in the browser by navigating to our repository on GitHub and clicking on the Actions tab to view all recent runs from all workflows.

You should see a list of commits that have triggered the build, and the name of the workflow it ran, just below it.

Screenshot of GitHub repository Actions tab showing all workflows currently running
Figure 2: GitHub repository Actions tab > All Workflows.

Clicking on any of the commits will take you to a summary view with a nice visualization of the build, and any artifacts that have resulted from it.

Summary of "Verify Signature with Gitsign" workflow's successful run
Figure 3: Github repository Actions tab > All Workflows > Summary.

Let's take a look at the build step. Click on the "build" button from the Summary above.

You should see a breakdown of the workflow steps and the output of each, including how long it took to run each step.

Summary of "Verify Commit Signatures" step
Figure 4: Github repository Actions tab > All Workflows > Summary > "Verify Commit Signatures" step.

Feel free to check out the repository here if you'd like to see the source code for this workflow.

Summary

This learning exercise presented the integration of Red Hat Trusted Artifact Signer with GitHub Actions. We covered the setup process for adding a new workflow and explored techniques for verifying signatures throughout the application lifecycle. These activities included setting up GitHub Actions by creating a workflow, adding a job to verify author commits and artifact signatures, and triggering the workflow build.

Previous resource
Overview: Integrate Red Hat Trusted Artifact Signer with GitHub Actions
Next resource
Additional resources