azure - Azure 构建管道:是否可以使用存储在 Key Vault 中的代码签名证书在 VSBuild 任务中对 MSIX 进行签名?
问题描述
当使用代码签名证书 (*.PFX) 时,我可以在 VSBuild 任务中对 MSIX 文件进行签名,该证书存储为 Build Pipeline 库部分中的安全文件,使用以下设置(为简洁起见截断):
注意:关键是我们如何分配p:PackageCertificateKeyFile
参数。
- task: AzureKeyVault@2
inputs:
azureSubscription: 'Dev (SomeGuid)'
KeyVaultName: 'SomeKeyVault'
SecretsFilter: 'SomeCertPassword'
RunAsPreJob: false
- task: DownloadSecureFile@1
name: signingCert
inputs:
secureFile: 'SomeCertName.pfx'
- task: VSBuild@1
inputs:
platform: '$(buildPlatform)'
solution: '$(solution)'
configuration: '$(buildConfiguration)'
msbuildArgs: '
/p:AppInstallerUri=$(msixInstallUrl)
/p:AppxBundle=Never
/p:AppxBundlePlatforms="$(buildPlatform)"
/p:AppxPackageDir="$(Build.ArtifactStagingDirectory)/"
/p:AppxPackageSigningEnabled=true
/p:GenerateAppInstallerFile=true
/p:PackageCertificateThumbprint=""
/p:PackageCertificateKeyFile="$(signingCert.secureFilePath)"
/p:PackageCertificatePassword="$(SomeCertPassword)"
/p:UapAppxPackageBuildMode=SideLoadOnly
'
但是,作为将代码签名证书存储在管道库的安全文件部分中的替代方法,我想将其存储在 Key Vault 的证书部分中,然后在AzureKeyVault
任务中检索它。因此,YAML 看起来像这样(为简洁起见被截断):
- task: AzureKeyVault@2
inputs:
azureSubscription: 'Dev (SomeGuid)'
KeyVaultName: 'SomeKeyVault'
SecretsFilter: 'SomeCertName,SomeCertPassword'
RunAsPreJob: false
- task: VSBuild@1
inputs:
platform: '$(buildPlatform)'
solution: '$(solution)'
configuration: '$(buildConfiguration)'
msbuildArgs: '
/p:AppInstallerUri=$(msixInstallUrl)
/p:AppxBundle=Never
/p:AppxBundlePlatforms="$(buildPlatform)"
/p:AppxPackageDir="$(Build.ArtifactStagingDirectory)/"
/p:AppxPackageSigningEnabled=true
/p:GenerateAppInstallerFile=true
/p:PackageCertificateThumbprint=""
/p:PackageCertificateKeyFile="$(SomeCertName)"
/p:PackageCertificatePassword="$(SomeCertPassword)"
/p:UapAppxPackageBuildMode=SideLoadOnly
'
想要这样做的原因是因为我cannot find *.appinstaller
在尝试运行单独的MSIX Code Signing
任务时得到了文件。在同一个构建任务中对包进行签名似乎更简单、更容易。
但是,我收到以下错误:
错误 APPX0104:找不到证书文件“***”。C:\Program 文件 (x86)\Microsoft Visual
据我所知,当从AzureKeyVault
任务变量中检索证书文件时,它被存储为字符串而不是文件。但是,MSBuild 任务需要一个文件。我尝试在网上搜索并尝试了一些 Powershell 脚本将导入的AzureKeyVault
任务变量从字符串转换为 base64,但我没有运气(请参阅下面的链接以供参考)。我实际上已经尝试了大约 40 种不同的方法来做到这一点,我担心列出它们只会混淆问题。结果,我觉得最好展示我正在尝试做的事情并询问它是否可能,如果是,如何?
解决方案
I was able to solve this by installing the MSIX Packaging extension to my Azure subscription, as referenced here: https://docs.microsoft.com/en-us/windows/msix/desktop/msix-packaging-extension?tabs=yaml
This extension is extremely useful because, much like the Azure Sign Tool (AST), which seems to be the only alternative to this method, it handles the converting of the code signing certificate string
to base64
and then signing the package. Obviously, there is a lot going on under the hood. You will need to configure your build pipeline to have access to the key vault. However, unlike the AST, this extension is far easier to use because it does not require all of the configurations that the AST requires. This is because the AST handles the task of retrieving the code signing certificate from the key vault whereas in my implementation I just use the Azure Key Vault task to pull the file into my build pipeline, which is also fairly easy to setup and implement. From my perspective, AST is too complicated to be useful.
Below is the full implementation that I use, which also parameterizes the Key Vaults that store the various code signing certificates by our subscriptions/environments.
trigger:
branches:
include:
- refs/heads/main
- refs/heads/demo
- refs/heads/develop
# The following is for testing purposes only. For now we disable automatically building for commits to feature branches in develop.
# - refs/heads/feature*
pool:
vmImage: 'windows-latest'
# Variable Declaration(s)
# Note: We must use the name/value combination because we are (dynamically) mixing in variable groups.
variables:
- name: buildPlatform
value: 'x64'
- name: buildConfiguration
value: 'Release'
- name: major
value: 1
- name: minor
value: 0
- name: build
value: 0
- name: revision
value: $[counter('rev', 0)]
# Set conditional variable group based off of the trigger branch (see trigger section above).
# Note: If we use SourceBranch then it will append `refs/heads/` to the SourceBranchName.
- ${{ if eq(variables['build.SourceBranch'], 'refs/heads/main') }}:
- group: PROD
- ${{ if eq(variables['build.SourceBranch'], 'refs/heads/qa') }}:
- group: QA
- ${{ if eq(variables['build.SourceBranch'], 'refs/heads/develop') }}:
- group: DEV
# The following is for testing purposes only.
# Note: If you want to run for a feature branch in develop then you must uncomment this line. Otherwise, you will get an error that the pipeline is not valid.
# "Step AzureKeyVault input ConnectedServiceName references service connection $(azureSubscription) which could not be found."
- ${{ if contains(variables['Build.SourceBranch'], 'refs/heads/feature') }}:
- group: DEV
steps:
# Update the AppXManifest file's major, minor, build, and revision parameters with the current values.
# Note: We use the day's build counter to determine the revision number.
# By rule, this counter is limited to 255 values per day and will start over at 1!
- powershell: |
[Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")
$path = "SomeAppName.MsixInstaller/Package.appxmanifest";
$doc = [System.Xml.Linq.XDocument]::Load($path);
$xName = [System.Xml.Linq.XName]::Get("{http://schemas.microsoft.com/appx/manifest/foundation/windows10}Identity");
$doc.Root.Element($xName).Attribute("Version").Value = "$(major).$(minor).$(build).$(revision)";
$doc.Save($path);
displayName: 'Version the Package Manifest'
# Retrieve the code signing certificate from the Key Vault.
# Note: 20211008 - Ignore the errors because the values are correct.
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: Retrieve Variable(s)'
inputs:
azureSubscription: '$(azureSubscription)'
KeyVaultName: '$(keyVaultName)'
SecretsFilter: 'SomeCertName'
RunAsPreJob: false
# Specify the minimum version of NuGet that we want to use to restore the solution's NuGet packages.
- task: NuGetToolInstaller@1
displayName: 'NuGet: Use v5.11.0'
inputs:
versionSpec: 5.11.0
checkLatest: true
# Run NuGet restore to download the NuGet packages before building the solution.
- task: NuGetCommand@2
displayName: 'NuGet: Run restore'
inputs:
command: 'restore'
restoreSolution: '**/*.sln'
# Build the MSIX package.
# Note: Set AppxPackageSigningEnabled=false to avoid a build error (i.e., missing thumbprint). We will sign the package in a subsequent step.
- task: VSBuild@1
displayName: 'VSBuild: Build the MSIX package'
inputs:
clean: true
configuration: '$(buildConfiguration)'
platform: '$(buildPlatform)'
restoreNugetPackages: false #adding this because we're doing an explicit restore above, lets skip the implicit restore.
solution: '**/*.sln'
msbuildArgs: '
/p:AppInstallerUri="$(appServiceUri)"
/p:AppxPackageDir="$(Build.ArtifactStagingDirectory)/"
/p:AppxBundle=Never
/p:AppxBundlePlatforms="$(buildPlatform)"
/p:UapAppxPackageBuildMode=SideLoadOnly
/p:GenerateAppInstallerFile=true
/p:AppxPackageSigningEnabled=false
'
# Sign the MSIX package with the code signing certificate from the Key Vault.
- task: MsixSigning@1
displayName: 'Code Sign MSIX Package'
inputs:
package: '$(Build.ArtifactStagingDirectory)\**\*.msix'
certificateType: 'base64'
encodedCertificate: '$(DeveloperCodeSigningCertFile)'
continueOnError: true
# Publish the MSIX package in preparation for deployment. This will be consumed by the Release Pipeline.
- task: PublishBuildArtifacts@1
displayName: 'Publish ArtifactName: drop'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
# Run tests (if any).
# Note: This tests sources that are found matching the given filter '**\*test*.dll,!**\*TestAdapter.dll,!**\obj\**
- task: VSTest@2
displayName: 'VSTest: Run Tests (if any)'
inputs:
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
推荐阅读
- javascript - 控制台检查单选按钮
- reactjs - google react-places-autocomplete api将搜索建议限制在澳大利亚/单个国家
- c# - InputField 和抓取事件
- r - 基于来自R中不同大小的数据帧的多个条件匹配值
- r - 如何使用 `exams2nops` 在 `exams` 生成的 pdf 中添加一行?
- swift - 协议的二级实现被忽略,有利于默认实现
- node.js - Discord.js 机器人:组织命令
- bash - libcurl c 代码中的 CURLOPT_COOKIEFILE 选项
- java - 使用 Java 的 Apache Sedona (Geospark) SQL:SQL 语句期间出现 ClassNotFoundException
- android - 是否可以在不需要用户更新到新版本的情况下对已发布的应用程序进行小的更改