- (注意:VB.NET或C#答案都可以。)*
使用new SDK安装上载的证书时遇到问题。
我使用REST API进行上传,因为新的SDK似乎没有办法完成这一部分。显然,使用旧的,即将被弃用的版本上传是可能的,如here所示,但时间无情地向前迈进,我们必须跟上。
问题是,当我用新的网站数据推送更新时,我得到了一个404:
状态:404(Not Found)-找不到证书370 A4097 E85476 AE 65545702 B410866882 AD 4541。
指纹和pfx证书上的指纹吻合,所以我们知道那部分是有效的。
我上传的时候收到了202,所以那部分也正常。但我本以为证书会出现在传送门里,但事实并非如此。这有点奇怪。
下面是我的完整代码,但为了简洁起见,这里是相关部分:
上传
oApiCert = New Certificate With {.Location = oWebSiteData.Location.DisplayName}
oApiCert.Properties.CanonicalName = sCanonicalName
oApiCert.Properties.ServerFarmId = oWebSiteData.AppServicePlanId.ToString
oApiCert.Properties.Password = My.Resources.PfxPassword
oApiCert.Properties.PfxBlob = oX509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
oApiCert.Properties.HostNames.AddRange(oX509Cert.SanHostNames)
sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
oClient = New HttpClient
oClient.DefaultRequestHeaders.Add("Authorization", "Bearer " & oToken.Token)
oClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
oResponse = Await oClient.PutAsync(sUrl, oContent)
oResponse.EnsureSuccessStatusCode()
更新
oSslStates = oWebSiteData.HostNameSslStates
oHostNames = oHostNames.Intersect(oWebSiteData.HostNames).ToList
oHostNames.ForEach(Sub(HostName)
oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name = HostName)
If oSslState Is Nothing Then
oSslState = New HostNameSslState With {.Name = HostName}
oSslStates.Add(oSslState)
End If
oSslState.Thumbprint = BinaryData.FromObjectAsJson(oX509Cert.Thumbprint)
oSslState.SslState = HostNameBindingSslState.SniEnabled
oSslState.ToUpdate = True
End Sub)
oSitePatch = New SitePatchInfo
For Each oState In oSslStates
If oState.ToUpdate Then
oSitePatch.HostNameSslStates.Add(oState)
End If
Next
oWebSiteResource.Update(oSitePatch) ' <-- 404 here '
这段代码是从LetsEncrypt-SiteExtension
包移植的,可以在这里找到。该库使用旧的SDK。
如果有其他可行的方法,我不一定非得用这种方法。问题是SDK的文档和代码示例少得可怜。
如何使用新的SDK在Azure应用服务上上载和安装第三方TLS证书?有人在这方面取得了成功吗?
完整编码
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net.Http
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports System.Threading.Tasks
Imports Azure
Imports Azure.Core
Imports Azure.Identity
Imports Azure.ResourceManager
Imports Azure.ResourceManager.AppService
Imports Azure.ResourceManager.AppService.Models
Imports Azure.ResourceManager.Resources
Imports Matrix.Common
Imports Newtonsoft.Json
Imports Nito.AsyncEx
Friend Module Program
Friend Sub Main()
AsyncContext.Run(Async Function()
Await UploadCert()
End Function)
End Sub
Private Async Function UploadCert() As Task
Dim oWebSiteResponse As Response(Of WebSiteResource)
Dim oWebSiteResource As WebSiteResource
Dim oResourceGroup As ResourceGroupResource
Dim sCanonicalName As String
Dim oSubscription As SubscriptionResource
Dim oWebSiteData As WebSiteData
Dim sJsonContent As String
Dim oCredential As ClientSecretCredential
Dim oSslStates As IList(Of HostNameSslState)
Dim oHostNames As List(Of String)
Dim aHostNames As String()
Dim oSitePatch As SitePatchInfo
Dim oArmClient As ArmClient
Dim oResponse As HttpResponseMessage
Dim oX509Cert As X509Certificate2
Dim oSslState As HostNameSslState
Dim aScopeUrl As String()
Dim sCertName As String
Dim oContext As TokenRequestContext
Dim oContent As StringContent
Dim oApiCert As Certificate
Dim oClient As HttpClient
Dim oToken As AccessToken
Dim oUrl As List(Of String)
Dim sUrl As String
oX509Cert = Utils.GetCertificate(My.Resources.CertFriendlyName)
aHostNames = My.Resources.HostNames.Split(",", StringSplitOptions.RemoveEmptyEntries)
oHostNames = New List(Of String)(aHostNames)
sCanonicalName = oHostNames.First
sCertName = $"{sCanonicalName}-{oX509Cert.Thumbprint}"
oUrl = New List(Of String) From {
"subscriptions",
My.Resources.SubscriptionId,
"resourceGroups",
My.Resources.ResourceGroup,
"providers",
"Microsoft.Web",
"certificates",
sCertName
}
sUrl = String.Join("/", oUrl)
sUrl = $"/{sUrl}?api-version=2022-03-01"
oCredential = New ClientSecretCredential(My.Resources.TenantId, My.Resources.ClientId, My.Resources.ClientSecret)
oArmClient = New ArmClient(oCredential)
oSubscription = Await oArmClient.GetDefaultSubscriptionAsync
oResourceGroup = Await oSubscription.GetResourceGroups.GetAsync(My.Resources.ResourceGroup)
oWebSiteResponse = Await oResourceGroup.GetWebSiteAsync(My.Resources.AppServiceName)
oWebSiteResource = oWebSiteResponse.Value
oWebSiteData = oWebSiteResource.Data
aScopeUrl = New String() {$"{My.Resources.ManagementEndpoint}.default"}
oContext = New TokenRequestContext(aScopeUrl)
oToken = Await oCredential.GetTokenAsync(oContext)
oApiCert = New Certificate With {.Location = oWebSiteData.Location.DisplayName}
oApiCert.Properties.CanonicalName = sCanonicalName
oApiCert.Properties.ServerFarmId = oWebSiteData.AppServicePlanId.ToString
oApiCert.Properties.Password = My.Resources.PfxPassword
oApiCert.Properties.PfxBlob = oX509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
oApiCert.Properties.HostNames.AddRange(oX509Cert.SanHostNames)
sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
oClient = New HttpClient
oClient.DefaultRequestHeaders.Add("Authorization", "Bearer " & oToken.Token)
oClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
oResponse = Await oClient.PutAsync(sUrl, oContent)
oResponse.EnsureSuccessStatusCode()
oSslStates = oWebSiteData.HostNameSslStates
oHostNames = oHostNames.Intersect(oWebSiteData.HostNames).ToList
oHostNames.ForEach(Sub(HostName)
oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name = HostName)
If oSslState Is Nothing Then
oSslState = New HostNameSslState With {.Name = HostName}
oSslStates.Add(oSslState)
End If
oSslState.Thumbprint = BinaryData.FromObjectAsJson(oX509Cert.Thumbprint)
oSslState.SslState = HostNameBindingSslState.SniEnabled
oSslState.ToUpdate = True
End Sub)
oSitePatch = New SitePatchInfo
For Each oState In oSslStates
If oState.ToUpdate Then
oSitePatch.HostNameSslStates.Add(oState)
End If
Next
oWebSiteResource.Update(oSitePatch) ' <-- 404 here '
End Function
End Module
1条答案
按热度按时间uelo1irk1#
天啊......那真是一次冒险!
好吧,在经历了很多痛苦之后,我终于找到了答案。(旁注:很高兴知道与Create/Update Certificate REST API等效的SDK,所以我还在寻找...)
技巧(变通方案?)是先将证书上传到密钥库,然后从那里将其部署到我们的应用程序服务。
一旦cert在保险库中,我们接下来要做的就是在PUT调用的JSON主体(不区分大小写)中提供四个值:
.Location
* 例如美国东部 *.Properties.KeyVaultSecretName
* 保管库中证书的名称 *.Properties.ServerFarmId
* 即AppService.AppServicePlanId.ToString*.Properties.KeyVaultId
* 即KeyVault.Value.Id*就是这样没有字节数组,没有密码,仅此而已。
除了一件事。。我们必须在Vault上授予适当的访问策略:
第一个用于将证书上传到Vault,第二个用于部署。
Microsoft Azure App Service主体的资源标识符为
abfa0a7c-a6b6-4736-8310-5855508787cd
,但Azure Gov云环境除外,在Azure Gov云环境中,资源标识符为6a02c803-dafd-4136-b4c3-5a6f318b4714
。就像这样:
像这样上传到vault:
现在我们可以将其部署到我们的App Service:
当我偶然发现这个seven-year-old blog post时,我发现了这一切(为多年来保留有价值的内容而欢呼三声)。另外,这里有一个QuickStart Template的概念。
关于这篇文章有一点值得注意:它声称,一个不受保护的PFX是必要的,这工作。然而,在我的测试中,我遇到了相反的情况:至少对我来说,它与密码保护的PFX一起工作得很好。我不打算花时间去找出这种差异我就跟着它走保护好我的PFXs那就这样吧YMMV
如果它可能会帮助别人,这里是完整的工作代码: