Running a local Azure Service Fabric cluster on SSL

Azure Service Fabric is Microsofts micro-services platform. Well, it's actually more than that but that is all well-documented in other places on the interwebs.

It is relatively new and documentation is still a bit behind so I had some trouble in getting the following setup working:

  • I want to run my production cluster on a domain name that is not the default. So instead of mycluster.westeurope.cloudapp.azure.com I want my-api.my-services.nl.
  • The custom API endpoint that is exposed through my cluster should run on https and not the default http.

The documentation, as far as it's available, is rather fragmented and I couldn't find the complete story so I thought I'd write it down for future reference. In my humble opinion, step 1 should always be to get it working on a local dev box so that's what I started out with. Reproducing and fixing an error on your dev-box is a lot easier than fixing the same error in a remote cluster. Besides, it helps in better understanding what is happening.

Here's a short summary of what needs to be done, all the details follow below:

  • Generate a self-signed root certificate and install that in the trusted root certificates store.
  • Generate a server authentication certificate that is derived from the root certificate.
  • Modify your hosts file to match the server certificate common name.
  • Extend the service manifest with an additional named endpoint.
  • Extend the Service Fabric application manifest file with a service EndpointBindingPolicy and EndpointCertificate.
  • Modify the generated OwinCommunicationListener to take the https protocol into account.
  • Add an additional named ServiceInstanceListener (besides the one that is listening on http).

If this all sounds like abracadabra, continue reading :)

Self-signed root certificate

This step isn't absolutely necessary but it makes the entire setup much nicer. We store this certificate with the other trusted root certificates so that certificates that are signed by it are automatically trusted. This prevents browser certificate warnings later. I used the same trick in an earlier post so I'm not going to repeat all the details.

The self-signed root certificate is generated using makecert:

makecert -r -pe -n "CN=SSLTestRoot"  
         -b 06/07/2016 -e 06/07/2018
         -ss root -sr localmachine -len 4096

Server authentication certificate

Next step is to use our root certificate to sign our server authentication certificate. Again we use makecert. This time the certificate is placed in the My store.

makecert -pe -n "CN=sfendpoint.local" -b 06/07/2016 -e 06/07/2018  
         -eku 1.3.6.1.5.5.7.3.1 -is root -ir localmachine -in SSLTestRoot
         -len 4096 -ss My -sr localmachine

Modify hosts file

The hosts file in C:\Windows\System32\drivers\etc is the first place Windows looks when it needs to resolve a host name like www.google.com. In our case, we want to add an entry that matches the common name in our server authentication certificate and sends the user to 127.0.0.1.

127.0.0.1  sfendpoint.local  

Add service endpoint to service manifest

We finished all the necessary preparations on our development machine, next step is Service Fabric configuration. In the service manifest file of the (API) service we wish to expose on https, we need to add an additional endpoint, besides the (http) endpoint that is already there.

<ServiceManifest Name="My.SF.ApiPkg" Version="1.0.0" ...>  
  <ServiceTypes>
    <StatelessServiceType ServiceTypeName="ApiType" />
  </ServiceTypes>

  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <ExeHost>
        <Program>My.SF.Api.exe</Program>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <ConfigPackage Name="Config" Version="1.0.0" />

  <Resources>
    <Endpoints>
      <Endpoint Protocol="http" Name="WebEndpoint" Type="Input" Port="8676" />
      <Endpoint Protocol="https" Name="WebEndpointHttps" Type="Input" Port="8677" />
    </Endpoints>
  </Resources>
</ServiceManifest>  

I use port numbers 8676 and 8677 for http and https respectively but that is up to you.

Important note: if you have multiple endpoints, make sure to give each one a unique name. Service Fabric won't complain but your service will not start. This took me some time to figure out because the error messages do not really point you in the right direction.

Extend the Service Fabric application manifest

Next step is the application manifest file. We need two things here: a reference to the certificate and a link between our micro service, the certificate and the endpoint. Note that we configure the certificate hash value (thumbprint) outside the application manifest file in a separate environment configuration file.

<ApplicationManifest ApplicationTypeName="MySFType"  
                     ApplicationTypeVersion="1.0.0" ...>
  <Parameters>
    <Parameter Name="Api_SslCertHash" DefaultValue="" />
  </Parameters>
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="My.SF.ApiPkg"
                        ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
      <EndpointBindingPolicy EndpointRef="WebEndpointHttps"
                             CertificateRef="my_api_cert" />
    </Policies>
  </ServiceManifestImport>
  <DefaultServices>
    <Service Name="Api">
      <StatelessService ServiceTypeName="ApiType" InstanceCount="-1">
        <SingletonPartition />
      </StatelessService>
    </Service>
  </DefaultServices>
  <Certificates>
    <EndpointCertificate X509FindValue="[Api_SslCertHash]" Name="my_api_cert" />
  </Certificates>
</ApplicationManifest>  

We added an EndpointBindingPolicy that references the https endpoint and the certificate my_api_cert. This tells Service Fabric that for this specific service it should add a certificate to the specified endpoint.

The certificate itself has a name and a thumbprint value that is a reference to a value in an environment-specific configuration file.

Modify OwinCommunicationListener

That was all the necessary Service Fabric configuration. What remains is some code changes. When you add a new stateless api service to your Service Fabric project in Visual Studio, an OwinCommunicationListener class is added. This class is responsible for booting a self-hosted Owin web server on the correct port number.

By default, this class assumes you never want to use https. So what you need to do is replace this line of code (that has a hard-code http reference):

_listeningAddress = string.Format(  
    CultureInfo.InvariantCulture,
    "http://+:{0}/{1}",
    port,
    string.IsNullOrWhiteSpace(_appRoot)
        ? string.Empty
        : _appRoot.TrimEnd('/') + '/');

with this line of code:

_listeningAddress = string.Format(  
  CultureInfo.InvariantCulture,
  "{0}://+:{1}/{2}",
  serviceEndpoint.Protocol,
  port,
  string.IsNullOrWhiteSpace(_appRoot)
      ? string.Empty
      : _appRoot.TrimEnd('/') + '/');

in the OpenAsync method. The serviceEndpoint variable should already be declared somewhere in the first few lines of OpenAsync.

Add a ServiceInstanceListener

Last but not least we must tell our service that it should (also) listen on the https endpoint. This happens in the StatelessService.CreateServiceInstanceListeners method that you override in your service class, which in my case looks like this:

internal sealed class Api : StatelessService  
{
  public Api(StatelessServiceContext context) : base(context) { }

  protected override IEnumerable<ServiceInstanceListener>
      CreateServiceInstanceListeners()
  {
    return new[]
    {
      new ServiceInstanceListener(
        serviceContext => new OwinCommunicationListener(
            Startup.ConfigureApp, serviceContext, ServiceEventSource.Current,
            "WebEndpoint"), "Http"),

      new ServiceInstanceListener(
        serviceContext => new OwinCommunicationListener(
            Startup.ConfigureApp, serviceContext, ServiceEventSource.Current,
            "WebEndpointHttps"), "Https")
    };
  }
}

Note that each listener references the name of the endpoint it should listen on.

Conclusion

That is 'all' there is to it. Well, it's actually quite a lot but the individual steps aren't too complicated. Using the instructions above it should now be rather easy to get this working on your machine too.

Next time: how to set this up for an actual Service Fabric cluster running in Azure.