certificateazure-service-fabrickestrel-http-serverservice-fabric-stateless

Service Fabric app Kestrel cert rotation on new cert


I am having a Service Fabric stateless app.

Using the following code for ServiceInstanceListener:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
        {
            return new ServiceInstanceListener[]
            {
                new ServiceInstanceListener(serviceContext =>
                    new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                    {
                        ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");
                        
                        return new WebHostBuilder()
                                    .UseKestrel(opt =>
                                    {
                                        int port = serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint").Port;
                                        opt.Listen(IPAddress.IPv6Any, port, listenOptions =>
                                        {
                                            X509Certificate2 cert = GetCertificateFromStore();
                                            listenOptions.UseHttps(cert);
                                            listenOptions.NoDelay = true;
                                        });

Function GetCertificateFromStore:

        private static X509Certificate2 GetCertificateFromStore()
        {
               string subjectCommonName = Environment.GetEnvironmentVariable("certsubject");
                X509Certificate2 certForApp = null;
                using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
                {
                    store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
                    var certCollection = store.Certificates;

                    // https://learn.microsoft.com/en-us/azure/service-fabric/cluster-security-certificate-management
                    foreach (var enumeratedCert in certCollection)
                    {
                        if (StringComparer.OrdinalIgnoreCase.Equals(subjectCommonName, enumeratedCert.GetNameInfo(X509NameType.SimpleName, forIssuer: false))
                          && DateTime.Now < enumeratedCert.NotAfter
                          && DateTime.Now >= enumeratedCert.NotBefore)
                        {
                            if (certForApp == null)
                            {
                                certForApp = enumeratedCert;
                            }
                            else
                            {
                                // the Service Fabric runtime will find and select the most recently issued matching certificate (largest value of the 'NotBefore' property). Note this is a change from previous versions of the Service Fabric runtime.
                                if (enumeratedCert.NotBefore > certForApp.NotBefore)
                                {
                                    certForApp = enumeratedCert;
                                }
                            }
                        }
                    }

                    if (certForApp == null)
                    {
                        throw new Exception($"Could not find a match for a certificate with subject 'CN={subjectCommonName}'.");
                    }

                    return certForApp;

}

In case a new version of cert is installed on the VMSS(say after cert rotation), how should the stateless app pick up the new cert without service restart.
In case the service is restarted, the latest cert is picked up.


Solution

  • You can modify your code like this:

    new WebHostBuilder()
       .UseKestrel(options =>
    {
       options.Listen(IPAddress.Any, 80);
       options.Listen(IPAddress.Any, 443, listenOptions =>
       {
          listenOptions.UseHttps(httpsOptions =>
          {
             httpsOptions.ServerCertificateSelector = (context, dnsName) =>
             {
                //return cached certificate, invalidate if needed (e.g. check periodically)
                if(_cachedCertificate is null)
                {
                  _cachedCertificate = GetCertificateFromStore();
                  return _cachedCertificate;
                }
             }
         }
    }
    

    More info here