Skip to content

Commit bdca844

Browse files
committed
git actions added
1 parent 16f4178 commit bdca844

14 files changed

Lines changed: 1040 additions & 137 deletions

.github/workflows/artifact-signing.yml

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Builds a demo .NET exe, pulls signing inputs from Key Vault, ensures the cert profile exists,
1+
# Builds a demo .NET exe, pulls signing inputs from Key Vault,
22
# then signs and verifies the binary using SignTool + the Azure Artifact Signing dlib.
33

44
name: Artifact Signing Demo
@@ -20,12 +20,17 @@ jobs:
2020
BUILD_CONFIGURATION: Release
2121
RUNTIME_IDENTIFIER: win-x64
2222

23+
# Set to 'true' if you want the workflow to create the certificate profile when missing.
24+
# Default is 'false' because identity validation + certificate profile creation are typically done in the Azure Portal.
25+
# If you set this to 'true', you must also provide the Identity validation Id (via Key Vault secret 'artifactSigningIdentityValidationId').
26+
AUTO_CREATE_CERT_PROFILE: 'false'
27+
2328
# Non-secret inputs (configure as GitHub repo variables or secrets):
2429
KEYVAULT_NAME: ${{ secrets.KEYVAULT_NAME }}
2530
ARTIFACT_SIGNING_RESOURCE_GROUP: ${{ secrets.ARTIFACT_SIGNING_RESOURCE_GROUP }}
2631

2732
# Optional override; only needed when creating the cert profile:
28-
CERT_PROFILE_TYPE: PublicTrust
33+
CERT_PROFILE_TYPE: PublicTrustTest
2934

