Loupe

Renouveler les certificats SSL des App Services Azure en C#

Dans cet article, nous allons découvrir comment utiliser les SDK de management Azure (version Fluent) pour renouveler ses certificats SSL Azure App Service.

Architecture des applications web Azure (App Service)

Uploader un certificat SSL sur une application web Azure et définir une liaison entre ce certificat et un nom de domaine est une opération classique. Cela peut être effectué directement depuis le portail Azure ou avec les outils en lignes de commandes (Azure CLI, Azure PowerShell).

Les caractéristiques d’hébergement et de mise à l’échelle d’une application web Azure sont définis par l’intermédiaire d’un plan de service. Ce plan de service nous offre un ensemble de fonctionnalités et d’options, cependant App Service étant un service PaaS, nous ne pouvons pas accéder directement à la machine. Comment pouvons-nous donc gérer nos certificats (SSL ou non d’ailleurs !) ?

Un certificat App Service est un type de ressource spécifique (Microsoft.Web/certificates) délivré par le fournisseur de ressources Microsoft.Web. Une liaison entre une application web Azure et un certificat SSL est un lien fort (on parle souvent de « binding SSL »), c’est pour cela qu’un certificat SSL ne peut être supprimé tant qu’il est lié à une ou plusieurs applications web. Pour être exact, le « binding SSL » s’effectue entre le certificat et un nom de domaine (« hostname ») définit sur l’application web.

Dans l’exemple suivant, je possède un groupe de ressources qui contient uniquement des ressources web (une application web avec un certificat SSL liée et un plan de service). En explorant la définition des ressources avec l’explorateur resources.azure.com, on distingue facilement les fournisseurs de ressources utilisées et les types de ressources associées :

azureresources.PNG

Avec le SDK de management Azure, nous allons utiliser les types de ressources Microsoft.Web/sites et Microsoft.Web/certificates pour récupérer les applications web et les certificats.

Utilisation du SDK de management Azure Fluent

Pour administrer ses ressources Azure en C#, il existe une librairie Fluent open source développée par Microsoft (https://github.com/Azure/azure-libraries-for-net). Elle est disponible via nuget: https://www.nuget.org/packages/Microsoft.Azure.Management.Fluent.

L’exemple de codes sera exécuté dans une application console et requiert plusieurs paramètres pour fonctionner :

  • Informations d’authentification à Azure : Azure AD clientId, clientSecret et tenantId (variable azure)
  • Groupe de ressources cible : groupe de ressources dans lequel les applications web Azure concernées par le renouvellement du / des certificat(s) sont placés (variable resourceGroupName)
  • Certificat SSL à renouveler : le certificat SSL actuellement utilisé par les applications Azure (variable oldCert)
  • Nouveau certificat SSL : le certificat SSL que nous voulons utiliser à la place de l’ancien (variable newCert)

 

Mais que fait le code ?

L’objectif final du code est de remplacer un ancien certificat par un nouveau et définir un lien / « binding SSL » entre un nom de domaine et le certificat.

Le code liste dans un premier temps l’ensemble des applications web et des certificats présents dans le groupe de ressource cible. Ensuite, pour chaque application web, il vérifie si l’application web possède un nom de domaine utilisant l’ancien certificat SSL. Si oui, l’ancien certificat doit être remplacé par le nouveau certificat et un nouveau binding SSL doit être effectué.

Pour représenter un certificat, la classe C# suivante sera utilisée :

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. Authentification à l’abonnement Azure

La première étape est de s’authentifier à l’abonnement Azure cible. Pour cela, nous allons créer une instance IAzure. Cet objet nous permettra d’effectuer des actions sur les ressources Azure de l’abonnement.

// 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. Lister les applications web et les certificats

Pour lister les applications web et les certificats présents dans un groupe de ressources spécifiques, 2 lignes de codes suffisent :

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

3. Vérifier si le certificat a besoin d’être renouvelé

Sur chaque application web, nous avons besoin de vérifier si un ou plusieurs noms de domaine utilisent le certificat à renouveler. Pour récupérer les noms de domaines d’une application web, on peut utiliser la propriété HostNameSslStates sur une instance de IWebApp. Pour vérifier si une liaison existe, on recherche une liaison avec le Thumbprint de l’ancien certificat.

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 du nouveau certificat et renouvellement du binding SSL

La dernière étape consiste à uploader le nouveau certificat et à mettre jour le binding SSL. La méthode suivante effectue ce travail :

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();
}

Si le certificat spécifié par le paramètre cert est déjà uploadé sur Azure, aucune erreur ne sera levée, la méthode « Update » utilisera simplement le certificat existant.

 

Code complet

En rassemblant les étapes 2 et 3, nous pouvons créer une méthode prête à l’emploi qui met à jour un certificat utilisé par un nom de domaine spécifique :

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("");
     }
}

Une fois la logique encapsulée dans une méthode, il suffit d’appeler cette dernière avec les paramètres précisés en début d’article :

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 :) 

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus