In this article we will use Azure Management Fluent SDK to renew ssl certificates on Azure Web Apps and to define new SSL Bindings.

 

Architecture point

On Azure App Service, it’s quite simple to define an SSL certificate and to bind an hostname on it. It can be done directly from the Azure portal, using Azure cli tools or directly with management APIs. Azure Web Apps hosting and scaling characteristics are defined using a service plan. App service is a PaaS service and we can’t access to the underlying machine, so how can we manage Azure App Service certificates ?

An App Service certificate is a specific resource of type (Microsoft.Web/certificates) provided by the resource provider Microsoft.Web. Once an SSL binding is defined between an App Service and a certificate, a strong link is defined between the Microsoft.Web/sites and Microsoft.Web/certificates resources. That’s why the certificate can’t be deleted if it has any SSL binding  defined.

In the following example, I have a resource group which contains only web resources (app services and service plan), by exploring the resource group definition in resources.azure.com we can see the resources providers used in it:

With the Azure Management Fluent SDK we are going to use Microsoft.Web/sites and Microsoft.Web/certificates types to retrieve App Services and certificates.

 

Usage of Azure Management Fluent SDK

To manage Azure Resources using C# we can use the fluent version of Management API sdk, it’s open source: https://github.com/Azure/azure-libraries-for-net and available from nuget: https://www.nuget.org/packages/Microsoft.Azure.Management.Fluent/

The code is actually running in a console app, it needs 4 types of inputs:

  • Azure Authentication information: Azure AD clientId, clientSecret and TenantId (azure variable)
  • Azure resource group target : Resource group where Azure App Service are located (resourceGroupName variable)
  • Old SSL certificate : The SSL certificate actually used in Azure by App Services, this one will expire soon (oldCert variable)
  • New SSL certificate : The SSL certificate we want to upload on Azure and use it to create SSL binding on existing App services (newCert variable)

 

What about the code ?

Its final goal is to upload the new certificate an Azure and to create SSL bindings between App Service hostnames using a domain “protected” by the new SSL certificate.

The code will first list all App Services and certificates located in the resource group. Then for each web app, it will check if it has a hostname with an SSL binding link to the old certificate, if true the SSL need to be reniew with the new certificate. If the SSL binding need to be reniewed, the new SSL certificate will be uploaded to Azure and the existing SSL binding will be override to use the new certificate.

 

To represent certificate the following class will be used:


public class Certificate
{
   private X509Certificate2 _inner;
   public string Path { get; }
   public string Password { get; }

   public Certificate(string path, string password)
   {
      this.Path = path;
      this.Password = password;
      _inner = new X509Certificate2(path, password);
   }

   public string Thumbprint => this._inner.Thumbprint;
}

 

1. Authentication to Azure subscription

The first step is to get authenticated on Azure, specifying the target subscription. We are going to create an IAzure instance which will allow us to deal with Azure resources:


// Set Up Azure Credentials
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenantId, AzureEnvironment.AzureGlobalCloud);

IAzure azure = Microsoft.Azure.Management.Fluent.Azure
  .Configure()
  .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
  .Authenticate(credentials)
  .WithSubscription(subscriptionId);

 

2. List web apps and certificates

Listing webs apps and certificates located in a specific resource group is quite simple with the management SDK, only two lines of code are needed:


var webApps = await azure.WebApps.ListByResourceGroupAsync(resourceGroupName);
var certificates = await azure.AppServices.AppServiceCertificates.ListByResourceGroupAsync(resourceGroupName);

 

3. Check if the certificate need to be reniewed on Web Apps

