InteXX
InteXX

Reputation: 6377

How to bind a Managed Certificate using the Azure SDK?


Note: the answer may be in either VB.NET or C#. I have no preference for this Q&A.


How do we go about binding a Managed Certificate to a Custom Domain using the new SDK?

Unfortunately, the samples don't cover this task. (In fact, they don't compile, with hundreds of broken references, so it's impossible to tell whether they're even accurate.)

And the documentation isn't very helpful, either. There's this guidance, and this, but those appear to be for purchased certificates. Nothing in that namespace mentions a Managed Certificate.

Here's the problem.

I've been able to successfully add a Custom Domain to my Azure App Service, using this code:

Dim tenantId As String = "89C4A752-7028-4F94-BF6D-A5B0AB83A30A"
Dim clientId As String = "AC4E5551-B056-4769-84AD-F7016E289122"
Dim clientSecret As String = "EJY5du3PVx#o2P3b*B^25t@LoVu8LX2Lgo"
Dim resourceGroupName As String = "group"
Dim webAppName As String = "site"
Dim customDomain As String = "example.com"

' Authenticate and get the client
Dim credential = New ClientSecretCredential(tenantId, clientId, clientSecret)
Dim armClient = New ArmClient(credential)

' Get the web app
Dim subscription = armClient.GetDefaultSubscriptionAsync.Result
Dim resourceGroup = subscription.GetResourceGroups.Get(resourceGroupName)
Dim webApp = resourceGroup.Value.GetWebSites.Get(webAppName)

' Set the domain properties
Dim domainProperties = New HostNameBindingData With {
  .CustomHostNameDnsRecordType = CustomHostNameDnsRecordType.A,
  .HostNameType = AppServiceHostNameType.Managed
}

Me.UpdateDns(webApp)

Dim op = webApp.Value.GetSiteHostNameBindings.CreateOrUpdate(Azure.WaitUntil.Completed, customDomain, domainProperties)

That works. The domain is added. But it's not bound to anything.

Adding a binding to a Managed Certificate is another matter entirely. I tried setting the .SslState property, like so:

Dim domainProperties = New HostNameBindingData With {
  .CustomHostNameDnsRecordType = CustomHostNameDnsRecordType.CName,
  .HostNameType = AppServiceHostNameType.Managed,
  .SslState = HostNameBindingSslState.SniEnabled
}

...but that results in an error:

Parameter Thumbprint is null or empty.

There is a .ThumbprintString property on the HostNameBindingData class, but where do we get that value from?

The repo referenced in this answer almost gets there, but it's nine years old and we're on a completely revamped SDK by now. Besides, he's uploading a .PFX, which is something completely different.

How do I create a new Managed Certificate and bind it to my newly added Custom Domain?

--EDIT--

In fact, they don't compile, with hundreds of broken references, so it's impossible to tell whether they're even accurate.

I got the source to build; it was a lot easier than I'd expected. All it needed was installation of the specific .NET SDK version indicated in the Global.json file in the repo root.

Oh... and an appropriate Package Source Mapping entry, assuming that's in use.

Upvotes: 0

Views: 84

Answers (1)

InteXX
InteXX

Reputation: 6377

I finally managed to get this worked out, thanks to a very helpful soul over at the SDK repo.

After some refinement, here's the code I ended up with (below). Note that the DNS functions are performed using the DnsClient and CloudflareClient packages.

And without further ado, here's the code:

Private Async Function UpdateCreateSecureBind() As Task
  Dim oWebSiteResponse As Response(Of WebSiteResource)
  Dim sHostName As String

  sHostName = "example.com"

  With Await Client.GetDefaultSubscriptionAsync
    With Await .GetResourceGroups.GetAsync(RESOURCE_GROUP_NAME)
      oWebSiteResponse = .Value.GetWebSites.Get(APP_SERVICE_NAME)
    End With
  End With

  Await UpdateDnsAsync(sHostName, oWebSiteResponse.Value.Data.CustomDomainVerificationId)
  Await CreateDomainAsync(sHostName)
  Await SecureDomainAsync(sHostName)
  Await BindCertificateAsync(sHostName)
End Function

