|
| 1 | +# Azure Artifact Signing (Azure DevOps + Terraform) |
| 2 | + |
| 3 | +Costa Rica |
| 4 | + |
| 5 | +[](https://github.com/) |
| 6 | +[brown9804](https://github.com/brown9804) |
| 7 | + |
| 8 | +Last updated: 2026-02-19 |
| 9 | + |
| 10 | +---------- |
| 11 | + |
| 12 | +`In Azure DevOps, code signing is an automated pipeline step that runs after build, using a cloud‑hosted certificate where the private key never leaves Azure`. The pipeline then uses SignTool + the Artifact Signing dlib to call the service endpoint associated with those resources. |
| 13 | + |
| 14 | +> - “Trusted Signing” = service branding/experience |
| 15 | +> - “Microsoft.CodeSigning/*” = the deployable Azure resources you manage with Terraform/ARM. |
| 16 | +
|
| 17 | +<details> |
| 18 | +<summary><b>List of References </b> (Click to expand)</summary> |
| 19 | + |
| 20 | +- [What is Artifact Signing?](https://learn.microsoft.com/en-us/azure/artifact-signing/overview) |
| 21 | +- [Artifact Signing](https://azure.microsoft.com/en-us/products/artifact-signing?msockid=38ec3806873362243e122ce086486339) |
| 22 | + |
| 23 | +</details> |
| 24 | + |
| 25 | +> This repo is a minimal, demo-friendly setup for: |
| 26 | +> - Provisioning Azure Artifact Signing (Trusted Signing) resources with Terraform. |
| 27 | +> - Building a small Windows .NET executable. |
| 28 | +> - Signing it in Azure DevOps using **SignTool + Artifact Signing dlib** (private key stays in Microsoft-managed HSMs). |
| 29 | +
|
| 30 | + <img width="1523" height="743" alt="image" src="https://github.com/user-attachments/assets/5617dfde-d84b-4dd9-904f-7669b4de9374" /> |
| 31 | + |
| 32 | +## Prereqs |
| 33 | + |
| 34 | +- Azure CLI installed and logged in (`az login`) |
| 35 | +- Terraform installed |
| 36 | +- Permission to register resource providers + create resources in your subscription. One-time provider registration (per subscription): |
| 37 | + |
| 38 | + ```pwsh |
| 39 | + az provider register --namespace Microsoft.CodeSigning |
| 40 | + ``` |
| 41 | + |
| 42 | +## What Terraform creates |
| 43 | + |
| 44 | +- Resource group |
| 45 | +- Artifact Signing account (`Microsoft.CodeSigning/codeSigningAccounts`) |
| 46 | +- Key Vault (RBAC-enabled) for pipeline variables/secrets (created by default) |
| 47 | +- Certificate profile (`.../certificateProfiles`) |
| 48 | + - Preferred: created by the Azure DevOps pipeline after you set the Key Vault secret `artifactSigningIdentityValidationId` (no second `terraform apply`) |
| 49 | + - Optional: created by Terraform if you set `identity_validation_id` and re-apply |
| 50 | +- Optional Azure DevOps resources (when `ado_enabled = true`) |
| 51 | + - Entra app registration + service principal |
| 52 | + - Azure DevOps project/repo/pipeline/service connection + authorizations |
| 53 | + |
| 54 | +<img width="451" height="622" alt="image" src="https://github.com/user-attachments/assets/1306d110-be8f-49a8-96dc-c0354a2a6404" /> |
| 55 | + |
| 56 | +From [What is Artifact Signing?](https://learn.microsoft.com/en-us/azure/artifact-signing/overview) |
| 57 | + |
| 58 | +> [!NOTE] |
| 59 | +> - **Identity validation** itself is **portal-only** (service requirement). Terraform can’t complete that workflow. |
| 60 | +> - After you complete it, set Key Vault secret `artifactSigningIdentityValidationId` and the pipeline will create the certificate profile automatically. |
| 61 | +> - If Terraform creates the Azure DevOps service connection (`ado_enabled = true`), it can read the generated WIF **Issuer/Subject** and create the Entra federated credential automatically. |
| 62 | +
|
| 63 | +## Deploy with Terraform |
| 64 | + |
| 65 | +Terraform files live in `terraform-infrastructure/`. |
| 66 | + |
| 67 | +1) Edit `terraform-infrastructure/terraform.tfvars` and set a globally-unique account name. |
| 68 | + |
| 69 | +2) Run Terraform: |
| 70 | + |
| 71 | +```pwsh |
| 72 | +cd terraform-infrastructure |
| 73 | +terraform init |
| 74 | +terraform validate |
| 75 | +terraform apply -auto-approve |
| 76 | +``` |
| 77 | + |
| 78 | +3) In Azure portal, open the Artifact Signing account and complete **Identity validation** (portal-only). |
| 79 | + |
| 80 | +4) Copy the **Identity validation Id** from the portal and set it in Key Vault: |
| 81 | + |
| 82 | +```pwsh |
| 83 | +$kvName = terraform -chdir=terraform-infrastructure output -raw keyvault_name |
| 84 | +az keyvault secret set --vault-name $kvName --name artifactSigningIdentityValidationId --value "00000000-0000-0000-0000-000000000000" |
| 85 | +``` |
| 86 | + |
| 87 | +5) Run the Azure DevOps pipeline. The `AzureCLI@2` step will create the certificate profile if it doesn’t exist yet, then sign the binaries. |
| 88 | + |
| 89 | +Optional: If you prefer Terraform to manage the certificate profile instead, set `identity_validation_id` in `terraform-infrastructure/terraform.tfvars` and run `terraform apply` again. |
| 90 | + |
| 91 | +## Azure DevOps (Terraform-managed) |
| 92 | + |
| 93 | +This repo can also create the Azure DevOps project/repo/pipeline/service-connection using the Terraform `azuredevops` provider. |
| 94 | + |
| 95 | +1) Create a PAT in Azure DevOps with permissions to manage projects/repos/pipelines/service connections. |
| 96 | + |
| 97 | +2) Set env vars (PowerShell): |
| 98 | + |
| 99 | +```pwsh |
| 100 | +$env:AZDO_ORG_SERVICE_URL = "https://dev.azure.com/<your-org>" |
| 101 | +$env:AZDO_PERSONAL_ACCESS_TOKEN = "<your-pat>" |
| 102 | +``` |
| 103 | + |
| 104 | +3) Enable the Azure DevOps resources in Terraform. |
| 105 | + |
| 106 | +Preferred: use the provided var-file: |
| 107 | + |
| 108 | +```pwsh |
| 109 | +cd terraform-infrastructure |
| 110 | +terraform apply -auto-approve -var-file=terraform.tfvars -var-file=terraform.ado.tfvars |
| 111 | +``` |
| 112 | + |
| 113 | +Alternative: set `ado_enabled = true` and `ado_org_service_url` in `terraform.tfvars`. |
| 114 | + |
| 115 | +4) Terraform will: |
| 116 | +- create the Azure DevOps project + repo + YAML pipeline |
| 117 | +- create the AzureRM service connection using Workload Identity Federation (WIF) |
| 118 | +- read the generated WIF Issuer/Subject and create the Entra federated credential |
| 119 | + |
| 120 | +Notes: |
| 121 | +- The `WorkloadIdentityFederation` auth scheme requires your org feature to be enabled. If your org can’t use it yet, set `ado_service_endpoint_authentication_scheme = "ServicePrincipal"` and also set `TF_VAR_ado_service_principal_client_secret` for the service principal secret. |
| 122 | +- Terraform creates an empty repo. You still need to push this repo’s code into the Azure DevOps repo (Terraform will output the clone URL). |
| 123 | + |
| 124 | +## Pipeline |
| 125 | + |
| 126 | +- [azure-pipelines.yml](azure-pipelines.yml) builds `SigningDemo.exe`, installs the required signing components via NuGet extraction, then signs using the official SignTool + `/dlib` flow. |
| 127 | +- If `keyVaultName` is set, the pipeline loads signing values from Key Vault via `AzureKeyVault@2`. |
| 128 | + |
| 129 | +Minimum pipeline variables (when not using the Terraform-managed variable group): |
| 130 | +- `azureServiceConnection` (service connection name; default `sc-artifact-signing`) |
| 131 | +- `keyVaultName` (Terraform output `keyvault_name`) |
| 132 | +- `artifactSigningResourceGroupName` (Terraform `resource_group_name`) |
| 133 | + |
| 134 | +Optional overrides (normally provided by Key Vault): |
| 135 | +- `artifactSigningEndpoint` |
| 136 | +- `artifactSigningAccountName` |
| 137 | +- `artifactSigningCertificateProfileName` |
| 138 | +- `artifactSigningIdentityValidationId` (only required until the profile exists) |
| 139 | + |
| 140 | +Optional (only used when creating the certificate profile): |
| 141 | +- `artifactSigningCertificateProfileType` (defaults to `PublicTrust` if unset) |
| 142 | +- `adoServicePrincipalObjectId` |
| 143 | + |
| 144 | +### Azure Key Vault for pipeline variables |
| 145 | + |
| 146 | +Terraform creates a Key Vault by default and wires RBAC so: |
| 147 | +- your current identity can set secrets |
| 148 | +- the Azure DevOps service principal can read secrets |
| 149 | + |
| 150 | +Terraform also populates these Key Vault secrets during `terraform apply`: |
| 151 | +- `artifactSigningEndpoint` |
| 152 | +- `artifactSigningAccountName` |
| 153 | +- `artifactSigningCertificateProfileName` |
| 154 | +- `artifactSigningIdentityValidationId` (placeholder until you set it after portal validation) |
| 155 | + |
| 156 | +The pipeline automatically loads them (it runs `AzureKeyVault@2` when `keyVaultName` is non-empty). |
| 157 | + |
| 158 | +If signing fails with 403, validate: |
| 159 | +- Endpoint matches region |
| 160 | +- Service connection identity has `Artifact Signing Certificate Profile Signer` at the certificate profile scope |
| 161 | + |
| 162 | +## Azure Portal link |
| 163 | + |
| 164 | +After `terraform apply`, open the resource group: |
| 165 | +https://portal.azure.com/#view/HubsExtension/BrowseResourceGroups |
| 166 | + |
| 167 | +<!-- START BADGE --> |
| 168 | +<div align="center"> |
| 169 | + <img src="https://img.shields.io/badge/Total%20views-1280-limegreen" alt="Total views"> |
| 170 | + <p>Refresh Date: 2026-02-19</p> |
| 171 | +</div> |
| 172 | +<!-- END BADGE --> |
0 commit comments