Skip to content

Commit 3c6fb4d

Browse files
authored
Merge pull request #3 from MicrosoftCloudEssentials-LearningHub/devops-base
DevOps base
2 parents 04c75f6 + bdca844 commit 3c6fb4d

51 files changed

Lines changed: 1704 additions & 877 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Builds a demo .NET exe, pulls signing inputs from Key Vault,
2+
# then signs and verifies the binary using SignTool + the Azure Artifact Signing dlib.
3+
4+
name: Artifact Signing Demo
5+
6+
on:
7+
push:
8+
branches: [ main ]
9+
workflow_dispatch: {}
10+
11+
permissions:
12+
contents: read
13+
id-token: write
14+
15+
jobs:
16+
sign:
17+
runs-on: windows-latest
18+
19+
env:
20+
BUILD_CONFIGURATION: Release
21+
RUNTIME_IDENTIFIER: win-x64
22+
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+
28+
# Non-secret inputs (configure as GitHub repo variables or secrets):
29+
KEYVAULT_NAME: ${{ secrets.KEYVAULT_NAME }}
30+
ARTIFACT_SIGNING_RESOURCE_GROUP: ${{ secrets.ARTIFACT_SIGNING_RESOURCE_GROUP }}
31+
32+
# Optional override; only needed when creating the cert profile:
33+
CERT_PROFILE_TYPE: PublicTrustTest
34+
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v4
38+
39+
- name: Setup .NET
40+
uses: actions/setup-dotnet@v4
41+
with:
42+
dotnet-version: '8.0.x'
43+
44+
- name: Publish (unsigned)
45+
shell: pwsh
46+
run: |
47+
dotnet publish .\SigningDemo\SigningDemo.csproj -c $env:BUILD_CONFIGURATION -r $env:RUNTIME_IDENTIFIER --self-contained false
48+
49+
- name: Azure Login (OIDC)
50+
uses: azure/login@v2
51+
with:
52+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
53+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
54+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
55+
56+
- name: Load signing variables from Key Vault
57+
shell: pwsh
58+
run: |
59+
$ErrorActionPreference = 'Stop'
60+
61+
if ([string]::IsNullOrWhiteSpace($env:KEYVAULT_NAME)) { throw "Missing KEYVAULT_NAME (set as a GitHub secret)." }
62+
63+
pwsh -NoProfile -ExecutionPolicy Bypass -File .\scripts\load-signing-config-from-kv.ps1
64+
65+
- name: Ensure certificate profile exists (optional)
66+
if: ${{ env.AUTO_CREATE_CERT_PROFILE == 'true' }}
67+
shell: pwsh
68+
run: |
69+
$ErrorActionPreference = 'Stop'
70+
71+
$rg = "$env:ARTIFACT_SIGNING_RESOURCE_GROUP".Trim()
72+
if ([string]::IsNullOrWhiteSpace($rg)) { throw "Missing ARTIFACT_SIGNING_RESOURCE_GROUP" }
73+
74+
$accountName = "$env:ARTIFACT_SIGNING_ACCOUNT_NAME".Trim()
75+
$profileName = "$env:ARTIFACT_SIGNING_CERT_PROFILE_NAME".Trim()
76+
$profileType = "$env:CERT_PROFILE_TYPE".Trim()
77+
if ([string]::IsNullOrWhiteSpace($profileType)) { $profileType = 'PublicTrust' }
78+
79+
if ([string]::IsNullOrWhiteSpace($accountName)) { throw "Missing ARTIFACT_SIGNING_ACCOUNT_NAME" }
80+
if ([string]::IsNullOrWhiteSpace($profileName)) { throw "Missing ARTIFACT_SIGNING_CERT_PROFILE_NAME" }
81+
82+
$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+
}
86+
87+
$subId = (az account show --query id -o tsv)
88+
if ([string]::IsNullOrWhiteSpace($subId)) { throw "Unable to determine subscription id from Azure CLI context." }
89+
90+
$profileResourceId = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.CodeSigning/codeSigningAccounts/$accountName/certificateProfiles/$profileName"
91+
$profileUrl = "https://management.azure.com$profileResourceId?api-version=2025-10-13"
92+
93+
Write-Host "Checking certificate profile: $profileResourceId"
94+
95+
az rest --method get --url $profileUrl --only-show-errors | Out-Null
96+
$getExit = $LASTEXITCODE
97+
98+
if ($getExit -eq 0) {
99+
Write-Host "Certificate profile exists: $profileName"
100+
exit 0
101+
}
102+
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
111+
}
112+
} | ConvertTo-Json -Depth 10
113+
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"
118+
119+
- name: Sign with Azure Artifact Signing (SignTool + dlib)
120+
shell: pwsh
121+
run: |
122+
$ErrorActionPreference = 'Stop'
123+
124+
$tempRoot = Join-Path "$env:RUNNER_TEMP" "artifact-signing"
125+
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
126+
127+
$nugetExe = Join-Path $tempRoot "nuget.exe"
128+
Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $nugetExe
129+
130+
$sdkOut = Join-Path $tempRoot "sdk"
131+
& $nugetExe install Microsoft.Windows.SDK.BuildTools -x -OutputDirectory $sdkOut | Out-Host
132+
133+
$signtool = Get-ChildItem -Path $sdkOut -Recurse -Filter signtool.exe | Where-Object { $_.FullName -match "\\x64\\signtool\\.exe$" } | Select-Object -First 1
134+
if (-not $signtool) {
135+
$signtool = Get-ChildItem -Path $sdkOut -Recurse -Filter signtool.exe | Where-Object { $_.FullName -match "\\x64\\signtool\\.exe" } | Select-Object -First 1
136+
}
137+
if (-not $signtool) { throw "signtool.exe not found after extracting Microsoft.Windows.SDK.BuildTools" }
138+
139+
$dlibOut = Join-Path $tempRoot "dlib"
140+
& $nugetExe install Microsoft.ArtifactSigning.Client -x -OutputDirectory $dlibOut | Out-Host
141+
142+
$dlib = Get-ChildItem -Path $dlibOut -Recurse -Filter Azure.CodeSigning.Dlib.dll | Where-Object { $_.FullName -match "\\x64\\Azure\\.CodeSigning\\.Dlib\\.dll$" } | Select-Object -First 1
143+
if (-not $dlib) {
144+
$dlib = Get-ChildItem -Path $dlibOut -Recurse -Filter Azure.CodeSigning.Dlib.dll | Select-Object -First 1
145+
}
146+
if (-not $dlib) { throw "Azure.CodeSigning.Dlib.dll not found after extracting Microsoft.ArtifactSigning.Client" }
147+
148+
$metadataPath = Join-Path $tempRoot "metadata.json"
149+
$metadata = @{
150+
Endpoint = "$env:ARTIFACT_SIGNING_ENDPOINT"
151+
CodeSigningAccountName = "$env:ARTIFACT_SIGNING_ACCOUNT_NAME"
152+
CertificateProfileName = "$env:ARTIFACT_SIGNING_CERT_PROFILE_NAME"
153+
CorrelationId = "gha-${{ github.run_id }}"
154+
} | ConvertTo-Json -Depth 4
155+
156+
$metadata | Out-File -FilePath $metadataPath -Encoding utf8
157+
158+
$unsigned = "$env:GITHUB_WORKSPACE\SigningDemo\bin\$env:BUILD_CONFIGURATION\net8.0\$env:RUNTIME_IDENTIFIER\publish\SigningDemo.exe"
159+
if (-not (Test-Path $unsigned)) { throw "Unsigned binary not found at $unsigned" }
160+
161+
$signedDir = "$env:GITHUB_WORKSPACE\artifacts"
162+
New-Item -ItemType Directory -Force -Path $signedDir | Out-Null
163+
$signed = Join-Path $signedDir "SigningDemo.signed.exe"
164+
Copy-Item $unsigned $signed -Force
165+
166+
Write-Host "Using signtool: $($signtool.FullName)"
167+
Write-Host "Using dlib: $($dlib.FullName)"
168+
169+
& $signtool.FullName sign /v /debug /fd SHA256 /tr "http://timestamp.acs.microsoft.com" /td SHA256 /dlib "$($dlib.FullName)" /dmdf "$metadataPath" "$signed"
170+
171+
- name: Verify signature
172+
shell: pwsh
173+
run: |
174+
$ErrorActionPreference = 'Stop'
175+
$signed = "$env:GITHUB_WORKSPACE\artifacts\SigningDemo.signed.exe"
176+
Get-AuthenticodeSignature $signed | Format-List
177+
178+
- name: Upload signed artifact
179+
uses: actions/upload-artifact@v4
180+
with:
181+
name: signed-drop
182+
path: artifacts\

.gitignore

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Local .terraform directories
22
.terraform/
3+
*.terraform.lock.hcl
4+
.terraform.lock.hcl
35
*src/.env
46
app-logs.zip
57
LogFiles
@@ -43,11 +45,3 @@ override.tf.json
4345
# Ignore CLI configuration files
4446
.terraformrc
4547
terraform.rc
46-
47-
# .NET build output
48-
**/bin/
49-
**/obj/
50-
51-
# Visual Studio / VS Code
52-
.vs/
53-
.vscode/

0 commit comments

Comments
 (0)