Sign an image with Notation using GitHub Actions

In this article, you'll learn how to create a GitHub Actions workflow to achieve the following goals:

  1. Build an image and push it to Azure Container Registry (ACR).
  2. Sign the image with Notation and the Notation Azure Key Vault plugin by using a signing key stored in Azure Key Vault (AKV). The generated signature is automatically pushed to ACR.

Prerequisites

  • You've created an Azure Key Vault and a self-signed signing key and certificate. To learn how to create these resources for testing purposes, see Create a self-signed certificate in AKV.
  • You've created an Azure Container Registry.
  • You have a GitHub repository to store the sample workflow file and GitHub Secrets.

Authenticate from Azure to GitHub

There are two ways to connect GitHub Actions to your ACR and AKV. Select one of them based on your needs:

Use the Azure login action with a service principal secret

  1. Execute the following commands to create a new service principal on Azure. Make sure to fill in placeholders with your own values.

    az login
    
    # Create a new service principal with the AcrPush role
    spn=<service-principal-name>
    acr_scope=/subscriptions/<subscription-id>/resourceGroups/<acr-resource-group>
    az ad sp create-for-rbac -n $spn --scopes $acr_scope --role acrpush --sdk-auth
    
  2. Save the clientId from the JSON output as an environment variable (without the quotation marks) for later use:

    clientId=<client-id-from-JSON-output>
    
  3. Copy the entire JSON output from the az ad sp create-for-rbac command.

  4. In your GitHub repository, create an encrypted secret called AZURE_CREDENTIALS, which is used to authenticate with ACR and AKV. See creating encrypted secrets for a repository for more details.

  5. Paste the entire JSON output from the az ad sp create-for-rbac command into the Secret field of AZURE_CREDENTIALS.

  6. Finally, enable your service principal created previously with access permissions to your AKV using az keyvault set-policy:

    # Set policy for your AKV
    akv=<your-akv-name>
    az keyvault set-policy --name $akv --spn $clientId --certificate-permissions get --key-permissions sign --secret-permissions get
    

Use the Azure login action with OpenID Connect (OIDC)

For more details, see Use the Azure login action with OpenID Connect

  1. Execute the following commands to register a new Microsoft Entra application:

    # Log in to Azure CLI
    az login
    
    # Register a new AAD application
    az ad app create --display-name <your-app-name>
    
  2. From the JSON output of the az ad app create command, save the value of appId as clientId for later use:

    clientId=<appId-from-JSON-output>
    
  3. Create a new service principal using the clientId you just obtained, and configure it with access to your ACR and AKV:

    # Create a new service principal
    az ad sp create --id $clientId
    
    # Assign the AcrPush role to your application
    acr_scope=/subscriptions/<subscription-id>/resourceGroups/<resource-group>
    az role assignment create --assignee $clientId --scopes $acr_scope --role acrpush
    
    # set access policy for your AKV
    akv=<your-akv-name>
    az keyvault set-policy --name $akv --spn $clientId --certificate-permissions get --key-permissions sign --secret-permissions get
    
  4. Your Microsoft Entra application is displayed under App registrations in the Azure portal. From there, follow Configure an app to trust an external identity provider to add a federated credential to your application.

  5. Finally, follow the instructions in Create GitHub secrets to add three GitHub Secrets. They are AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID.

Create the GitHub Actions workflow

Once you've configured authentication, you're ready to create the GitHub Actions workflow.

  1. Create a .github/workflows directory in your repository on GitHub if this directory does not already exist.

  2. In the .github/workflows directory, create a file named <your-workflow>.yml, filling in your own name for the placeholder.

  3. Copy the signing template workflow from the collapsed section below into your own <your-workflow>.yml file.

Click here to see the signing workflow template.
# Build and push an image to ACR, setup notation and sign the image
name: notation-github-actions-sign-template

on:
  push:

env:
  ACR_REGISTRY_NAME: <registry_name_of_your_ACR>          # example: myRegistry.azurecr.io
  ACR_REPO_NAME: <repository_name_of_your_ACR>            # example: myRepo
  KEY_ID: <key_id_of_your_private_key_to_sign_from_AKV>   # example: https://mynotationakv.vault.azure.net/keys/notationLeafCert/c585b8ad8fc542b28e41e555d9b3a1fd
  NOTATION_EXPERIMENTAL: 1                                # [Optional] when set, use Referrers API in the workflow (Recommended)

