5 Application Authorization Best Practices for Better Cybersecurity
With the rise of cloud native applications, the challenges of security, compliance and access control have become top of mind. For many organizations, microservices have become the architecture of choice, not only for building new applications, but for migrating legacy apps.
With their many benefits, modern applications made these challenges significantly harder to solve. While in traditional, monolithic apps, there were may be dozens of individual components, with thousands of access possibilities that needed securing, today’s apps have thousands of moving parts, with potentially trillions of access possibilities, creating a substantially larger attack surface for every app.
Authorization — the means by which app owners can control who (and what) can access applications, and what they’re allowed to do, once inside — is at the heart of these challenges.
If you can solve for authorization — allowing only the actions and users that you specifically permit at key access points — you can solve the vast majority of security, compliance and operations problems that arise from the complexity of modern applications.
We’re going to share five best practices, with some technical details, to get authorization right in your cloud native application.
1. Use Policy as Code
This is the most critical move you can make to adopt modern authorization. GigaOm defines policy as code as a means for “mapping human-readable policies into machine-enforceable code.” This is a way to take critical security, operations and compliance policies and automate them in your application and across teams.
Most companies and developer teams used to hard code authorization logic and policies directly into applications, but given the complexity of cloud native environments, this no longer works. This is the same reason why developers adopt other as-code solutions, like Infrastructure as Code.
Here, Open Policy Agent (OPA), created and maintained by Styra, is by far the most popular and dependable tool for implementing policy as code. It allows you to decouple authorization logic and policy from the application itself while standardizing on a single trusted tech-agnostic tool.
OPA runs alongside the discrete app elements such as an API gateway or service mesh L7 proxy. OPA enables unified policy-based access control across the stack, and it’s become a best-practice tool for modern apps.
2. Implement a Zero Trust Architecture
Now that we’ve covered the tool used for modern authorization, let’s talk about policy enforcement. Zero trust may be a buzzword, but with OPA, you can functionally implement the principle of least privilege in your cloud native application.
As I’ve written about previously, two critical elements for implementing the NIST SP 800-207 Zero Trust Architecture are that a subject (user or service) must only be able to access a resource within an application when they have been both authenticated and authorized.
This means that the subject’s identity has been verified by an identity provider and that an authorization system has determined that the identity is allowed to access the resources. Let’s look at how we can implement these features of a zero trust architecture in a microservice using OPA.
Here, OPA is used to authorize communication between services in a service mesh. Specifically, it runs as a sidecar to an open source L7 proxy like Envoy, which is the basis of popular service meshes like Kong.
Meanwhile, we can leverage identity frameworks like SPIFFE/SPIRE (authentication) to create IDs that uniquely identify trusted workloads and services. In the example below, we’re creating a true “never trust, always verify” system between different services in an application (a frontend service and database).
In this model, each individual service, when authenticated, is assigned a trusted, verifiable identity (a SPIFFE ID, assigned by SPIRE). When the frontend service calls the database to fetch some data, the request, paired with the SPIFFE ID, is sent to OPA, which runs as a sidecar and implements Envoy’s external authorization API. OPA evaluates this call against the policy, “only the frontend service with an associated SPIFFE ID is authorized to communicate with the database.”
In this case, the authorization check passes. Critically, no other service is ever able to communicate with the database except for the frontend service authorized by policy as code.
3. Ensure Token Validation for Your Users
We can take a similar approach for end-user authorization. As my colleague Anders Eknert discussed in another article in The New Stack, one key best practice is to implement token validation for your users — ensuring you can establish secure trust for user identities. In other words, when a user calls a particular service, you can implement an OPA policy that requires that the user have a verified identity token.
Much the same way as in the service-service example above, we can use a different standard, JSON web tokens (JWTs), which are issued by a trusted identity provider in your environment, like OpenID Connect or Oauth2. And in an identical way, the same OPA can still sit alongside an L7 proxy like Envoy in your microservice environment, such as in the example below.
In a similar way, the user is authenticated with a “signed” JWT token establishing their identity. When a user calls the service, OPA evaluates the request against the policy, “Only a user with a valid and unexpired JWT is authorized to access the service.”
Of course, you can, and should, layer these policies to make more fine-grained, context-based authorization decisions, such as using role-based access control (RBAC) policies or specifying that the user must also belong to the engineering department. An even more fine-grained authorization approach would include attribute-based access control (ABAC) policies — for instance, that engineer must be on call, that they control a minimum budget (say, $500K) or have a certain level of authority (a senior engineer) in the company.
Note that, with OPA, there is zero functional difference between RBAC and ABAC — you simply need to add more fine-grained policies with a data source to support policy decision-making for those users like LDAP, Active Directory or ForgeRock, etc.
4. Enable Least-Privilege Access Control Across the Application
With these above frameworks in place, you can much more easily enable least-privilege access control at different points of your application — for instance, at the API gateway or between frontend and backend services. Using the policies sample below, you can get an idea of how OPA can concretely enable this least-privilege best practice within your application — that is, denying all traffic by default, while only allowing the traffic that is specifically authorized by policy.
API gateways are a standard feature of modern applications and, by virtue of exposing application APIs to the internet, are a critical enforcement point for policy. To see how a least-privilege policy might work here, let’s imagine a hypothetical example with a company, ACME Health, and their API gateway that controls access to sensitive data, such as filed insurance claims.
Here, a sample gateway policy with OPA would be: “Allow only the primary insurance holder of Acme Health to see filed claims.” In this case, the identity of the insurance plan enrollee would be verified by OPA, by taking a look at the user’s JWT, per the best practice above. In Rego, the OPA policy language, the policy might look something like this (here’s the link to the policy in Gist):
Backend Database Service
In the Acme Health example, the next step is for a frontend service to query the backend database to retrieve the insurance claims list. Here, the sample OPA policy would be: “Allow only GET requests on the /claims path.” The policy would also verify the SPIFFE ID of the service that is calling the backend database, enabling a true least-privilege model. In Rego, such a policy would look like below (here’s the link to the policy in Gist):
5. Use the OPA Learning Resources at Your Disposal
One of the benefits of aligning your microservices authorization around an open source standard like OPA and policy as code is that there is a robust community and a rich set of educational resources you can tap. For example, the Styra Academy provides free courses on deploying OPA for different use cases and learning Rego. On the latter point, the Rego Playground is an excellent resource to learn and explore Rego policy.
Meanwhile, the OPA Community Slack lets you engage with the community directly. Finally, resources like the OPA 101 Starter’s Guide can give you a full rundown on everything OPA, and the OPA documentation provides practical guidance to get you started.
Modern, cloud-native security is difficult, and application authorization has become one of the most critical focuses for enterprises. Yet, there are practical steps that can help streamline application security. Using the steps above, you can whittle down the number of potential access possibilities in a microservices application from literally trillions, to, in effect, only those authorized by policy as code as enforced by OPA.
Note that this is a strategy you can implement not just in your application, but anywhere across the cloud-native stack, from Kubernetes admission control, to cloud-delivered infrastructure (think policy checks on Infrastructure-as-Code resource changes), to CICD pipelines.
Want more guidance on application authorization? Check out our on-demand virtual event, Level Up AuthZ for Modern Applications where we cover everything you need to know about application authorization.