使用最新SDK在Azure应用服务上安装TLS证书时出现问题

k7fdbhmy  于 2023-05-23  发布在  其他
关注(0)|答案(1)|浏览(295)
  • (注意:VB.NET或C#答案都可以。)*

使用new SDK安装上载的证书时遇到问题。
我使用REST API进行上传,因为新的SDK似乎没有办法完成这一部分。显然,使用旧的,即将被弃用的版本上传是可能的,如here所示,但时间无情地向前迈进,我们必须跟上。
问题是,当我用新的网站数据推送更新时,我得到了一个404:
状态:404(Not Found)-找不到证书370 A4097 E85476 AE 65545702 B410866882 AD 4541。
指纹和pfx证书上的指纹吻合,所以我们知道那部分是有效的。
我上传的时候收到了202,所以那部分也正常。但我本以为证书会出现在传送门里,但事实并非如此。这有点奇怪。
下面是我的完整代码,但为了简洁起见,这里是相关部分:

上传

  1. oApiCert = New Certificate With {.Location = oWebSiteData.Location.DisplayName}
  2. oApiCert.Properties.CanonicalName = sCanonicalName
  3. oApiCert.Properties.ServerFarmId = oWebSiteData.AppServicePlanId.ToString
  4. oApiCert.Properties.Password = My.Resources.PfxPassword
  5. oApiCert.Properties.PfxBlob = oX509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
  6. oApiCert.Properties.HostNames.AddRange(oX509Cert.SanHostNames)
  7. sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
  8. oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
  9. oClient = New HttpClient
  10. oClient.DefaultRequestHeaders.Add("Authorization", "Bearer " & oToken.Token)
  11. oClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
  12. oResponse = Await oClient.PutAsync(sUrl, oContent)
  13. oResponse.EnsureSuccessStatusCode()

更新

  1. oSslStates = oWebSiteData.HostNameSslStates
  2. oHostNames = oHostNames.Intersect(oWebSiteData.HostNames).ToList
  3. oHostNames.ForEach(Sub(HostName)
  4. oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name = HostName)
  5. If oSslState Is Nothing Then
  6. oSslState = New HostNameSslState With {.Name = HostName}
  7. oSslStates.Add(oSslState)
  8. End If
  9. oSslState.Thumbprint = BinaryData.FromObjectAsJson(oX509Cert.Thumbprint)
  10. oSslState.SslState = HostNameBindingSslState.SniEnabled
  11. oSslState.ToUpdate = True
  12. End Sub)
  13. oSitePatch = New SitePatchInfo
  14. For Each oState In oSslStates
  15. If oState.ToUpdate Then
  16. oSitePatch.HostNameSslStates.Add(oState)
  17. End If
  18. Next
  19. oWebSiteResource.Update(oSitePatch) ' <-- 404 here '

这段代码是从LetsEncrypt-SiteExtension包移植的,可以在这里找到。该库使用旧的SDK。
如果有其他可行的方法,我不一定非得用这种方法。问题是SDK的文档和代码示例少得可怜。
如何使用新的SDK在Azure应用服务上上载和安装第三方TLS证书?有人在这方面取得了成功吗?

