In my course “Deploying ASP.NET Core Microservices Using Kubernetes and AKS” that you can find @pluralsight, I discuss the use of SLL inside and outside the kubernetes cluster.
In this course I made the choice to have no HTTPS between the services inside my cluster, but only to have SSL on the web frontend. But in my demo’s I did not address how I set up SSL on the website that I deploy to the cluster. In this article I want to give you some pointers how to make this work.
What do I need to get SSL on the website?
First of all you need a certificate file that can be used. Since I host the web endpoint direct as a Kubernetes service, without the use of any ingress controller, the only way to make this work is either by putting something in front of the website that will handle SSL (e.g. by using a Web Application firewall), or by handling SSL in the pod itself. You can also delegate this work to ingress in the cluster, but I did not use that in this scenario. If you want to use ingress and set up SSL that way, then you can find other articles like here, that you can follow. In this article I will go through the steps to handle the SSL communication direct with the kestrel server that runs inside the pod. Setting up kestrel to use the production certificate. Because we set up SSL without the use of the ingress controller this implies that the request will be handled by the actual pods that host the website. For this we need to make some tweaks to the ASP.NET core website startup, so it accepts the certificate file I have for the domain globoticket.com.
Get a certificate file that can be used to configure SSL for production on ASP.NET Core
ASP.NET core runs on the Kestrel server when you build the website in a docker container. You can configure the kestrel server to use a certificate to host the website on the domain name you have for your production environment. For this I used the capabilities you can find in azure to create a so called App Service Certificate. This is normally used to create a certificate that you can bind to an APP Service. In my case I just wanted to get the certificate and export it so I can then deploy it to the Kubernetes cluster as a secret and then use it in the container to configure the server.
When you create the app certificate and you imported it into a keyvault, you can export it as an PFX file using a few lines of PowerShell. there is an article you can find here that contains that PowerShell script that you can use.
Now we have an PFX file, and we can use this to configure the server. For this we can use two options. One is to make a change to the codebase and configure kestrel to use the pfx file with a password that you provide. My first idea wat to use this and then provide the PFX file as a file mounted to the pod, but this was more challenging then I thought. There is no simple way to just upload the pfx file to the cluster as a secret and then mount it to the pod. This is because you cannot set a secret to just contain a binary file. Instead I was required convert the pfx file to a base64 encoded string, that I can upload as a secret to the cluster.
To create a base64 encoded string from the pfx file you can use the following lines of PowerShell:
1 2 3 4 |
$Content = Get-Content -Path globoticket.pfx -Encoding Byte $Base64 = [System.Convert]::ToBase64String($Content) $Base64 | Out-File globotiket.pfx.txt |
Because I now have a pfx file that is not understood by default by ASP.NET core I also needed to change a bit of startup code. At startup I save the pfx file to local storage and then configure Kestrel using environment variables to pick up the certificate file and the password.
For this I leverage the environment variables ASPNETCORE_Kestrel__Certificates__Default__Password
and ASPNETCORE_Kestrel__Certificates__Default__Path
When these are configured then the server will pick up the pfx file and configure SSL automatically, without any additional code changes
The way I solved this is by creating a new environment variable that I called base64pfxfile
and I provided it the value that I first pushed to the cluster as a secret. This can be done with the following configuration:
1 2 3 4 5 6 |
- name: base64pfxfile valueFrom: secretKeyRef: name: base64pfxfile key: file |
This keeps the config file clean of any secrets and it passes the base 64 encoded string to the container as an environment variable.
I had to change the startup code of the ASP.NET website to decode the string, save it to a local file and then configure the environment variable ASPNETCORE_Kestrel__Certificates__Default__Path
to point to the file.
The Password is passed in the configuration in the same way. I created a secret and pass that secret as a value to the environment variable ASPNETCORE_Kestrel__Certificates__Default__Password
1 2 3 4 5 6 |
- name: ASPNETCORE_Kestrel__Certificates__Default__Password valueFrom: secretKeyRef: name: pfx-password key: pwd |
The startup code was changed as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class Program { public static void Main(string[] args) { WriteBase64PfxFileFromEnvironmentToDisk(); CreateHostBuilder(args).Build().Run(); } private static void WriteBase64PfxFileFromEnvironmentToDisk() { var base64pfxfile = Environment.GetEnvironmentVariable("base64pfxfile"); if (!string.IsNullOrWhiteSpace(base64pfxfile)) { var filename = Path.Combine(Directory.GetCurrentDirectory(), "certfile.pfx"); File.WriteAllBytes(filename, Convert.FromBase64String(base64pfxfile)); Environment.SetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Path", filename); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } |
The way to create the secrets is as follows:
1 2 3 |
kubectl create secret generic pfx-password --from-literal=pwd="yourpasswordgoeshere" kubectl create secret generic base64pfxfile --from-literal=file=”base64encodedstring” |
Now we have all the ingredients to make it work.
Next you can push the changes to the azure DevOps or GitHub repo and then let the GitHub Action or Azure DevOps pipeline run the build and deployment to the cluster.
The final step was to configure the domain name globoticket.com point to the IP address that is used by the Service endpoint and browse to the website.
Hope this helps setting up SSL on your externally exposed website endpoint. I made the choice to do it direct in the pod, of course there are also other ways. You can also set up the ingress controller to take care of the SSL termination and then pass through the request to the web pod.
Leave a Reply