End To End Encryption Between Nginx Aspnetcore Kestrel Angular Angular CSharp DotNetCore Let's Encrypt NGINX

Jul 20th, 2018 - written by Kimserey with .

Internet is moving toward secure connections whereby HTTPS is a priority. Browsers are now warning users when navigating to non secured website. With this movement, Kestrel and ASPNET Core have adopted the mentality of security by default rather than security when needed. HTTPS will now be the default and HTTP will be a necessity due to implementation constraints. Together with Lets Encrypt and ACME protocol, we do not have excuses for not implementing an SSL connection.

  1. Setup the example
  2. SSL self signed certificate for Nginx
  3. SSL self signed certificate for Kestrel
  4. SSL self signed certificate for Angular CLI

1. Setup the example

We assume that our environment is on Ubuntu, with nginx and dotnet installed. If you are on Windows, you can install the linux subsystem with Ubuntu 16.04 as describe on my previous post. This will give access to most of the features of Ubuntu via a bash prompt.

To start we create a HelloWorld application with dotnet. Here is the Startup.cs which simply serves a Hello world.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Startup
{
    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

When we run this application, we can access http://localhost:5000. Next we configure our localhost proxy with nginx. We create localhost file in /etc/nginx/sites-available/ where we proxy localhost calls to localhost:5000.

server {
    listen 80;
    include /etc/nginx/proxy_params;

    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header Upgrade $http_upgrade;

    location / {
        proxy_pass http://localhost:5000;
    }
}

Next symlink the file and remove the default file. And restart nginx:

1
2
3
sudo ln -s /etc/nginx/sites-available/localhost /etc/nginx/sites-enabled/localhost
sudo rm /etc/nginx/sites-enabled/default
sudo service nginx restart

Now when we navigate to http://localhost, we should have our call proxied to http://localhost:5000. So far we have the following:

Browser --(http)--> nginx --(http)--> kestrel

So far none of our connection is secured.

2. SSL self signed certificate for Nginx

The first communication is over internet therefore in order to keep the confidentiality of the commumication we can configure.

Here we will be creating a self signed certification which means that the certificate is issued and signed by ourselves instead of being issues by a trusted certificate authority. The difference is that our certificate will not be trusted by default in the browser.

If you need a SSL certificate signed by a trusted CA, you can install SSL nginx using Certbot but you would need to have a valid domain.

So we start first by create a X509 certificate which we will use to setup SSL on Nginx. To create it we will use the same method as explain in my previous post on create a self signed certificate with Openssl.

1
sudo openssl req -x509 -newkey rsa:4096 -keyout localhost.key -out localhost.crt -days 3650 -nodes -subj "/CN=localhost" -config config.cnf

config.cnf is the copy of the configuration from OpenSSL which has been modified to add the subject alternate name as discussed in my previous post in section 3). Then we move the created certificate and key to /etc/localhost and use it to configure SSL on Nginx:

server {
    listen 443 ssl;
    ssl_certificate /etc/localhost/localhost.crt;
    ssl_certificate_key /etc/localhost/localhost.key;

    include /etc/nginx/proxy_params;

    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header Upgrade $http_upgrade;

    location / {
        proxy_pass http://localhost:5000;
    }
}

Now when we navigate to https://localhost, our communication is encrypted using the SSL cert we created and then proxied as http://localhost:5000.

So far we have the following:

Browser --(https)--> nginx --(http)--> kestrel

3. SSL self signed certificate for Kestrel

Now that we have secured the connection to nginx, we can secure the connection to Kestrel.

Since nginx and kestrel are both on localhost in our example (as we are developing locally), we can use the same certificate which we created as it is bound to the localhost common name.

We start by combining .crt and .key into a .pfx file to be used by Kestrel. A .pfx file is a file packagine the private and public certificate into a file protected by a password. It adds a protection in the event of the key failling under the wrong hands.

1
2
sudo openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt -name "Localhost Self Signed - Example"
sudo mv localhost.pfx /mnt/c/Projects/HelloWorld/HelloWorld

Next we add the HTTPS configuration on the WebHostBuilder. We copied localhost.pfx to the root of the project therefore we can reference it using the name only but it could be placed anywhere, UseHttps accepts a path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(opts => {
                opts.ListenLocalhost(5001, listenOptions =>
                {
                    listenOptions.UseHttps("localhost.pfx", "mypassword");
                });
            })
            .UseStartup<Startup>();
}

Now when we navigate to https://localhost5001, our connection will be secured when directly interacting with Kestrel. Since Nginx proxies calls to Kestrel, we need to have Nginx verify whether the SSL certificate delivered by Kestrel is valid. We can do that by using the directives proxy_ssl_trusted_certificate and proxy_ssl_verify.

1
2
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.cer; 
proxy_ssl_verify on;

ca-certificates.cer contains all certificate trusted by the system but we haven’t done anything to make Ubuntu trust our certificate therefore when we try to access https://localhost, we get the following error in nginx logs:

[error] 478#478: *28 upstream SSL certificate verify error: (18:self signed certificate) while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", upstream: "https://127.0.0.1:5001/", host: "localhost"

SSL certificate verify error, if we want Nginx to trust our localhost.crt, we will need to configure Ubuntu to recognize it. To do so, we need to use the update-ca-certificates command. If we look at man update-ca-certificates, we can see the following instruction:

Furthermore all certificates with a .crt extension found below /usr/local/share/ca-certificates are also included as implicitly trusted.

So to extend the implicit trusted certificates, we place our certificate under /usr/local/share/ca-certificates/localhost/ then run the command sudo update-ca-certificates.

1
2
3
4
sudo mkdir /usr/local/share/ca-certificates/localhost/
sudo cp /etc/localhost/localhost.crt /usr/local/share/ca-certificates/localhost/
sudo update-ca-certificates
sudo service nginx reload

And we are done, we should now be able to navigate to https://localhost! Now all our communication channels will be encrypted.

1
Browser --(https)--> nginx --(https)--> kestrel

4. SSL self signed certificate for Angular CLI

As a side note, Angular CLI also allows to serve locally with SSL with the following command --ssl:

1
ng serve --ssl --port=4201

It will generate a self signed certificate and serve on https://localhost:4201. Navigate to the address and download the certificate from Chome directly by clicking on Copy File. Once downloaded, install it on the Trusted Root CA and restart all browsers. The certificate will now be trusted and during development we will be serving on https.

Conclusion

Today we saw how to encrypt our communication between client and Nginx and between Nginx to Kestrel. We saw how to create a self signed certificate using openssl. We started by looking at how to enable SSL for Nginx by configuring the site file and using the Nginx proxy module. Next we looked into Kestrel and how we could use the same SSL certificate to encrypt communication between Nginx and Kestrel for all proxied requests. Hope you liked this post, see you next time!

Designed, built and maintained by Kimserey Lam.