3035
steps:
3136
- name: Checkout
@@ -54,88 +59,62 @@ jobs:
5459
$ErrorActionPreference = 'Stop'
5560
5661
if ([string]::IsNullOrWhiteSpace($env:KEYVAULT_NAME)) { throw "Missing KEYVAULT_NAME (set as a GitHub secret)." }
57-
if ([string]::IsNullOrWhiteSpace($env:ARTIFACT_SIGNING_RESOURCE_GROUP)) { throw "Missing ARTIFACT_SIGNING_RESOURCE_GROUP (set as a GitHub secret)." }
58-
59-
function Get-KvSecret([string]$name) {
60-
$value = az keyvault secret show --vault-name $env:KEYVAULT_NAME --name $name --query value -o tsv
61-
if ($LASTEXITCODE -ne 0) { throw "Failed to read Key Vault secret '$name' from vault '$($env:KEYVAULT_NAME)'" }
62-
if ($null -eq $value) { return '' }
63-
return "$value".Trim()
64-
}
65-
66-
$endpoint = Get-KvSecret 'artifactSigningEndpoint'
67-
$account = Get-KvSecret 'artifactSigningAccountName'
68-
$profile = Get-KvSecret 'artifactSigningCertificateProfileName'
69-
$idvId = Get-KvSecret 'artifactSigningIdentityValidationId'
70-
71-
if ([string]::IsNullOrWhiteSpace($endpoint)) { throw "Key Vault secret artifactSigningEndpoint is empty." }
72-
if ([string]::IsNullOrWhiteSpace($account)) { throw "Key Vault secret artifactSigningAccountName is empty." }
73-
if ([string]::IsNullOrWhiteSpace($profile)) { throw "Key Vault secret artifactSigningCertificateProfileName is empty." }
74-
75-
# Mask sensitive-ish values in logs.
76-
Write-Host "::add-mask::$endpoint"
77-
Write-Host "::add-mask::$account"
78-
Write-Host "::add-mask::$profile"
79-
if (-not [string]::IsNullOrWhiteSpace($idvId)) { Write-Host "::add-mask::$idvId" }
8062
81-
"ARTIFACT_SIGNING_ENDPOINT=$endpoint" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
82-
"ARTIFACT_SIGNING_ACCOUNT_NAME=$account" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
83-
"ARTIFACT_SIGNING_CERT_PROFILE_NAME=$profile" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
84-
"ARTIFACT_SIGNING_IDENTITY_VALIDATION_ID=$idvId" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
63+
pwsh -NoProfile -ExecutionPolicy Bypass -File .\scripts\load-signing-config-from-kv.ps1
8564
86-
- name: Ensure certificate profile exists
65+
- name: Ensure certificate profile exists (optional)
66+
if: ${{ env.AUTO_CREATE_CERT_PROFILE == 'true' }}
8767
shell: pwsh
8868
run: |
8969
$ErrorActionPreference = 'Stop'
9070
9171
$rg = "$env:ARTIFACT_SIGNING_RESOURCE_GROUP".Trim()
9272
if ([string]::IsNullOrWhiteSpace($rg)) { throw "Missing ARTIFACT_SIGNING_RESOURCE_GROUP" }
9373
94-
$endpoint = "$env:ARTIFACT_SIGNING_ENDPOINT".Trim()
9574
$accountName = "$env:ARTIFACT_SIGNING_ACCOUNT_NAME".Trim()
9675
$profileName = "$env:ARTIFACT_SIGNING_CERT_PROFILE_NAME".Trim()
9776
$profileType = "$env:CERT_PROFILE_TYPE".Trim()
9877
if ([string]::IsNullOrWhiteSpace($profileType)) { $profileType = 'PublicTrust' }
9978
100-
if ([string]::IsNullOrWhiteSpace($endpoint)) { throw "Missing ARTIFACT_SIGNING_ENDPOINT" }
10179
if ([string]::IsNullOrWhiteSpace($accountName)) { throw "Missing ARTIFACT_SIGNING_ACCOUNT_NAME" }
10280
if ([string]::IsNullOrWhiteSpace($profileName)) { throw "Missing ARTIFACT_SIGNING_CERT_PROFILE_NAME" }
10381
10482
$identityValidationId = "$env:ARTIFACT_SIGNING_IDENTITY_VALIDATION_ID".Trim()
83+
if ([string]::IsNullOrWhiteSpace($identityValidationId)) {
84+
throw "AUTO_CREATE_CERT_PROFILE=true but Key Vault secret 'artifactSigningIdentityValidationId' is empty. Complete identity validation in the portal and set that secret."
85+
}
10586
10687
$subId = (az account show --query id -o tsv)
10788
if ([string]::IsNullOrWhiteSpace($subId)) { throw "Unable to determine subscription id from Azure CLI context." }
10889
10990
$profileResourceId = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.CodeSigning/codeSigningAccounts/$accountName/certificateProfiles/$profileName"
11091
$profileUrl = "https://management.azure.com$profileResourceId?api-version=2025-10-13"
11192
112-
Write-Host "Ensuring certificate profile exists: $profileResourceId"
93+
Write-Host "Checking certificate profile: $profileResourceId"
94+
95+
az rest --method get --url $profileUrl --only-show-errors | Out-Null
96+
$getExit = $LASTEXITCODE
11397
114-
$profileExists = $false
115-
try {
116-
az rest --method get --url $profileUrl --only-show-errors | Out-Null
117-
$profileExists = $true
118-
} catch {
119-
$profileExists = $false
98+
if ($getExit -eq 0) {
99+
Write-Host "Certificate profile exists: $profileName"
100+
exit 0
120101
}
121102
122-
if (-not $profileExists) {
123-
if ([string]::IsNullOrWhiteSpace($identityValidationId)) {
124-
throw "Certificate profile is missing. Complete identity validation in the portal and set Key Vault secret 'artifactSigningIdentityValidationId'."
103+
Write-Host "Certificate profile missing (or not readable). Creating it..."
104+
105+
$body = @{
106+
properties = @{
107+
identityValidationId = $identityValidationId
108+
profileType = $profileType
109+
includeStreetAddress = $false
110+
includePostalCode = $false
125111
}
112+
} | ConvertTo-Json -Depth 10
126113
127-
$body = @{
128-
properties = @{
129-
identityValidationId = $identityValidationId
130-
profileType = $profileType
131-
includeStreetAddress = $false
132-
includePostalCode = $false
133-
}
134-
} | ConvertTo-Json -Depth 10
135-
136-
az rest --method put --url $profileUrl --body $body --only-show-errors | Out-Null
137-
Write-Host "Created certificate profile: $profileName"
138-
}
114+
az rest --method put --url $profileUrl --body $body --only-show-errors | Out-Null
115+
if ($LASTEXITCODE -ne 0) { throw "Failed to create certificate profile via ARM. Ensure the GitHub identity has RG Contributor (or equivalent) permissions." }
116+
117+
Write-Host "Created certificate profile: $profileName"
139118
140119
- name: Sign with Azure Artifact Signing (SignTool + dlib)
141120
shell: pwsh

README.md

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Demo: Azure Artifact Signing (GitHub Actions/ADO + Terraform)
1+
# Demo: Azure Artifact Signing (GitHub Actions + Terraform)
22

33
Costa Rica
44

@@ -19,6 +19,10 @@ Last updated: 2026-02-19
1919

2020
- [What is Artifact Signing?](https://learn.microsoft.com/en-us/azure/artifact-signing/overview)
2121
- [Artifact Signing](https://azure.microsoft.com/en-us/products/artifact-signing?msockid=38ec3806873362243e122ce086486339)
22+
- [Artifact Signing pricing](https://azure.microsoft.com/en-us/pricing/details/artifact-signing/?msockid=38ec3806873362243e122ce086486339)
23+
- [Quickstart: Set up Artifact Signing](https://learn.microsoft.com/en-us/azure/artifact-signing/quickstart?tabs=registerrp-portal%2Caccount-portal%2Corgvalidation%2Ccertificateprofile-portal%2Cdeleteresources-portal)
24+
- [Set up signing integrations to use Artifact Signing](https://learn.microsoft.com/en-us/azure/artifact-signing/how-to-signing-integrations)
25+
- [Microsoft Included CA Certificate List](https://ccadb.my.salesforce-sites.com/microsoft/IncludedCACertificateReportForMSFT)
2226

2327
</details>
2428

@@ -46,9 +50,12 @@ Last updated: 2026-02-19
4650
- Resource group
4751
- Artifact Signing account (`Microsoft.CodeSigning/codeSigningAccounts`)
4852
- Key Vault (RBAC-enabled) for pipeline variables/secrets (created by default)
49-
- Certificate profile (`.../certificateProfiles`)
50-
- Preferred: created by the GitHub Actions workflow after you set the Key Vault secret `artifactSigningIdentityValidationId` (no second `terraform apply`)
51-
- Optional: created by Terraform if you set `identity_validation_id` and re-apply
53+
54+
Certificate profile + identity validation are created in the Azure Portal (service requirement).
55+
56+
Optional advanced paths:
57+
- Terraform can create the certificate profile if you set `identity_validation_id` and re-run `terraform apply`.
58+
- The GitHub Actions workflow can create it if you opt in via `AUTO_CREATE_CERT_PROFILE: 'true'` in the workflow (requires broader RBAC such as RG `Contributor`).
5259

5360
When `github_enabled = true`, Terraform also creates:
5461
- Entra app registration + service principal for GitHub Actions (OIDC)
@@ -60,113 +67,116 @@ From [What is Artifact Signing?](https://learn.microsoft.com/en-us/azure/artifac
6067

6168
> [!NOTE]
6269
> - **Identity validation** itself is **portal-only** (service requirement). Terraform can’t complete that workflow.
63-
> - After you complete it, set Key Vault secret `artifactSigningIdentityValidationId` and the GitHub Actions workflow will create the certificate profile automatically.
70+
> - The **Identity validation Id** is not exposed via the Azure management API for the code signing account, so Terraform cannot “wait and fetch” it automatically.
71+
> - Identity validation + certificate profile creation can be done entirely in the portal.
72+
> - If you want Terraform (or the workflow) to create the certificate profile, you must copy the Identity validation Id from the portal.
6473
6574
## Deploy with Terraform
6675

6776
Terraform files live in `terraform-infrastructure/`.
6877

78+
If you're using GitHub Actions, prefer the fully automated bootstrap path below (it configures GitHub OIDC + secrets and runs Terraform for you):
79+
80+
```pwsh
81+
pwsh -NoProfile -ExecutionPolicy Bypass -File .\scripts\bootstrap-github-actions.ps1
82+
```
83+
6984
1) Edit `terraform-infrastructure/terraform.tfvars` and set a globally-unique account name.
7085

71-
2) Run Terraform:
86+
Not using the bootstrap script? See the internal runbook: [_docs/README.md](_docs/README.md).
87+
88+
By default (`github_autodetect = true`), Terraform will auto-detect `github_owner/github_repo` during `terraform apply` (from GitHub Actions env vars if present, otherwise from your local git `origin`). `github_ref` defaults to `refs/heads/main` unless you set it explicitly.
89+
90+
If this repo is not a git clone (for example, you downloaded a zip) or `origin` is not set to GitHub, autodetect can’t determine the values.
91+
92+
2) Run Terraform (manual):
7293

7394
```pwsh
7495
cd terraform-infrastructure
7596
terraform init
76-
terraform validate
7797
terraform apply -auto-approve
7898
```
7999

80-
3) Artifact Signing **identity verification / identity validation** (portal-only):
100+
What this first `terraform apply` does:
101+
- Creates the resource group, Artifact Signing account, and Key Vault.
102+
- Populates Key Vault secrets for the workflow (`artifactSigningEndpoint`, `artifactSigningAccountName`, `artifactSigningCertificateProfileName`).
103+
- If `github_enabled = true`: creates the GitHub OIDC Entra app/service principal + federated identity credential, and assigns RBAC so the workflow can read Key Vault secrets and sign.
81104

82-
- In Azure portal, open the Artifact Signing account and complete **Identity validation**.
83-
- This is required by the service and cannot be automated by Terraform.
84-
- If the portal says you need **Artifact Signing Identity Verifier**, re-run Terraform (it assigns this role to the identity running `terraform apply` by default) and wait a few minutes for RBAC to propagate.
105+
What it cannot do:
106+
- Complete Artifact Signing **identity validation** (this is portal-only).
85107

86-
4) Copy the **Identity validation Id** from the portal and set it in Key Vault:
108+
Identity validation (portal-only):
87109

88-
```pwsh
89-
$kvName = terraform -chdir=terraform-infrastructure output -raw keyvault_name
90-
az keyvault secret set --vault-name $kvName --name artifactSigningIdentityValidationId --value "00000000-0000-0000-0000-000000000000"
91-
```
92110

93-
5) Run the GitHub Actions workflow. It will create the certificate profile if it doesn’t exist yet, then sign the binaries.
111+
In Azure portal:
112+
- Open the Artifact Signing account and complete **Identity validation**.
113+
- Create the **certificate profile** (use the same name as `certificate_profile_name`).
94114

95-
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.
115+
Optional (Terraform-managed certificate profile):
116+
- Copy the **Identity validation Id** (GUID) from the portal.
117+
- Paste it into `identity_validation_id` in `terraform-infrastructure/terraform.tfvars`.
118+
- Run `terraform apply` again.
119+
120+
Push/merge to `main` (or run the workflow via `workflow_dispatch`). The GitHub Actions workflow will build + sign the binaries.
96121

97122
## Azure Key Vault for workflow variables
98123

99-
Terraform creates a Key Vault by default and wires RBAC so:
100-
- your current identity (the identity running `terraform apply`) can set secrets (`Key Vault Secrets Officer`)
101-
- the GitHub Actions service principal can read secrets (`Key Vault Secrets User`)
124+
Terraform creates a Key Vault by default and wires RBAC so the workflow can read signing inputs.
125+
126+
| Principal | Role assigned | Why it exists |
127+
| --- | --- | --- |
128+
| Your current identity (the identity running `terraform apply`) | `Key Vault Secrets Officer` | Can set secrets during provisioning |
129+
| GitHub Actions service principal | `Key Vault Secrets User` | Can read secrets at workflow runtime |
102130

103-
> This Key Vault is **RBAC-enabled** (`rbac_authorization_enabled = true`). You will see access under **Key Vault → Access control (IAM)** (not under “Access policies”).
131+
> [!NOTE]
132+
> - This Key Vault is **RBAC-enabled** (`rbac_authorization_enabled = true`). You will see access under **Key Vault → Access control (IAM)** (not under “Access policies”).
133+
> - The GitHub Actions workflow reads them from Key Vault at runtime using the Azure CLI.
104134
105135
Least privilege options:
106-
- Set `keyvault_populate_secrets = false` if you do not want Terraform to write secrets into Key Vault (you can manage secrets yourself and/or set pipeline variables another way).
107-
- If you want to view **Keys** and/or **Certificates** in the Azure portal, opt in to RBAC for your current identity:
108-
- `keyvault_grant_keys_access_to_current = true` (assigns `Key Vault Crypto User`)
109-
- `keyvault_grant_certificates_access_to_current = true` (assigns `Key Vault Certificates User`)
110-
- Or, if you want the simplest “make the portal work” option (broad permissions):
111-
- `keyvault_grant_administrator_to_current = true` (assigns `Key Vault Administrator`)
136+
137+
| Setting / choice | Effect |
138+
| --- | --- |
139+
| `keyvault_populate_secrets = false` | Terraform will not write secrets into Key Vault (you can manage secrets yourself and/or set pipeline variables another way). |
140+
| `keyvault_grant_keys_access_to_current = true` | Grants your current identity `Key Vault Crypto User` (lets you view **Keys** in the portal). |
141+
| `keyvault_grant_certificates_access_to_current = true` | Grants your current identity `Key Vault Certificates User` (lets you view **Certificates** in the portal). |
142+
| `keyvault_grant_administrator_to_current = true` | Simplest “make the portal work” option (broad permissions): grants your current identity `Key Vault Administrator`. |
112143

113144
Terraform also populates these Key Vault secrets during `terraform apply`:
114-
- `artifactSigningEndpoint`
115-
- `artifactSigningAccountName`
116-
- `artifactSigningCertificateProfileName`
117-
- `artifactSigningIdentityValidationId` (placeholder until you set it after portal validation)
118145

119-
The GitHub Actions workflow reads them from Key Vault at runtime using the Azure CLI.
146+
| Secret name | Notes |
147+
| --- | --- |
148+
| `artifactSigningEndpoint` | Service endpoint used by the workflow |
149+
| `artifactSigningAccountName` | Artifact Signing account name |
150+
| `artifactSigningCertificateProfileName` | Certificate profile name |
151+
| `artifactSigningIdentityValidationId` | Optional. Only needed if Terraform or the workflow will create the certificate profile. |
152+
120153

121154
If signing fails with 403, validate:
122-
- Endpoint matches region
123-
- GitHub Actions identity has `Artifact Signing Certificate Profile Signer` at the certificate profile scope
124155

125-
## GitHub Actions (recommended)
156+
| Check | Expected |
157+
| --- | --- |
158+
| Endpoint matches region | `artifactSigningEndpoint` points to the correct region |
159+
| Role at certificate profile scope | GitHub Actions identity has `Artifact Signing Certificate Profile Signer` at the certificate profile scope |
126160

127-
This repo includes a GitHub Actions workflow that performs the end-to-end signing flow:
161+
## GitHub Actions
162+
163+
This repo includes a GitHub Actions workflow that performs the signing flow:
128164
- build/publish unsigned exe
129165
- load signing inputs from Key Vault
130-
- create the certificate profile if missing (after you complete portal identity validation)
131166
- sign + verify + upload artifact
132167

133168
Workflow file:
134169
- [.github/workflows/artifact-signing.yml](.github/workflows/artifact-signing.yml)
135170

136-
### Enable GitHub OIDC (Terraform)
137-
138-
1) In [terraform-infrastructure/terraform.tfvars](terraform-infrastructure/terraform.tfvars), set:
139-
- `github_enabled = true`
140-
- `github_owner = "<your-owner>"`
141-
- `github_repo = "<your-repo>"`
142-
- `github_ref = "refs/heads/main"` (or your default branch)
143-
144-
2) Run:
145-
146-
```pwsh
147-
cd terraform-infrastructure
148-
terraform apply -auto-approve
149-
```
150-
151-
Terraform outputs the Entra app client id for GitHub:
152-
- `github_app_client_id`
153-
154-
### Configure GitHub repo secrets
155-
156-
In GitHub → Settings → Secrets and variables → Actions, set these **Secrets**:
171+
### GitHub OIDC
157172

158-
- `AZURE_CLIENT_ID` = Terraform output `github_app_client_id`
159-
- `AZURE_TENANT_ID` = your Entra tenant id
160-
- `AZURE_SUBSCRIPTION_ID` = your Azure subscription id
161-
- `KEYVAULT_NAME` = Terraform output `keyvault_name`
162-
- `ARTIFACT_SIGNING_RESOURCE_GROUP` = Terraform output `resource_group_name`
173+
Why this exists:
174+
- GitHub Actions must authenticate to Azure to (1) read signing inputs from Key Vault and (2) call the Artifact Signing service.
175+
- OIDC lets GitHub obtain an Azure token **without** storing an Azure client secret in GitHub.
163176

164-
Then push to `main` (or run the workflow manually via `workflow_dispatch`).
177+
In this repo, OIDC + GitHub secrets are configured automatically by the bootstrap step in **Deploy with Terraform**.
165178

166-
Notes:
167-
- The workflow uses OIDC, so there is no Azure client secret.
168-
- The GitHub Actions identity must have RBAC to read Key Vault secrets (`Key Vault Secrets User`) and to sign (`Artifact Signing Certificate Profile Signer`).
169-
- If you want the workflow to auto-create the certificate profile, you typically also need RG `Contributor` for that identity (`assign_contributor_role_to_github_sp = true`).
179+
Manual (service requirement): complete the **Identity validation** step in the Azure Portal when prompted.
170180

171181
## Azure Portal link
172182

0 commit comments

Comments
 (0)