Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Secure Containerized Microservice With .NET Core

DZone's Guide to

Building a Secure Containerized Microservice With .NET Core

This detailed tutorial will show you how to build and secure a containerized microservice using .NET Core, for use in cross-platform applications.

· Microservices Zone ·
Free Resource

Learn how modern cloud architectures use of microservices has many advantages and enables developers to deliver business software in a CI/CD way.

The microservices craze is in full swing, and for good reason. It's not a silver bullet for every problem, but it's certainly becoming a practical solution for scalability and resilience in enterprise software systems.

The .Net Core project has also made some great progress in this arena, enabling you to make solid, cross-platform applications utilizing pre-written code from the .Net Core Framework. This enables you to develop lean microservices on a Windows, OSX or Linux workstation, and deploy them to either a Windows, OSX or Linux server. The ability to generate Linux binaries means you can take advantage of containerization on this platform. 

Today I'm going to show how easy it is to build a REST Microservice in .Net Core 2 (Web API) and deploy it in a container to a Debian server. There's ample documentation covering parts of this process, but this is a comprehensive tutorial showing the process from start to finish.

Create the .Net Core Project

We will use the Dotnet CLI to create our application. For this, you will need:

  • A machine with the .NET Core SDK installed (Can be Windows, Mac or Linux)

  • A text editor (I'm using Visual Studio Code, but it's optional)

  • A way to test the Web API (I'm using POSTMan)

As a note, you can use Visual Studio in Windows to create this project with the built-in wizards and the result will be the same. I prefer doing it manually.

First, you need to create the project, I'm doing this from the command prompt. 

 dotnet new webapi -o friendlyphonenumber 

This will scaffold out a new .Net Core Web API project. You'll have everything you need to be included in the /friendlyphonenumber directory. This works identically on all three OS platforms.

Set Up Serialization

Open up your friendlyphonenumber.csproj file and add the following in the itemgroup containing your package references:

<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />


This will add our Serialization services to the project. Then run

 dotnet restore 

This will enable you to control the serialization, specifically in naming the properties with your chosen format, as opposed to C# naming conventions. 

Create Some Models

This service uses a REST API, and we're going to be sending JSON objects to it. Then it will process the data and return a new object using web requests. Although there are only single properties here we'll create a model for each object sent and returned. 

Save the file. Now we'll create a container for the outgoing phone number that will be formatted in a similar fashion. 

Create another class named FormattedPhoneNumber.cs. Make sure and include System.Runtime Serialization for these objects.

using System.Runtime.Serialization;

namespace friendlyphonenumber.Models {
 [DataContract(Name = "FormattedPhoneNumber")]
 public class FormattedPhoneNumber {
  [DataMember(Name = "PhoneNumber")]
  public string PhoneNumber {
   get;
   set;
  }
 }
}

Finally, let's create the class that's the core of the service, the phone number converter. This takes a Numeric phone number and converts it to our friendly text. 

using System;

namespace friendlyphonenumber.Models {
 public class PhoneNumberFormatter {
  public long NumericPhoneNumber {
   get;
   set;
  }
  public string FormattedPhoneNumber {
   get;
   set;
  }

  public void ConvertPhoneNumber() {
   FormattedPhonenumber = String.Format("{0:(###) ###-####}", NumericPhoneNumber);
  }
 }
}


This is a nice testable object we can extend later if we feel like.

Creating the Controller

Next, we'll create a controller. In the new project, delete ValuesControllers.cs in the controllers folder. That's the sample one added by the .Net CLI and we won't be using it.

Create a new class and call it FormatPhoneNumber.cs.

In this class, we'll create a single method that will take a POST with the numeric phone number in it, and return an object with the formatted object in it.

We'll add usings for Microsoft.AspNetCore.Mvc and our models.

Make the sure the class extends the Controller class for proper functionality.

using Microsoft.AspNetCore.Mvc;
using friendlyphonenumber.Models;

namespace friendlyphonenumber.Controllers {
 [Route("api/[controller]")]
 public class FormatPhoneNumber: Controller {
  PhoneNumberFormatter formatter = new PhoneNumberFormatter();

  public IActionResult Post([FromBody] NumericPhoneNumber phoneNumber) {

   if (phoneNumber != null) {
    formatter.NumericPhoneNumber = phoneNumber.PhoneNumber;
    formatter.ConvertPhoneNumber();
    FormattedPhoneNumber friendlyNumber = new FormattedPhoneNumber() {
     PhoneNumber = formatter.FormattedPhoneNumber
    };
    return Ok(friendlyNumber);
   } else {
    return BadRequest();
   }
  }
 }
}


The controller takes in a Post with an object formatted with a numeric number and returns an IActionResult with an object containing the "friendly" or formatted phone number.

Save these files, and now we're ready to run our application. 

 dotnet run 

You should see something like this:
.Net Core at the OSX Terminal
Our Web API is running on http://localhost:5000. So we'll do a quick smoke test with Postman.

In Postman, we create a simple POST that sends in raw JSON to http://localhost:5000/api/FormatPhoneNumber.

The object we send looks like this:

{
    "PhoneNumber": "5035551212"
}


After the service processes this object, we get a nicely formatted result:

Using POSTMan to create a request

So now we know our service is working as expected. So let's publish a framework dependent build of the application:

 dotnet publish -f netcoreapp2.0 -c Release 

What this does is build an application that will run on any supported target and use the installed .Net Core framework from the machine.

A quick note: you can publish a self-contained deployment with a specified target, in our case it would Debian 9. It would publish everything needed to run, including the framework. It can be run on a machine without the .Net Core Framework installed. However these builds are larger by comparison, and since we're creating a microservice we want to build a smaller, leaner container that we can replicate to scale if needed.

After this build completes, we have our application's artifacts:
Image title
I'll use scp to transfer the files to my Debian machine:

 scp -r * <yourusername>@<your host>:/home/<yourusername>/apps/friendlyphonenumber 


Setting Up the Linux Host

We're now going to deploy the application to a Linux server. For this, you'll need:

  • A Linux Server connected to the internet (I'm using Debian 9)

  • The .NET Core SDK installed

  • Docker Installed

I have copied over my artifacts and the .NET Core SDK is installed, so I should be able to just run the DLL:

 dotnet friendlyphonenumber.dll 

And it appears to be running great.

Image title
But when we test it out, you'll quickly notice something.

Image title
If we check the response from localhost it produces output. It throws an error because we're not sending it JSON, but we can at least see the response is processed. If we try to access it externally:

Image title
You can see it's blocked and doesn't work. This is because our application is only listening on the localhost interface. We have some more steps to do with our application. Ideally, we should be using something like Nginx as a proxy, but that's outside the scope of this article so we'll set up the application to listen on the outside interface directly. Open up Program.cs in your project, and add the following to the create default builder method:

.UseKestrel(options => {
 options.Listen(IPAddress.Any, 5000);
})

Your BuildWebHost method should now look like this:

Kestrel Options .NET Core

Now, rebuild the project and transfer the new artifacts over. When we run the file again:

dotnet friendlyphonenumber.dll

Image title
And we can now access the server externally. So, we just got an email from our project manager, turns out this needs to be secured with SSL. As it turns out this isn't as big of a problem as it sounds.

Add SSL to Our Service

Now we need to generate a certificate to secure to our service, we'll use Let's Encrypt to build a certificate that we can use to secure our connections.

Note: These are distribution specific steps for setting up let's encrypt on Debian 9. You can skip this if you already have let's encrypt or have installed certificates on your server. If you're using a different distribution consult the documentation for setting up Let's encrypt on your server.

Next, we'll install Let's Encrypt, which is now a part of the Debian 9 distribution:

 sudo apt-get install python-certbot-nginx -t stretch 

Now, we'll run the certbot to just set up a certificate only without installing it to a web server:

 sudo certbot certonly 

Since I don't have a webserver installed, it asks how I want to handle the authentication piece, and I'll just select a temporary webserver (standalone):

Image title
After filling out the information, I now have a certificate created here:

 /etc/letsencrypt/live/sandbox.jeremymorgan.com/fullchain.pem 

Your location will vary depending on the URL you're using. Now, we need to convert this to a .pfx to use with Kestrel (the .Net Core web server). So to do that, I'll do the following to generate a .pfx cert in the application's folder:

sudo openssl pkcs12 -export -out friendlyphonenumber.pfx \

-inkey /etc/letsencrypt/live/sandbox.jeremymorgan.com/privkey.pem \

-in /etc/letsencrypt/live/sandbox.jeremymorgan.com/cert.pem \

-certfile /etc/letsencrypt/live/sandbox.jeremymorgan.com/chain.pem


Again, for your server, you'll want to replace "sandbox.jeremymorgan.com" with your domain name for the locations.

This will ask you for an export password, create one and make a note of it because we'll be using it in a minute.

Change the ownership of the .pfx so you can use it:

 sudo chown jeremy friendlyphonenumber.pfx 

Now we've generated a pfx file for this site. Next, we need to modify the application again to listen with an SSL connection.

In program.cs, replace the Kestrel options we added earlier with the following:

options.Listen(IPAddress.Any, 5001, listenOptions => {
 listenOptions.UseHttps("friendlyphonenumber.pfx", "(your password)");
});


 So it should now look like this:

Kestrel with SSL support
And we'll have to add another package reference to our .csproj:

<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.0.1" />


Now rebuild and redeploy the app to the Linux host.

After this is completed we can only access the endpoint securely:

Connect to .Net Core Web API with SSL
Now we're ready to put it into a container.

Building the Docker Container

We have Docker installed and configured on this server, so I want to build a container for this application.

Now I'm going to create a new directory for a docker container

 ~/containers/friendlyphonenumber 

Next, I'll create an artifacts folder in this directory, and copy our binaries and certificate into it.

 mkdir artifacts 


 cp -r ../../apps/friendlyphonenumber artifacts/ 


Now we'll create the docker file in our directory. This is going to be a fairly simple configuration:

FROM microsoft/aspnetcore:2.0
WORKDIR /app
COPY ./artifacts .
EXPOSE 5001
ENTRYPOINT ["dotnet", "friendlyphonenumber.dll"]


This file simply:

  • Starts with the aspnetcore base image

  • Creates a working directory

  • Copies our artifacts into the container

  • Opens the 5001 port

  • Runs the application

Now that we have our Docker file we'll build an image:

 docker build -t friendlyphonenumber1 . 

And it builds our first image. 

ASP .NET Core Web API in a Docker Image
So let's run it:

 docker run -d -p 5001:5001 friendlyphonenumber1:latest 

We're going to run this container detached and map port 5001 to 5001 on the host machine, and we hit it with curl and see it's available from the outside again, but this time running in a Docker container:

Accessing our .Net Core Web API in a container from the outside
Now if we want or need to, we can spin up another identical container to this:

 docker run -d -p 5002:5001 friendlyphonenumber1:latest --name friendlyphonenumber2 
This container is identical but listens to port 5002 in addition to the one we have listening on 5001.

In fact, you could create a bunch of these and use something like Kubernetes to do load balancing and container management.

There are lots of possibilities here, and you can easily scale this application out to use a lot more containers and even more servers. 

Conclusion

In this article, we covered creating an SSL Secured and Containerized Microservice with .Net Core. We covered the process from start to finish. If you were to build a production application for this you'd certainly want some better error handling, and use something like Nginx as a proxy, and Kubernetes to manage your containers. It's very easy to set up and the .Net Core package makes it really easy to build solid microservices you can scale to the cloud.

The source code and Docker files for this application are available here.

If you have any questions or remarks, feel free to leave them in the comments.

Discover how to deploy pre-built sample microservices OR create simple microservices from scratch.

Topics:
microservices ,.net core ,linux ,debian linux ,c# ,docker

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}