完整编码

  1. Imports System
  2. Imports System.Collections.Generic
  3. Imports System.Linq
  4. Imports System.Net.Http
  5. Imports System.Security.Cryptography.X509Certificates
  6. Imports System.Text
  7. Imports System.Threading.Tasks
  8. Imports Azure
  9. Imports Azure.Core
  10. Imports Azure.Identity
  11. Imports Azure.ResourceManager
  12. Imports Azure.ResourceManager.AppService
  13. Imports Azure.ResourceManager.AppService.Models
  14. Imports Azure.ResourceManager.Resources
  15. Imports Matrix.Common
  16. Imports Newtonsoft.Json
  17. Imports Nito.AsyncEx
  18. Friend Module Program
  19. Friend Sub Main()
  20. AsyncContext.Run(Async Function()
  21. Await UploadCert()
  22. End Function)
  23. End Sub
  24. Private Async Function UploadCert() As Task
  25. Dim oWebSiteResponse As Response(Of WebSiteResource)
  26. Dim oWebSiteResource As WebSiteResource
  27. Dim oResourceGroup As ResourceGroupResource
  28. Dim sCanonicalName As String
  29. Dim oSubscription As SubscriptionResource
  30. Dim oWebSiteData As WebSiteData
  31. Dim sJsonContent As String
  32. Dim oCredential As ClientSecretCredential
  33. Dim oSslStates As IList(Of HostNameSslState)
  34. Dim oHostNames As List(Of String)
  35. Dim aHostNames As String()
  36. Dim oSitePatch As SitePatchInfo
  37. Dim oArmClient As ArmClient
  38. Dim oResponse As HttpResponseMessage
  39. Dim oX509Cert As X509Certificate2
  40. Dim oSslState As HostNameSslState
  41. Dim aScopeUrl As String()
  42. Dim sCertName As String
  43. Dim oContext As TokenRequestContext
  44. Dim oContent As StringContent
  45. Dim oApiCert As Certificate
  46. Dim oClient As HttpClient
  47. Dim oToken As AccessToken
  48. Dim oUrl As List(Of String)
  49. Dim sUrl As String
  50. oX509Cert = Utils.GetCertificate(My.Resources.CertFriendlyName)
  51. aHostNames = My.Resources.HostNames.Split(",", StringSplitOptions.RemoveEmptyEntries)
  52. oHostNames = New List(Of String)(aHostNames)
  53. sCanonicalName = oHostNames.First
  54. sCertName = $"{sCanonicalName}-{oX509Cert.Thumbprint}"
  55. oUrl = New List(Of String) From {
  56. "subscriptions",
  57. My.Resources.SubscriptionId,
  58. "resourceGroups",
  59. My.Resources.ResourceGroup,
  60. "providers",
  61. "Microsoft.Web",
  62. "certificates",
  63. sCertName
  64. }
  65. sUrl = String.Join("/", oUrl)
  66. sUrl = $"/{sUrl}?api-version=2022-03-01"
  67. oCredential = New ClientSecretCredential(My.Resources.TenantId, My.Resources.ClientId, My.Resources.ClientSecret)
  68. oArmClient = New ArmClient(oCredential)
  69. oSubscription = Await oArmClient.GetDefaultSubscriptionAsync
  70. oResourceGroup = Await oSubscription.GetResourceGroups.GetAsync(My.Resources.ResourceGroup)
  71. oWebSiteResponse = Await oResourceGroup.GetWebSiteAsync(My.Resources.AppServiceName)
  72. oWebSiteResource = oWebSiteResponse.Value
  73. oWebSiteData = oWebSiteResource.Data
  74. aScopeUrl = New String() {$"{My.Resources.ManagementEndpoint}.default"}
  75. oContext = New TokenRequestContext(aScopeUrl)
  76. oToken = Await oCredential.GetTokenAsync(oContext)
  77. oApiCert = New Certificate With {.Location = oWebSiteData.Location.DisplayName}
  78. oApiCert.Properties.CanonicalName = sCanonicalName
  79. oApiCert.Properties.ServerFarmId = oWebSiteData.AppServicePlanId.ToString
  80. oApiCert.Properties.Password = My.Resources.PfxPassword
  81. oApiCert.Properties.PfxBlob = oX509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
  82. oApiCert.Properties.HostNames.AddRange(oX509Cert.SanHostNames)
  83. sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
  84. oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
  85. oClient = New HttpClient
  86. oClient.DefaultRequestHeaders.Add("Authorization", "Bearer " & oToken.Token)
  87. oClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
  88. oResponse = Await oClient.PutAsync(sUrl, oContent)
  89. oResponse.EnsureSuccessStatusCode()
  90. oSslStates = oWebSiteData.HostNameSslStates
  91. oHostNames = oHostNames.Intersect(oWebSiteData.HostNames).ToList
  92. oHostNames.ForEach(Sub(HostName)
  93. oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name = HostName)
  94. If oSslState Is Nothing Then
  95. oSslState = New HostNameSslState With {.Name = HostName}
  96. oSslStates.Add(oSslState)
  97. End If
  98. oSslState.Thumbprint = BinaryData.FromObjectAsJson(oX509Cert.Thumbprint)
  99. oSslState.SslState = HostNameBindingSslState.SniEnabled
  100. oSslState.ToUpdate = True
  101. End Sub)
  102. oSitePatch = New SitePatchInfo
  103. For Each oState In oSslStates
  104. If oState.ToUpdate Then
  105. oSitePatch.HostNameSslStates.Add(oState)
  106. End If
  107. Next
  108. oWebSiteResource.Update(oSitePatch) ' <-- 404 here '
  109. End Function
  110. End Module
