In the era of microservices, APIs serve as the vital conduits through which a staggering volume of data flows among microservices and applications. As companies increasingly adopt a microservices architecture, a critical question consistently arises: How do we make sure only the right people access our data, and how do we protect that data in each microservice and app?
Back in the old days of monolithic software, authentication was a very straightforward process — authenticate the request, store permissions in a central state, and verify each action has adequate permissions against this state. However, microservices have shifted this landscape dramatically.
This simple problem becomes a bit more complex, or actually, to be completely honest, a lot more complex. Should authentication occur independently for each microservice? How do we seamlessly pass the user’s security claims between different services? Are there security advantages to breaking the environment into smaller parts?
At Flow, many of our customers have faced these questions, and this blog post is here to help you make sense of this complicated world and guide you on how to keep your data safe in the age of microservices.
The Naive Approach
When first encountering the need for authentication, the first approach that comes to mind is constructing some sort of authentication gateway. The idea is to have a component at the entrance of the environment that deals with authentication. This is usually accomplished using reverse proxies such as Kong or Nginx but can be done in various other ways.
This component verifies who the user is and attaches data such as the user ID and various claims (what this user is able to do, what limitations this user has, etc.) to the downstream request.
This approach is simple at first glance, but on further examination, it might introduce some problems.
Firstly, it’s all about trust. It assumes that everything behind this gateway is trusted. Anyone with network access to one of the microservices behind the gateway can pretty much party with whatever those services allow.
Additionally, since passing around the claims is the responsibility of the programmer, this can lead to human errors, and programmers might cut some corners, which can cause serious security risks.
Let’s take the following example:
Let’s say that a Kubernetes cluster hosts a Prometheus server. One day, a major security flaw hits Prometheus. It allows a malicious actor to execute code on the Prometheus server.
Because it’s hosted on the same cluster as your sensitive microservices that process sensitive data, it has network access to them. This, in turn, would allow the attacker to impersonate any user on your system, potentially allowing it to access any sensitive user data processed on those services.
The Secure Approach
The solution is simple. Authenticate every request on each service. At login, there is one service that is responsible for authenticating the user and generating an authentication token (“Token Generator”).
This token is then sent to the server on each request and passed around with every internal request within the environment. Every microservice is responsible for authenticating it separately and drawing the claims out of the token.
There are several variations on this idea, but the main point generally involves having one component generate tokens, and all the others rely on those tokens.
The advantages of this approach are clear. Instead of trusting all internal services, you only trust the token issuer in your system. If you are using JWT as your token, you can store the permissions object within it as well.
Let’s take the hypothetical case we took before. Now, the compromised Prometheus server cannot generate a valid token without authenticating against the “token-generator.” Using a forged token would result in an authentication error.
But now a new problem pops up: how should you implement this in your environment? Let’s say you’ve decided this is the way to go, and you have many Kubernetes clusters on different cloud providers and locations. Making sure each service hosted in those clusters requires authentication can be tedious.
Furthermore, even if you have already ensured all the services require authentication, new software may be written and expose an unauthenticated API as the organization grows.
How to Implement This in Your Environment
As with most security issues, there is more than just technology; there is also an organizational effort involved.
In reality, there is a big gap between ideal security architecture and the dynamic chaos of a living, evolving product. The idea is to try and build a bridge that connects your security aspirations with the pulse of a developing product.
How can you do that?
- Discover all your APIs. Begin by cataloging all your systems and microservices. Document the APIs associated with each microservice, including their endpoints, authentication methods, and the types of data they handle. Determine whether these APIs are publicly accessible, etc.
- Establish an API Authentication Baseline. Assess the security posture of your APIs by evaluating the authentication method used in each one. This will provide insights into the security of your data and API infrastructure.
- Enhance Posture and Establish Alerting. Once a baseline has been established, it’s time to migrate services one by one to require authentication. You should gradually see more and more parts of your environment protected this way. Once your environment is sufficiently secured, configure alerting rules that allow you to maintain this state. For example, it alerts you every time a microservice serves sensitive data without adequate authentication. Implement alerting solutions designed to identify and respond to security incidents automatically. Ensure that your alerting system can trigger immediate responses to address any security issues that arise.
In the world of microservices, navigating the realm of authentication can feel like a complex puzzle. While the simpler solutions may seem tempting initially, they often lead to headaches down the road.
On the other hand, taking the secure path of individual service authentication is like finding the right key to unlock the puzzle. It’s a mix of technology integration and team alignment, a bit like putting together a perfectly balanced dish.
By proactively diving into practices such as digging deep into your systems and keeping an eye on security, you can build a bridge between your ideal security dreams and real-world implementation. This approach not only helps you set up a solid microservices environment but also ensures you can keep it safe and sound as technology keeps evolving.