CI/CD Code Signing with Azure KeyVault and Azure DevOps

Introduction

This post provides instructions to sign a file through an Azure DevOps build pipeline using a signing certificate stored in Azure KeyVault. This can be used for multiple scenarios, anytime you need to sign something, in this case we’ve set this up for an automated CI/CD process of a Windows application.

There are 3 steps needed to achieve this:

  1. Set up a key vault in Azure
  2. Set up permissions to allow Azure DevOps to access the key vault
  3. Configure the build pipeline to perform the signing

Step 1. Set up a Key Vault

Create a Key Vault

To create a new key vault in the Azure portal, select “All services” from the Azure dashboard, then search for “key vaults”.

In terms of best practices, Microsoft recommends setting up a vault for each application and environment in order to be able to manage access with a high degree of granularity and “mitigate access across concerns”. In this case the application is the DevOps build pipeline, and if you need different signing certificates for different environments, then you could consider having key vaults for each environment in a structure like this:

  • MyBuildPipeline-Prod
  • MyBuildPipeline-UAT
  • MyBuildPipeline-Dev

Select Pricing Tier

When creating a new key vault, there are two pricing tiers to choose from: Standard and Premium.

The Premium tier offers the ability to store HSM-based certificates using Azure Key Vault Managed HSM, which is an Azure service that provides HSMs in the cloud, thereby making HSM usage easier and more secure. An HSM (Hardware Security Module) is a physical device that stores digital keys and certificates and performs cryptographic functions, and is required for more stringent security requirements and regulations, such as working with EV (Extended Verification) certificates.

The pricing breakdown for the premium tier can be found here , and the security best practices here.

Import/Generate a Certificate

Once the key vault is created you have the option to:

  • import an existing certificate for which you have the private keys (e.g. a .pfx file)
  • generate a self-signed certificate
  • request a certificate from a certificate authority

Requesting a Certificate from a Certificate Authority

Azure provides the option to connect directly to a certificate authority (currently DigiCert and GlobalSign are supported). You can set up this connection by adding the certificate authority to the key vault. More info about this can be found here.

This integration allows one to request certificates directly from the Azure key vault, and has the added benefit of allowing the certificate to be automatically renewed!

See also the troubleshooting section and FAQ.

Step 2. Set up Access Permissions

Once the key vault and certificate have been created, the next step is to configure access permissions to allow the Azure DevOps pipeline to use the certificate.

There are a number of different ways to achieve this, but this post is going to focus on Service Principals for this purpose, which is an approach that is supported across the majority of Azure services.

An Azure service principal is an Azure Active Directory identity that gives applications access to Azure services and resources, and is used to create a Service Connection in Azure DevOps.

Create a Service Principal

Creating a service principal is done by creating an Active Directory app registration. In this case the application that we want to give access to is the pipeline itself. To create the registration, open “Azure Active Directory” from the Azure dashboard, then under “App registrations” select “New registration”, then enter a name for it that matches your build pipeline and save it.

Once you have created the app registration, you will see a page like the following:

Take note of the Application (client) ID and the Directory (tenant) ID, which will be needed later.

Then, under “Certificate & secrets”, create a new secret and make sure to copy the value of the secret and store it in a secure place, e.g. a password manager.

Set Key Vault Permissions

Now that the build pipeline is created, one needs to assign the correct permissions to it to be able to access the key vault. This consists of adding:

  • The Key Vault Secrets User role on the subscription
  • An access policy on the key vault

Key Vault Secrets User Role

Open the main subscription in Azure, select “Access Control (IAM)”, click on “Add”, and select “Add role assignment”.

On the page that appears search for “Key Vault Secrets User” and click “Next”.

On the last page, select the app registration (service principal) that you created as the member, then click on “Review + assign”.

Key Vault Access Policy

Go back to the key vault that you created to hold the certificate, select “Access policies”, and click on “Add Access Policy”.

On the page that appears, select the following permissions via the dropdowns:

  • Key permissions: Get, List, Sign
  • Secret permissions: Get, List
  • Certificate permissions: Get, List

Then select the app registration as the principal, click the “Add” button, and then don’t forget to also click the “Save button” on the previous page!

Create an Azure DevOps Service Connection

In Azure DevOps, go to the properties of your project and select “Service connections” from the menu bar, then click on “New service connection”, select “Azure Resource Manager”, and click the “Next” button.

On the next page select “Service principal (manual)” and click “Next”.

On the page that follows, fill out the fields with these values:

  • Subscription Id & Name: get these values from your main Azure subscription
  • Service Principal Id: the Application (client) ID of the app registration
  • Service principal key: the secret value of the app registration
  • Tenant Id: the Directory (tenant) ID of the app registration
  • Service connection name: any name, e.g. MyPipelineAzureConnection

Step 3. Configure Build Pipeline

Now that the service connection has been created in Azure DevOps to provide access to the key vault, the build pipeline can be configured to perform the code signing by using the following yaml tasks:

  - task: DotNetCoreCLI@2
    displayName: 'Install Azure SignTool'
    inputs:
      command: custom
      custom: tool
      arguments: install --global AzureSignTool
  - task: PowerShell@2
    displayName: 'Sign Executable'    
    inputs:
      targetType: inline
      script: |
        & AzureSignTool sign -kvt $(AzureKeyVaultTenantId) -kvu $(AzureKeyVaultUrl) -kvi $(AzureKeyVaultClientId) -kvs $(AzureKeyVaultClientSecret) -kvc $(AzureKeyVaultCertificateName) -tr http://timestamp.digicert.com -v "PathToFileToBeSigned"

The first task installs the Azure signing tool, and the second command launches the tool with a number of parameters. The values needed for these parameters are as follows:

Key vault properties that can be found in the Overview section of the key vault:

  • AzureKeyVaultTenantId: The key vault’s directory ID
  • AzureKeyVaultUrl: a url in the format “https://(mykeyvaultname).vault.azure.net”

Service Principal properties:

  • AzureKeyVaultClientId: the service principal (app registration) Id
  • AzureKeyVaultClientSecret: the service principal (app registration) secret
  • AzureKeyVaultCertificateName: name of certificate inside the key vault

These values should be stored as variables, either in a variable group in the Azure DevOps Library, or for added security in the key vault itself. One can add text values in a key vault under the “Secrets” section, and these can be retrieved and populated automatically into variables with the task below:

  - task: AzureKeyVault@2
    displayName: 'Connect to Azure Key Vault'
    inputs:
      ConnectedServiceName: '$(AzureKeyVaultServiceConnectionName)' 
      KeyVaultName: '$(AzureKeyVaultName)'

The variables AzureKeyVaultServiceConnectionName and AzureKeyVaultName can be stored in the Azure DevOps Library.

Leave a Reply