uelo1irk

uelo1irk1#

天啊......那真是一次冒险!
好吧,在经历了很多痛苦之后,我终于找到了答案。(旁注:很高兴知道与Create/Update Certificate REST API等效的SDK,所以我还在寻找...)
技巧(变通方案?)是先将证书上传到密钥库,然后从那里将其部署到我们的应用程序服务。
一旦cert在保险库中,我们接下来要做的就是在PUT调用的JSON主体(不区分大小写)中提供四个值:

  1. .Location * 例如美国东部 *
  2. .Properties.KeyVaultSecretName * 保管库中证书的名称 *
  3. .Properties.ServerFarmId * 即AppService.AppServicePlanId.ToString*
  4. .Properties.KeyVaultId * 即KeyVault.Value.Id*
    就是这样没有字节数组,没有密码,仅此而已。
    除了一件事。。我们必须在Vault上授予适当的访问策略:
  • 我们的服务宗旨= CERTIFICATE IMPORT
  • Microsoft Azure应用服务主体= SECRET GET

第一个用于将证书上传到Vault,第二个用于部署。
Microsoft Azure App Service主体的资源标识符为abfa0a7c-a6b6-4736-8310-5855508787cd,但Azure Gov云环境除外,在Azure Gov云环境中,资源标识符为6a02c803-dafd-4136-b4c3-5a6f318b4714
就像这样:

像这样上传到vault:

  1. Dim oCertClient As CertificateClient
  2. Dim oVaultUri As Uri
  3. Dim oOptions As ImportCertificateOptions
  4. Dim aPfxCert As Byte()
  5. oVaultUri = New Uri($"https://{My.Resources.VaultName}.vault.azure.net")
  6. oCertClient = New CertificateClient(oVaultUri, Credential)
  7. aPfxCert = X509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
  8. oOptions = New ImportCertificateOptions(CertName, aPfxCert) With {.Password = My.Resources.PfxPassword}
  9. Await oCertClient.ImportCertificateAsync(oOptions)

现在我们可以将其部署到我们的App Service:

  1. oApiCert = New Upload.Certificate With {.Location = WebSiteResource.Data.Location.DisplayName}
  2. oApiCert.Properties.KeyVaultSecretName = CertName
  3. oApiCert.Properties.ServerFarmId = WebSiteResource.Data.AppServicePlanId.ToString
  4. oApiCert.Properties.KeyVaultId = oKeyVaultResponse.Value.Id.ToString
  5. aScopeUrls = New String() {$"{My.Resources.ManagementEndpoint}.default"}
  6. oContext = New TokenRequestContext(aScopeUrls)
  7. oToken = Await Credential.GetTokenAsync(oContext)
  8. oHttpClient = New HttpClient
  9. oHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {oToken.Token}")
  10. oHttpClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
  11. ...
  12. sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
  13. oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
  14. oHttpResponse = Await oHttpClient.PutAsync(sUrl, oContent)
  15. oHttpResponse.EnsureSuccessStatusCode()