On each web app, we need to check if it has an  hostname bind to the old certificate. To get web app hostname we can use the property HostNameSslStates on an IWebApp instance. To check if a binding exist we compare thumbprint values.


 foreach (var webApp in webApps)
 {

       // 1. Check hostnames with an SSL binding on the target domain {targetDomain}
       var sslStates = webApp.HostNameSslStates;
       var domainSslMappings = new List<KeyValuePair<string, HostNameSslState>>(sslStates.Where(_ => _.Key.Contains($".{targetDomain}")));
                
       if (domainSslMappings.Any())
       {
            foreach (var domainMapping in domainSslMappings)
            {
                 // 2. Check if the ssl mapping uses the old certificate
                 bool needToRenew = domainMapping.Value.SslState == SslState.SniEnabled && domainMapping.Value.Thumbprint == oldCert.Thumbprint;

                 // 3. If the old certificate is still used we update the ssl binding with the new one
                 if (needToRenew)
                 {
                     string hostName = domainMapping.Value.Name;

                      // 4. Upload certificate and do the ssl binding
                      await DefineSSLBinding(webApp, newCert, hostName);
                 }
              }
        }
 }

 

4. Upload the certificate and overide the SSL binding

The last step is to upload the certificate an override the existing SSL binding, this step can be done with the following method:

private static async Task DefineSSLBinding(IWebApp webApp, Certificate cert, string hostName)
{
   await webApp.Update()
    .DefineSslBinding()
    .ForHostname(hostName)
    .WithPfxCertificateToUpload(cert.Path, cert.Password)
    .WithSniBasedSsl()
    .Attach()
    .ApplyAsync();
}

If the certificate is already uploaded in Azure, this call won’t generate any errors, it will simply use the certificate to define the SSL binding.

 

Final sample

We can gather part 2 and 3 in a single method, adding some Console.Writeline(“”) to get some information in the console. This method uses the DefineSSLBinding method explained in step 4.


private static async Task SwitchCertificate(IAzure azure, string resourceGroupName, Certificate oldCert, Certificate newCert, string targetDomain)
{
     // List azure resources (web apps, certificates) in the target resource group
     var webApps = await azure.WebApps.ListByResourceGroupAsync(resourceGroupName);
     var certificates = await azure.AppServices.AppServiceCertificates.ListByResourceGroupAsync(resourceGroupName);

     foreach (var webApp in webApps)
     {
          Console.WriteLine(webApp.Name);

          // 1. Check hostnames with an SSL binding on the target domain {targetDomain}
          var sslStates = webApp.HostNameSslStates;
          var domainSslMappings = new List<KeyValuePair<string, HostNameSslState>>(sslStates.Where(_ => _.Key.Contains($".{targetDomain}")));
                
          if (domainSslMappings.Any())
          {
               foreach (var domainMapping in domainSslMappings)
               {
                   // 2. Check if the ssl mapping uses the old certificate
                   bool needToRenew = domainMapping.Value.SslState == SslState.SniEnabled && domainMapping.Value.Thumbprint == oldCert.Thumbprint;

                   // 3. If the old certificate is still used we update the ssl binding with the new one
                   if (needToRenew)
                   {
                       string hostName = domainMapping.Value.Name;
                       Console.WriteLine($"- {hostName}");

                       // 4. Upload certificate and do the ssl binding
                       await DefineSSLBinding(webApp, newCert, hostName);

                      Console.WriteLine("-- New binding done ! ");
                   }
               }
           }
           else
             Console.WriteLine("No mapping");

          Console.WriteLine("");
     }
}

 

Now that we have the logic encapsulated, let’s define the inputs and call the method we just defined :

string rgName = "********-rg";
string targetDomain = "mydomain.com";
string folder = @"C:\*************\Certificates";

Certificate oldCert = new Certificate($"{folder}\\wildcard-2017.pfx", "*****");
Certificate newCert = new Certificate($"{folder}\\wildcard-2018.pfx", "*****");

await SwitchCertificate(azure, rgName, oldCert, newCert, targetDomain);

 

Happy coding 🙂


Manage App Service outbound ip addresses //MS Build 2018 - Serverless announcements : Azure Function & Azure Event Grid

Leave a Reply

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *