Securing Microservices with Public key infrastructure

Problem Statement

OAuth

When you design the services based on microservice architecture, typically authentication would be via federated authentication. It would have some oAuth service or STS (Security Token Service) which would grant token to the client.

In my scenario, their were bunch of microservices each nicely encapsulated in the Docker containers, each were portable and move from one host to another for high availability and redundancy. There was an API Gateway, which would be serving as the facade for these microservices and would do service discovery and bunch of other stuff.

Authentication was one work API Gateway did for us. It would received the jwt token from the client and check for its authenticity, scope and validity. Once all is good, it would forward call to microservice which is been designated for. Each microservice based on operation would call other microservices, collate the data and give back the response.

Now, the problem arise how to we authenticate call’s between Microservices? There were couple of options

Option 1

Leave inter microservices call  happen with authentication

Unfortunately, this wasn’t the choice for us, as these services where deployed on public shared infrastructure.

Option 2

Let each service do token authentication with forwarded jwt token

This would be the option, but was overkill for us. Each microservice needs to have token authentication code. Plus, this would be anti-pattern as each microservice architecture, as cross cutting concerns like authentication is seeping in to the services. If there is change in jwt token data structure, changes needs to done in all microservices.

This was no go for us. We need some lightweight authentication just to validate if the call is from the our clan for microservices.

Option 3

This lead to our third option, PKI based authentication.

Here, it’s simple, all microservice would run on HTTPS and would take client certificate for authentication. It would check client certificates against it own certificate and if it’s valid, it would let the call happen.

It was secure as all communication was over HTTPS and simpler to implement than JWT authentication

Solution

Here, how we did it.

We have microservices coded in python and nodejs. Caller where ASPNET dotnet core web apps and few data analytic services.

Simplistic Flask application is as below

Python Code

#!/usr/bin/env python

from flask import Flask
from werkzeug import serving
import ssl
import sys

HTTPS_ENABLED = True
VERIFY_USER = True
API_HOST = "0.0.0.0"
API_PORT = 8000
API_CRT = "server.crt"
API_KEY = "server.key"
API_CA_T = "ca.crt"

app = Flask(__name__)

@app.route("/")
def main():
   return "Top-level content"

context = None
if HTTPS_ENABLED:
   context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
   if VERIFY_USER:
       context.verify_mode = ssl.CERT_REQUIRED
       context.load_verify_locations(API_CA_T)

   try:
       context.load_cert_chain(API_CRT, API_KEY)
   except Exception as e:
       sys.exit("Error starting flask server. " +
           "Missing cert or key. Details: {}"
           .format(e))

serving.run_simple(

   API_HOST, API_PORT, app, ssl_context=context)

 

Here, I have shown using the werkzeug server which good for development environment. On production you need to use some uwsgi server like gunicorn or uwsgi. Then this HTTPS code needs to be push to uwsgi service code.

For nodejs api services, here option to create the https server 

NodeJS Code

var fs = require('fs');

var https = require('https');

var options = {
   key: fs.readFileSync('server.key’),
   cert: fs.readFileSync('server.crt'),
   ca: fs.readFileSync('ca.crt'),
   requestCert: true,
   rejectUnauthorized: true
};

https.createServer(options, function (req, res) {
   console.log(new Date()+' '+
       req.connection.remoteAddress+' '+
       req.socket.getPeerCertificate().subject.CN+' '+
       req.method+' '+req.url);

   res.writeHead(200);

   res.end("hello world\n");

}).listen(4433);

 

The calling were dotnet applications. They would typically use some nuget package like RestSharp and code would be as following

CSharp (C#) Code

public static IRestResponse<User> AsyncHttpRequestLogIn(string path, string method, object obj)
{
        var client = new RestClient(Constants.BASE_URL + path); // https:....
        var request = method.Equals("POST") ? new RestRequest(Method.POST) : new RestRequest(Method.GET);
        request.RequestFormat = RestSharp.DataFormat.Json;

        // The path to the certificate.
        string certificate = "cer/clientcert.pfx";     
		X509Certificate2 certificates = new X509Certificate2();
		certificates.Import(...);

		client.ClientCertificates = new X509CertificateCollection(){certificate});

        request.AddBody( 
            obj
        );

        IRestResponse<User> response = client.Execute<User>(request);

        return response;

}

 

This way, I had secure my inter microservice communication using the certificates. Note, code which I have shown here is minimalist without the complexity of  production environment, but good enough to guide the way.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s