jobs:
  notation-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: prepare
        id: prepare
        # Use `v1` as an example tag, user can pick their own
        run: |
          echo "target_artifact_reference=${{ env.ACR_REGISTRY_NAME }}/${{ env.ACR_REPO_NAME }}:v1" >> "$GITHUB_ENV"
      
      # Log in to Azure with your service principal secret
      - name: Azure login
        uses: Azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      # If you are using OIDC and federated credential, make sure to replace the above step with below:
      # - name: Azure login
      #   uses: Azure/login@v1
      #   with:
      #     client-id: ${{ secrets.AZURE_CLIENT_ID }}
      #     tenant-id: ${{ secrets.AZURE_TENANT_ID }}
      #     subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      # Log in to your ACR registry
      - name: ACR login
        run: |
            az acr login --name ${{ env.ACR_REGISTRY_NAME }}
      # Build and push an image to the registry
      # Use `Dockerfile` as an example to build an image
      - name: Build and push
        id: push
        uses: docker/build-push-action@v4
        with:
          push: true
          tags: ${{ env.target_artifact_reference }}
      # Get the manifest digest of the OCI artifact
      - name: Retrieve digest
        run: |
          echo "target_artifact_reference=${{ env.ACR_REGISTRY_NAME }}/${{ env.ACR_REPO_NAME }}@${{ steps.push.outputs.digest }}" >> "$GITHUB_ENV"
      
      # Install Notation CLI with the default version "1.1.0"
      - name: setup notation
        uses: notaryproject/notation-action/setup@v1
      
      # Sign your OCI artifact using private key stored in AKV
      - name: sign OCI artifact using key pair from AKV
        uses: notaryproject/notation-action/sign@v1
        with:
          plugin_name: azure-kv
          plugin_url: https://github.com/Azure/notation-azure-kv/releases/download/v1.0.1/notation-azure-kv_1.0.1_linux_amd64.tar.gz
          plugin_checksum: f8a75d9234db90069d9eb5660e5374820edf36d710bd063f4ef81e7063d3810b
          key_id: ${{ env.KEY_ID }}
          target_artifact_reference: ${{ env.target_artifact_reference }}
          signature_format: cose
          plugin_config: |-
            ca_certs=.github/cert-bundle/cert-bundle.crt
            self_signed=false
          # If you are using self-signed certificate from AKV, then the `plugin_config` should be:
          # plugin_config: |-
          #   self_signed=true
          allow_referrers_api: 'true'
  1. Finally, update the environment variables based on your own environment and your chosen authentication method by following the comments in the template. Save and commit the file to your repository.

Trigger the GitHub Actions workflow

  1. The trigger logic in the sample workflow is set to the on: push event. Committing the workflow file to a branch in your repository triggers the push event and runs your workflow.

  2. On success, you'll be able to see the image is built and pushed to your ACR with a COSE format signature attached. Your output will look similar to the following:

Run notaryproject/notation-action/sign@v1
input plugin_name is azure-kv
input plugin url is https://github.com/Azure/notation-azure-kv/releases/download/v1.0.1/notation-azure-kv_1.0.1_linux_amd64.tar.gz
input plugin checksum is f8a75d9234db90069d9eb5660e5374820edf36d710bd063f4ef81e7063d3810b
/home/runner/work/_temp/44b611a3-0570-4539-862e-b009af46fc91/notation version
Notation - a tool to sign and verify artifacts.

Version:     1.1.1
Go version:  go1.22.4
Git commit:  3dafd534fe069f2c0ce6127eb33d2e3e476723c3
installing signing plugin via Notation...
/home/runner/work/_temp/44b611a3-0570-4539-862e-b009af46fc91/notation plugin install --url https://github.com/Azure/notation-azure-kv/releases/download/v1.0.1/notation-azure-kv_1.0.1_linux_amd64.tar.gz --sha256sum f8a75d9234db90069d9eb5660e5374820edf36d710bd063f4ef81e7063d3810b
Downloading plugin from https://github.com/Azure/notation-azure-kv/releases/download/v1.0.1/notation-azure-kv_1.0.1_linux_amd64.tar.gz
Download completed
Successfully installed plugin azure-kv, version 1.0.1
/home/runner/work/_temp/44b611a3-0570-4539-862e-b009af46fc91/notation plugin ls
NAME       DESCRIPTION                       VERSION   CAPABILITIES                ERROR   
azure-kv   Notation Azure Key Vault plugin   1.0.1     [SIGNATURE_GENERATOR.RAW]   <nil>   
/home/runner/work/_temp/44b611a3-0570-4539-862e-b009af46fc91/notation sign --signature-format cose --id ****** --plugin azure-kv --plugin-config=self_signed=true --allow-referrers-api ******
Warning: This feature is experimental and may not be fully tested or completed and may be deprecated. Report any issues to "https://github/notaryproject/notation"
Warning: using the Referrers API to store signature. On success, must set the `--allow-referrers-api` flag to list, inspect, and verify the signature.
Successfully signed ************
  1. Alternatively, you can configure your workflow file to trigger when a new tag is pushed to the Github repository. See Triggering a workflow for more details. This is a common practice that's done in order to secure a software release process while using GitHub Actions.

View your GitHub Actions workflow results

Under your GitHub repository name, click the Actions tab of your GitHub repository to see the workflow logs.