A common architectural style used today is Microservices. In contrast to a monolith application architecture, a microservice-based solution comprises of many services that collaborate to produce the required application functionality. Each microservice exhibits the following architectural characteristics:
- Loosely coupled
- Independently deployable
- Organized around business capabilities
For these characteristics to work, each service is responsible for all of its functionality, from service entry point right through to storage. You can think of it as a top-to-bottom slice of application functionality.
Assuming the service's functionality requires some form of authorization, the starting point is often first to establish the identity of who is making the request (subject). There are many ways to obtain this identity; probably the most common is using a JSON Web Token (JWT) from the inbound request. An Identity provider, trusted by the service, signs the JWT as proof that it has verified the identity held in the JWT. In essence, the microservice has delegated identity verification to the identity provider; after all, this is a cross-cutting concern, and identity is common across all services.
How about authorization?
Authorization is a business function, with each service deciding on the criteria to determine access or not. There may be a tendency, like authentication, to consider authorization as a cross-cutting concern. In this case, you would have a single policy service inside the network, issuing permits and denies. There are, however, issues with this approach:
- Performance is impacted by making a round trip to the Policy Decision Point (PDP), which incurs latency. To counter this, the microservice could cache the result, but this can lead to the service making authorization decisions against stale data.
- In an ABAC solution, the PDP needs to obtain attributes from the Policy Information Point (PIP) to make decisions. Each microservice will likely require policy decisions to be based on some attributes that are unique to itself. Implementing a single PIP for all services is, in effect, creating a monolithic PDP.
- Each service is now no longer "Independently deployable". A new version of a microservice may require new attributes for policy decisions, and so a deployment to the monolithic PDP is also necessary to extend its PIP.
- There is a single point of failure that can bring down the entire microservice ecosystem or at least force it to run on cached decisions that may no longer be valid.
An approach that is more consistent with the microservice model is to give each service its own PDP and PIP. This provides the following benefits:
- No latency, and therefore no need to cache decisions
- Each service has its own PIP with the knowledge of how to obtain attributes necessary for the service to produce its authorization decisions. The PDP is no longer a monolith.
- The service is independently deployable. A new version of the microservice that requires new attributes for authorization only requires the local PIP to be extended.
- One less dependency to fail. An issue in one microservice's authorization engine is unlikely to affect other microservices.
The Policy Access Point (PAP) provides policies to the PDP. Each service could have its own PAP deployed alongside the service. However, this requires a re-deployment of the service to make any changes in security policy. A preferred approach is to have the policies stored in a centralized PAP that each service PDP observes for changes and re-loads, allowing administrators to deploy new security policies instantly.
The Enforcer ABAC engine is an excellent fit for a microservices architecture. Each microservice can have an embedded PDP and PIP with an external PAP to provide policies. Enforcer comes with a Git PAP implementation allowing policies to be updated and deployed using your preferred Git workflow. Each service configures itself to watch a Git branch, and when a file changes, each microservice PDP automatically loads the new changes.
The important point here is that although the policy is distributed from a central point, each of the services works autonomously, temporary failure of the git storage doesn't prevent the service from continuing to execute.
Authorization is not a cross-cutting concern. Each microservice has specific requirements to protect its functionality. Keeping authorization local to the service keeps the service true to microservice best practices.