Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Micro service security

Over the last few days I've been playing with the micro service pattern and all is going well but security seems to baffle me.

So If I may ask a question: How do I handle user authentication on an individual service? At the moment I pass a request to the Gateway API which in turns connects to the service.

Question Edited Please See Below

Bearing in mind that the individual services should not know about each other. The Gateway is the aggregator as such.

Current architecture.

enter image description here

A little code to simulate the request:

Frontend - Client App

public class EntityRepository<T>
{
    private IGateway _gateway = null;
    public EntityRepository(IGateway gateway)
    {
        this._gateway = gateway;
    }
    public IEnumerable<T> FindAll()
    {
        return this._gateway.Get(typeof(T)).Content.ReadAsAsync<IEnumerable<T>>().Result;
    }
    public T FindById(int id)
    {
        return this._gateway.Get(typeof(T)).Content.ReadAsAsync<T>().Result;
    }
    public void Add(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
    public void Update(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
    public void Save(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
}


   //Logic lives elsewhere
   public HttpResponseMessage Get(Type type)
   {
      return Connect().GetAsync(Path(type)).Result;
   }
   public HttpResponseMessage Post(Type type, dynamic obj)
   {
      return Connect().PostAsync(Path(type), obj);
   }
    private string Path(Type type)
    {
        var className = type.Name;
        return "api/service/" + Application.Key + "/" + className;
    }
    private HttpClient Connect()
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("X");

        // Add an Accept header for JSON format.
         client.DefaultRequestHeaders.Accept.Add(
         new MediaTypeWithQualityHeaderValue("application/json"));

        return client;
    }

I use generics to determine where it needs to fire once it hit's the gateway. So if the Type is Category it will fire the Category service thus calling:

public IEnumerable<dynamic> FindAll(string appKey, string cls)
{
    var response = ConnectTo.Service(appKey, cls);
    return (appKey == Application.Key) ? (response.IsSuccessStatusCode) ? response.Content.ReadAsAsync<IEnumerable<dynamic>>().Result : null : null;
}

The Gateway does not contain the physical files/Class's of the types.

After a little code, I was hoping someone could give me a little demonstration or the best approach to handle security/user authentication with the current architecture.

Case Scenario 1 User hits the web app and logs in, at that point the users encrypted email and password is sent to the Gateway API which is then passed to the User Service and decides whether the user is authenticated - all well and good but now I want to fetch all Messages from the Message Service that the user has received. I cannot really say in the Gateway if the user is authenticated, fetch the messages because that does not solve the issue of calling the Message Service outside of the Gateway API

I also cannot add authentication to each individual service because that would require all respective services talking to the User Service and that defeats the purpose of the pattern.

Fixes: Only allow the Gateway to call the Services. Requests to services outside of the Gateway should be blocked.

I know security is a broad topic but within the current context, I'm hoping someone could direct me with the best course of action to resolve the issue.

Currently I have Hardcoded a Guid in all off the applications, which in turn fetches data if the app is equal.

like image 988
Tez Wingfield Avatar asked Jan 14 '16 16:01

Tez Wingfield


People also ask

What does micro service mean?

Microservices are an architectural approach to building applications. As an architectural framework, microservices are distributed and loosely coupled, so one team's changes won't break the entire app.

Why is security important in microservices?

In a microservices-based application, each individual microservice communicates without another microservice through well-defined APIs – this increases the attack surface and makes the APIs vulnerable to security threats. To overcome this security threat, it is imperative that all microservices are properly secured.


2 Answers

Edit

This answer is about the Gateway <-> Micro service communication. The user should of course be properly authenticated when the App talks with the gateway

end edit

First of all, the micro services should not be reachable from internet. They should only be accessible from the gateway (which can be clustered).

Second, you do need to be able to identify the current user. You can do it by passing the UserId as a HTTP header. Create a WebApi filter which takes that header and creates a custom IPrincipal from it.

Finally you need some way to make sure that the request comes from the gateway or another micro service. An easy way to do that is to use HMAC authentication on a token.

Store the key in the web.config for each service and the gateway. Then just send a token with each request (which you can authenticate using a WebApi authentication filter)

To generate a hash, use the HMACSHA256 class in .NET:

private static string CreateToken(string message, string secret)
{
    secret = secret ?? "";
    var keyByte = Encoding.ASCII.GetBytes(secret);
    var messageBytes = Encoding.ASCII.GetBytes(message);
    using (var hasher = new HMACSHA256(keyByte))
    {
        var hashmessage = hasher.ComputeHash(messageBytes);
        return Convert.ToBase64String(hashmessage);
    }
}

So in your MicroServiceClient you would do something like this:

var hash = CreateToken(userId.ToString(), mySharedSecret);
var myHttpRequest = HttpRequest.Create("yourUrl");
myHttpRequest.AddHeader("UserId", userId);
myHttpRequest.AddHeader("UserIdToken", hash);
//send request..

And in the micro service you create a filter like:

public class TokenAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
    protected string SharedSecret
    {
        get { return ConfigurationManager.AppSettings["SharedSecret"]; }
    }

    public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        await Task.Run(() =>
        {
            var userId = context.Request.Headers.GetValues("UserId").FirstOrDefault();
            if (userId == null)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }

            var userIdToken = context.Request.Headers.GetValues("UserIdToken").FirstOrDefault();
            if (userIdToken == null)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }

            var token = CreateToken(userId, SharedSecret);
            if (token != userIdToken)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }


            var principal = new GenericPrincipal(new GenericIdentity(userId, "CustomIdentification"),
                new[] {"ServiceRole"});
            context.Principal = principal;
        });
    }

    public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
    }

    public bool AllowMultiple
    {
        get { return false; }
    }

    private static string CreateToken(string message, string secret)
    {
        secret = secret ?? "";
        var keyByte = Encoding.ASCII.GetBytes(secret);
        var messageBytes = Encoding.ASCII.GetBytes(message);
        using (var hasher = new HMACSHA256(keyByte))
        {
            var hashmessage = hasher.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
    }
}
like image 77
jgauffin Avatar answered Oct 13 '22 18:10

jgauffin


Option 1 (Preferred)

The easy way is the micro services should be behind the gateway, hence you would whitelist services to connect to them, meaning only authorized and trusted parties have access (i.e. the gateway only). Clients shouldn't have direct access to them. The Gateway is your night club bouncer.

Option 2

You can use a JWT or some form of token and share the secret key between the services. I use JWT Authorization Bearer tokens.

The other services don't need to query the user service, they just need to know that the token is valid, then they have authorization to use the API. I get the JWT passed from the client to the gateway and inject it into the request that is sent to the other service behind, just a straight pass through.

The micro service behind needs to have the same JWT consumption as the gateway for authorization but as I mentioned that is just determining a valid token, not querying a valid user.

But this has an issue that once someone is authorized they can jump call upon other users data unless you include something like a claim in the token.

My Thoughts

The part that I found a challenge from Monolithic to Micro Services was that you needed to switch where you place your trust. In Monolithic you control everything you are in charge. The point of Micro Services is that other services are in complete control of their domain. You have to place your trust in that other service to fulfill its obligations and not want to recheck and reauthorize everything at every level beyond what is necessary.

like image 37
Adam Avatar answered Oct 13 '22 18:10

Adam