Private Async Function UpdateDnsAsync(HostName As String, VerificationId As String) As Task
  Dim oCloudflare As Intexx.Cloudflare.ICloudflare
  Dim oDnsRecord As Result(Of DnsRecord)
  Dim oDnsClient As IDnsClient
  Dim oFqdn As Result(Of String)

  oDnsClient = New Dns.DnsClient
  oCloudflare = New Intexx.Cloudflare.Cloudflare(HostName, My.Resources.Cloudflare.Token)
  oFqdn = Await oDnsClient.ResolveFqdnAsync(APP_SERVICE_DOMAIN)

  oDnsRecord = Await oCloudflare.AddOrUpdateRecordAsync("@", DnsRecordType.A, oFqdn.Value)

  Do While Not (Await oDnsClient.ValidateRecordAsync(oDnsRecord.Value.Name, oDnsRecord.Value.Content, QueryType.A)).Value
    Await Task.Delay(1000)
  Loop

  oDnsRecord = Await oCloudflare.AddOrUpdateRecordAsync($"asuid.{HostName}", DnsRecordType.Txt, VerificationId)

  Do While Not (Await oDnsClient.ValidateRecordAsync(oDnsRecord.Value.Name, oDnsRecord.Value.Content, QueryType.TXT)).Value
    Await Task.Delay(1000)
  Loop
End Function

Private Async Function CreateDomainAsync(HostName As String) As Task
  Dim oBindingData As HostNameBindingData
  Dim oOperation As ArmOperation(Of SiteHostNameBindingResource)
  Dim oBindings As SiteHostNameBindingCollection

  With Await Client.GetDefaultSubscriptionAsync
    With Await .GetResourceGroups.GetAsync(RESOURCE_GROUP_NAME)
      With Await .Value.GetWebSites.GetAsync(APP_SERVICE_NAME)
        oBindings = .Value.GetSiteHostNameBindings
      End With
    End With
  End With

  oBindingData = New HostNameBindingData With {
    .CustomHostNameDnsRecordType = CustomHostNameDnsRecordType.A,
    .HostNameType = AppServiceHostNameType.Verified
  }

  oOperation = Await oBindings.CreateOrUpdateAsync(WaitUntil.Completed, HostName, oBindingData)
End Function

Private Async Function SecureDomainAsync(HostName As String) As Task
  Dim oWebSiteResponse As Response(Of WebSiteResource)
  Dim oCertificateData As AppCertificateData
  Dim oCertificates As AppCertificateCollection
  Dim oOperation As ArmOperation(Of AppCertificateResource)
  Dim oLocation As AzureLocation

  With Await Client.GetDefaultSubscriptionAsync
    With Await .GetResourceGroups.GetAsync(RESOURCE_GROUP_NAME)
      oWebSiteResponse = .Value.GetWebSites.Get(APP_SERVICE_NAME)
      oCertificates = .Value.GetAppCertificates
    End With
  End With

  oLocation = New AzureLocation("East US")
  oCertificateData = New AppCertificateData(oLocation) With {
    .CanonicalName = HostName,
    .ServerFarmId = oWebSiteResponse.Value.Data.AppServicePlanId
  }
  oCertificateData.HostNames.Add(HostName)

  oOperation = Await oCertificates.CreateOrUpdateAsync(WaitUntil.Completed, HostName, oCertificateData)
End Function

Private Async Function BindCertificateAsync(HostName As String) As Task
  Dim oWebSiteResponse As Response(Of WebSiteResource)
  Dim oAppCertificate As AppCertificateResource
  Dim oSslStates As IList(Of HostNameSslState)
  Dim oSslState As HostNameSslState
  Dim oPatch As SitePatchInfo

  With Await Client.GetDefaultSubscriptionAsync
    With Await .GetResourceGroups.GetAsync(RESOURCE_GROUP_NAME)
      oWebSiteResponse = Await .Value.GetWebSites.GetAsync(APP_SERVICE_NAME)
      oAppCertificate = Await .Value.GetAppCertificates.GetAsync(HostName)
    End With
  End With

  oSslStates = oWebSiteResponse.Value.Data.HostNameSslStates

  oSslState = oSslStates.FirstOrDefault(Function(SslState) SslState.Name.Equals(HostName, StringComparison.OrdinalIgnoreCase))
  oSslState.ThumbprintString = oAppCertificate.Data.ThumbprintString
  oSslState.SslState = HostNameBindingSslState.SniEnabled
  oSslState.ToUpdate = True

  oPatch = New SitePatchInfo
  oPatch.HostNameSslStates.Add(oSslState)

  Await oWebSiteResponse.Value.UpdateAsync(oPatch)
End Function

This works.

Upvotes: 0

Related Questions