当我偶然发现这个seven-year-old blog post时,我发现了这一切(为多年来保留有价值的内容而欢呼三声)。另外,这里有一个QuickStart Template的概念。
关于这篇文章有一点值得注意:它声称,一个不受保护的PFX是必要的,这工作。然而,在我的测试中,我遇到了相反的情况:至少对我来说,它与密码保护的PFX一起工作得很好。我不打算花时间去找出这种差异我就跟着它走保护好我的PFXs那就这样吧YMMV
如果它可能会帮助别人,这里是完整的工作代码:

  1. Imports System
  2. Imports System.Collections.Generic
  3. Imports System.Linq
  4. Imports System.Net.Http
  5. Imports System.Security.Cryptography.X509Certificates
  6. Imports System.Text
  7. Imports System.Threading.Tasks
  8. Imports Azure
  9. Imports Azure.Core
  10. Imports Azure.Identity
  11. Imports Azure.ResourceManager
  12. Imports Azure.ResourceManager.AppService
  13. Imports Azure.ResourceManager.AppService.Models
  14. Imports Azure.ResourceManager.KeyVault
  15. Imports Azure.ResourceManager.Resources
  16. Imports Azure.Security.KeyVault.Certificates
  17. Imports Matrix.Common
  18. Imports Newtonsoft.Json
  19. Imports Nito.AsyncEx
  20. Friend Module Program
  21. Friend Sub Main()
  22. AsyncContext.Run(Async Function()
  23. Await InstallNewCert()
  24. End Function)
  25. End Sub
  26. Private Async Function InstallNewCert() As Task
  27. Dim oWebSiteResponse As Response(Of WebSiteResource)
  28. Dim oResourceGroup As ResourceGroupResource
  29. Dim oSubscription As SubscriptionResource
  30. Dim oCredential As ClientSecretCredential
  31. Dim oArmClient As ArmClient
  32. Dim oX509Cert As X509Certificate2
  33. Dim sCertName As String
  34. oCredential = New ClientSecretCredential(My.Resources.TenantId, My.Resources.ClientId, My.Resources.ClientSecret)
  35. oX509Cert = Utils.GetCertificate(My.Resources.CertFriendlyName)
  36. sCertName = oX509Cert.SanHostNames.Where(Function(HostName)
  37. Return _
  38. Not HostName.Contains("*"c) AndAlso
  39. HostName.Occurs("."c) = 1
  40. End Function).First
  41. sCertName = sCertName.Split("."c).First
  42. sCertName = $"{sCertName}-{Guid.NewGuid}"
  43. oArmClient = New ArmClient(oCredential)
  44. oSubscription = Await oArmClient.GetDefaultSubscriptionAsync
  45. oResourceGroup = Await oSubscription.GetResourceGroups.GetAsync(My.Resources.SecondaryResourceGroup)
  46. oWebSiteResponse = Await oResourceGroup.GetWebSiteAsync(My.Resources.AppServiceName)
  47. Await UploadCertToVault(oCredential, oX509Cert, sCertName)
  48. Await DeployCertFromVault(oSubscription, oCredential, oWebSiteResponse.Value, sCertName)
  49. Await UpdateBindings(oWebSiteResponse.Value, oX509Cert)
  50. End Function
  51. Private Async Function UploadCertToVault(
  52. Credential As ClientSecretCredential,
  53. X509Cert As X509Certificate2,
  54. CertName As String) As Task
  55. Dim oCertClient As CertificateClient
  56. Dim oVaultUri As Uri
  57. Dim oOptions As ImportCertificateOptions
  58. Dim aPfxCert As Byte()
  59. oVaultUri = New Uri($"https://{My.Resources.VaultName}.vault.azure.net")
  60. oCertClient = New CertificateClient(oVaultUri, Credential)
  61. aPfxCert = X509Cert.Export(X509ContentType.Pfx, My.Resources.PfxPassword)
  62. oOptions = New ImportCertificateOptions(CertName, aPfxCert) With {.Password = My.Resources.PfxPassword}
  63. Await oCertClient.ImportCertificateAsync(oOptions)
  64. End Function
  65. Private Async Function DeployCertFromVault(
  66. Subscription As SubscriptionResource,
  67. Credential As ClientSecretCredential,
  68. WebSiteResource As WebSiteResource,
  69. CertName As String) As Task
  70. Dim oKeyVaultResponse As Response(Of KeyVaultResource)
  71. Dim oResourceGroup As ResourceGroupResource
  72. Dim oHttpResponse As HttpResponseMessage
  73. Dim sJsonContent As String
  74. Dim oHttpClient As HttpClient
  75. Dim aScopeUrls As String()
  76. Dim oContext As TokenRequestContext
  77. Dim oApiCert As Upload.Certificate
  78. Dim oContent As StringContent
  79. Dim oToken As AccessToken
  80. Dim oUrl As List(Of String)
  81. Dim sUrl As String
  82. oResourceGroup = Await Subscription.GetResourceGroups.GetAsync(My.Resources.PrimaryResourceGroup)
  83. oKeyVaultResponse = Await oResourceGroup.GetKeyVaultAsync(My.Resources.VaultName)
  84. oApiCert = New Upload.Certificate With {.Location = WebSiteResource.Data.Location.DisplayName}
  85. oApiCert.Properties.KeyVaultSecretName = CertName
  86. oApiCert.Properties.ServerFarmId = WebSiteResource.Data.AppServicePlanId.ToString
  87. oApiCert.Properties.KeyVaultId = oKeyVaultResponse.Value.Id.ToString
  88. aScopeUrls = New String() {$"{My.Resources.ManagementEndpoint}.default"}
  89. oContext = New TokenRequestContext(aScopeUrls)
  90. oToken = Await Credential.GetTokenAsync(oContext)
  91. oHttpClient = New HttpClient
  92. oHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {oToken.Token}")
  93. oHttpClient.BaseAddress = New Uri(My.Resources.ManagementEndpoint)
  94. oUrl = New List(Of String) From {
  95. "subscriptions",
  96. My.Resources.SubscriptionId,
  97. "resourceGroups",
  98. My.Resources.PrimaryResourceGroup,
  99. "providers",
  100. "Microsoft.Web",
  101. "certificates",
  102. CertName
  103. }
  104. sUrl = String.Join("/", oUrl)
  105. sUrl = $"/{sUrl}?api-version=2022-03-01"
  106. sJsonContent = JsonConvert.SerializeObject(oApiCert, JsonHelper.DefaultSerializationSettings)
  107. oContent = New StringContent(sJsonContent, Encoding.UTF8, "application/json")
  108. oHttpResponse = Await oHttpClient.PutAsync(sUrl, oContent)
  109. oHttpResponse.EnsureSuccessStatusCode()
  110. End Function
  111. Private Async Function UpdateBindings(
  112. WebSiteResource As WebSiteResource,
  113. X509Cert As X509Certificate2) As Task
  114. Dim oSslStates As IList(Of HostNameSslState)
  115. Dim oSitePatch As SitePatchInfo
  116. Dim oSslState As HostNameSslState
  117. oSslStates = WebSiteResource.Data.HostNameSslStates
  118. oSitePatch = New SitePatchInfo
  119. With X509Cert.SanHostNames.Intersect(WebSiteResource.Data.HostNames).ToList
  120. .ForEach(Sub(HostName)
  121. oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name = HostName)
  122. If oSslState Is Nothing Then
  123. oSslState = New HostNameSslState With {.Name = HostName}
  124. oSslStates.Add(oSslState)
  125. End If
  126. oSslState.Thumbprint = BinaryData.FromObjectAsJson(X509Cert.Thumbprint)
  127. oSslState.SslState = HostNameBindingSslState.SniEnabled
  128. oSslState.ToUpdate = True
  129. End Sub)
  130. End With
  131. For Each oState In oSslStates
  132. If oState.ToUpdate Then
  133. oSitePatch.HostNameSslStates.Add(oState)
  134. End If
  135. Next
  136. Await WebSiteResource.UpdateAsync(oSitePatch)
  137. End Function
  138. End Module
展开查看全部

相关问题