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:
- Build an image and push it to Azure Container Registry (ACR).
- 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
- Use the Azure login action with OpenID Connect
Use the Azure login action with a service principal secret
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
Save the clientId from the JSON output as an environment variable (without the quotation marks) for later use:
clientId=<client-id-from-JSON-output>
Copy the entire JSON output from the
az ad sp create-for-rbac
command.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.Paste the entire JSON output from the
az ad sp create-for-rbac
command into the Secret field ofAZURE_CREDENTIALS
.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
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>
From the JSON output of the
az ad app create
command, save the value ofappId
asclientId
for later use:clientId=<appId-from-JSON-output>
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
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.Finally, follow the instructions in Create GitHub secrets to add three GitHub Secrets. They are
AZURE_CLIENT_ID
,AZURE_TENANT_ID
, andAZURE_SUBSCRIPTION_ID
.
Create the GitHub Actions workflow
Once you've configured authentication, you're ready to create the GitHub Actions workflow.
Create a
.github/workflows
directory in your repository on GitHub if this directory does not already exist.In the
.github/workflows
directory, create a file named<your-workflow>.yml
, filling in your own name for the placeholder.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'
- 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
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.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 ************
- 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.