Recently, I met with Or Weis — a Snyk Ambassador — to discuss access control in the cloud. Or is an entrepreneur, based in Tel Aviv, where he founded Permit.io, a solution that empowers developers to bake in permissions and access control into any product in minutes and takes away the pain of constantly rebuilding them.
In our talk, we covered a variety of topics, like:
- Why it’s so important to always be using modern access controls in the cloud
- Why RBAC just isn’t enough
- Security and compliance
- The different layers of access control in the cloud
- The IAM waterfall
- Reducing your attack surface while building access control
Along with all that, we also discussed access control best practices that you could start applying to your cloud applications. In this blog post, we’ll recap those practices, pulling in information directly from the larger conversation. To learn about them in greater depth, I’d recommend watching the full discussion:
5 best practices for modern cloud access controls
Whether you want to build access control on our own, with open source or with proprietary tools, here are five best practices to make sure that what you are building is both future proof and foolproof, and that will provide guardrails to stabilize and protect your system.
- Decouple code from policy
- Design for event driven updates (decouple data from code)
- Design a back office for stakeholders
- Create an interface for customers
- Implement GitOps
1. Decouple code from policy
The first, and probably most important best practice, is decoupling policy and code. What we often see is people baking in their authorization logic into the logic of the application itself. At the end of the day, what you get is spaghetti code, with “if” conditions that query both the database and other sources for the authorization logic — and in parallel also query for the application logic. And as time progresses, they usually just mesh together. So with developers adding new conditions over time, it is almost impossible to understand which parts of the “if” condition are for authorization and which ones are for the application.
The result is that when you want to upgrade your authorization layer or the application itself, you’ll need to check this line by line and refactor, which brings us to the world of refactoring pain. So what we want to do is to decouple our policy from the code itself. We want to move to policy as code (PaC) and we want that code managed separately, ideally as a separate microservice that the other components in the application can query to get the logic that they need.
The natural questions from a new developer to this whole area might be:
- Who controls the access to that service?
- Which one comes first, the auth or the app?
To be blunt, this is a huge, highly-recursive world of complexity called authorization for authorization. To boil it down, you can minimize the access control of the subsequent layer and a lot of times you can minimize it to authentication exclusively. So, when your microservices connect to the authorization microservice, they are allowed to do so simply by having an identity that is verified by that microservice. Layers will make things more complex, but most tools try and solve it for you, or at least take a significant part of the load of that for you.
Let’s keep in mind that we ideally want to create a separate microservice for authorization. We do not need that microservice to be perfect. It can be as stupid as a function that always returns “true” initially. And while we do not want to build everything from day one, we want to have placeholders connected the right way so we can upgrade them gradually when new requirements come in. Whether it is a lambda function, a small container, or whatever you want, it has to be something that can evolve separately and that you can refactor with ease as additional requirements come in.
2. Design for event driven updates (decouple data from code)
The second best practice is to be event driven. Complex systems have complex policies that will change with the surface of the application itself. If your application is designed to be event driven from the start, it will make future updates easier to manage.
Let’s take an example. Imagine you want a simple policy like: Only users that have paid for a feature can use that feature. That bit of information does not reside in your database anymore or even as part of your application. It is part of a third-party service like Stripe, Chargebee, or PayPal. So we want to be able to propagate information from those services in real time as they change, and have them impact our authorization layer.
The best way to do that is to be event driven. Essentially, this means being able to receive webhooks from those external services or components and being able to propagate them into our authorization layer. This is called “decoupling data from code” and it is as important as decoupling policy from code.
3. Back office for stakeholders + 4. Interface for customers
Because we know that we are going to have customers and other stakeholders (like a product manager, security companies, etc.), we know that people are going to want interfaces to be able to manage the access control. While we do not have to build those interfaces from day one, we should be prepared to create them and provide them as the requirements come in.
So we want to have a back office where the different stakeholders can manage the application, and we want interfaces that we can provide to the customers themselves so they can invite users on their own, assign roles, even configure policies, etc. As most engineers will agree, the best service is self-service.
4. Implement GitOps
Lastly — and this brings us back to policy as code — simplify security with GitOps. We are talking about having complex rules and complex instructions managed in an environment that is ever-changing and has many stakeholders. The best way to manage that is through code. Just as we have infrastructure as code and security as code, we should also have policy as code.
If you manage our policy as separate code (ideally in a framework that is suitable for it) in a Git repository, you can elegantly manage versions, tests, benchmarks, and code reviews. This prevents you from reinventing the wheel on how to manage the policies and how to connect this to your system.
Always be modern
Keep these five best practices in mind as you are starting to build something. It is going to save you months later in refactoring just by being aware that these things are going to be coming up the road forward.
Finally, big thanks to Or Weis for talking with me and sharing his expertise on this subject. For any questions about this theme, we recommend to reach out to Or Weis on Twitter or LinkedIn, or join the DevSecCon community Discord and ask him your questions directly there.