Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions OneBranchPipelines/build-release-package-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ parameters:
type: boolean
default: true

# Authenticode-sign the Windows native .pyd extensions with ESRP (CP-230012).
# Enabled by default so BOTH Official and NonOfficial runs produce signed wheels.
# Disable only for fast dev iterations where ESRP signing is not needed.
- name: signWindowsBinaries
displayName: 'Sign Windows native .pyd binaries (ESRP)'
type: boolean
default: true

# =========================
# PLATFORM CONFIGURATIONS
# =========================
Expand Down Expand Up @@ -396,6 +404,9 @@ extends:
shortPyVer: ${{ config.pyVer }}
architecture: ${{ config.arch }}
oneBranchType: '${{ variables.effectiveOneBranchType }}'
# Sign the native .pyd with ESRP (CP-230012) in both Official and
# NonOfficial runs.
signWindowsBinaries: ${{ parameters.signWindowsBinaries }}
# Phase 2: install the external ODBC wheel before pytest (bundled driver removed).
odbcDependsOn:
- ConsolidateOdbc
Expand Down
95 changes: 95 additions & 0 deletions OneBranchPipelines/stages/build-windows-single-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ parameters:
- name: installOdbcWheel
type: boolean
default: false
# Sign the native Python extension (.pyd) with ESRP (CP-230012) before it is
# packaged into the wheel. Runs in BOTH Official and NonOfficial runs. Set to
# false only for fast local/dev iterations where ESRP creds are unavailable.
- name: signWindowsBinaries
type: boolean
default: true

stages:
- stage: ${{ parameters.stageName }}
Expand Down Expand Up @@ -65,6 +71,15 @@ stages:
shortPyVer: ${{ parameters.shortPyVer }}
# Target architecture (can differ from host for cross-compilation)
targetArch: ${{ parameters.architecture }}
# Arch suffix used in the built extension's file name. build.bat maps the
# target arch to the wheel/PE tag: x64 -> amd64, arm64 -> arm64, x86 -> win32.
# Used to name the exact .pyd for signing (no wildcards).
${{ if eq(parameters.architecture, 'arm64') }}:
pydArch: 'arm64'
${{ elseif eq(parameters.architecture, 'x86') }}:
pydArch: 'win32'
${{ else }}:
pydArch: 'amd64'
# System access token for authenticated downloads (e.g., GitHub artifacts)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Expand Down Expand Up @@ -248,6 +263,32 @@ stages:
displayName: 'Build PYD for $(targetArch)'
continueOnError: false

# =========================
# ESRP CODE SIGNING (Windows native extension)
# =========================
# Authenticode-sign the freshly built ddbc_bindings.*.pyd with CP-230012
# BEFORE it is copied to the bindings/apiScan artifacts and packaged into
# the wheel by setup.py bdist_wheel. Signing in place (rather than
# unpack/repack of the .whl) keeps wheel RECORD hashes correct.
#
# Runs in BOTH Official and NonOfficial runs (no oneBranchType gate) so
# every published wheel contains a signed extension. The `pattern` names
# the exact .pyd for THIS Python version + arch (e.g.
# ddbc_bindings.cp312-amd64.pyd) so signing is scoped precisely to our own
# extension - NOT the vcredist msvcp140.dll that build.bat also copies next
# to it (that DLL is already signed by Microsoft and is not ours to sign).
- ${{ if eq(parameters.signWindowsBinaries, true) }}:
- template: /OneBranchPipelines/steps/compound-esrp-code-signing-step.yml@self
parameters:
appRegistrationClientId: '$(SigningAppRegistrationClientId)'
appRegistrationTenantId: '$(SigningAppRegistrationTenantId)'
authAkvName: '$(SigningAuthAkvName)'
authSignCertName: '$(SigningAuthSignCertName)'
esrpClientId: '$(SigningEsrpClientId)'
esrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)'
signPath: '$(Build.SourcesDirectory)\mssql_python'
pattern: 'ddbc_bindings.cp$(shortPyVer)-$(pydArch).pyd'

# =========================
# MSSQL_PY_CORE INSTALLATION
# =========================
Expand Down Expand Up @@ -354,6 +395,60 @@ stages:
python setup.py bdist_wheel
displayName: 'Build wheel package'

# =========================
# SIGNED-WHEEL EVIDENCE (verification only)
# =========================
# Prove that the .pyd embedded in the freshly built wheel is
# Authenticode-signed. This is a read-only check (unpack to a temp dir and
# inspect) - it does NOT modify or repack the wheel, so RECORD hashes are
# untouched. Provides the signing evidence attached to the GitHub PR.
- ${{ if eq(parameters.signWindowsBinaries, true) }}:
- pwsh: |
$ErrorActionPreference = 'Stop'

python -m pip install --upgrade wheel | Out-Null

$verifyRoot = Join-Path "$(Agent.TempDirectory)" "wheel-sign-verify"
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $verifyRoot
New-Item -ItemType Directory -Force -Path $verifyRoot | Out-Null

$wheels = @(Get-ChildItem "$(Build.SourcesDirectory)\dist" -Filter *.whl -File)
if ($wheels.Count -eq 0) {
Write-Error "No wheel found in dist\ to verify"
exit 1
}

foreach ($wheel in $wheels) {
Write-Host "Unpacking $($wheel.Name) for signature verification"
$dest = Join-Path $verifyRoot $wheel.BaseName
New-Item -ItemType Directory -Force -Path $dest | Out-Null
python -m wheel unpack "$($wheel.FullName)" --dest "$dest"
}

$nativeFiles = @(Get-ChildItem $verifyRoot -Recurse -Include ddbc_bindings.*.pyd -File)
if ($nativeFiles.Count -eq 0) {
Write-Error "No ddbc_bindings .pyd found inside the built wheel(s)"
exit 1
}

$invalid = @()
foreach ($file in $nativeFiles) {
$signature = Get-AuthenticodeSignature $file.FullName
Write-Host "$($file.Name): $($signature.Status) [$($signature.SignerCertificate.Subject)]"
if ($signature.Status -ne 'Valid') {
$invalid += $file.FullName
}
}

if ($invalid.Count -gt 0) {
Write-Error "Wheel contains unsigned/invalid native binaries:`n$($invalid -join "`n")"
exit 1
}

Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $verifyRoot
Write-Host "Verified: the .pyd inside the built wheel is Authenticode 'Valid'."
displayName: 'Verify signed .pyd inside built wheel'

# =========================
# ARTIFACT PUBLISHING
# =========================
Expand Down
Loading
Loading