 Welcome to this CNCF webinar, where I'll be talking about policy-based access control using Open Policy Agents, which is a CNCF-graduated project. The first, whom I am, Anders, and I work as a developer advocate at Styra. Styra is the inventors of Open Policy Agents, an open policy agent project, and one of the main contributors. I have a long background in software development and primarily in identity systems. I've worked with OPA for about two years now, and when I don't, I'm mainly interested in cooking, food, and football. I'm on Twitter, I'm on GitHub, and if you want to connect, feel free to do so. So, policy is code and OPA. What's the challenge we're trying to tackle? What is the problem we're trying to solve here? I think one common theme here is pretty much to manage policy in increasingly distributed, complex, and heterogeneous systems. We have all these applications, stacks, programming languages, frameworks. We have deployment platforms like Kubernetes. We have the underlying cloud and infrastructure, and we have data in all its forms. And all these systems, they tend to deal with policy in their own way, using their own domain-specific languages, their own way of logging, and so on. So, what we're really trying to do here is to unify policy enforcement and the way we deal, like, manage policy, distribute policy, and log policy decisions across this whole stack. And that's really what OPA is about. There are many other systems to work with policy. If you zoom into one of these icons or one of these systems, you'll probably find ways of working with policy for just that system that OPA is trying to tackle the problem at large. So, what is OPA then? It's an open-source general-purpose policy ending. And general purpose, it kind of has to be in order to fulfill this kind of promise of unifying policy management or unifying policy enforcement across this diverse set of technologies and products. OPA offers a unified tool set and a framework for working with policy across this whole stack. OPA decouples policy from application logic, meaning that you can extract policies from your regular business logic or application logic. Sort of like you'd extract some of the data management to a database, you'd extract policy to OPA. So that's kind of what we mean when we talk about decoupling. OPA is a decision-ending, so it returns decisions. It doesn't really enforce them itself. That's still up to your application. What it's meant to do with that response. So if OPA says, no, this should not be allowed or this should be denied, it's still up to your application to actually do something about it. Maybe send back a 403 or whatever is appropriate in that context. Policies, they're written in a declarative language called Rigo, which we will look into closer later today. Since we're talking about a general-purpose policy engine, we're seeing use cases here ranging from anything from Kubernetes admission control, microservice authorization, infrastructure policies, data source filtering of attributes, or CI CD, like build and deployment pipelines. So there's policies all across the stack. OPA really fulfills this promise of unifying policy enforcement across the stack. So OPA is a vibrant open source community. I think these numbers might even be a little dated by now because last I checked, we had over 5,000 GitHub stars and even 4,000 Slack users. It doesn't really matter. OPA is a big and vibrant community and not only do we have the open policy agent project proper, but also there are many interesting projects beneath that were included in the whole big OPA project, such as Conf test for testing during config validation and having policies on configuration files. There is OPA Gatekeeper for doing admission control and there is plugins for editors such as VS Code and IntelliJ. Of course, OPA is not just a hobbyist open source project, but it's also used by some of the largest companies in the world. And if that's not enough to convince you, maybe this quote will. The open policy agent project is super dope. I finally have a framework that helps me translate written security policies into executable code for every layer of the stack. So that kind of summarizes what OPA is about and also why it's so useful. So OPA then, how does it work? How can OPA service requests for all these diverse services and all these diverse technologies? And the answer behind that is the policy decision model. The way it works is that any service, this is normally an application, or it could be an envoy proxy, or it could be the Kubernetes API server, or it could be a Kafka. And again, so this is where the general purpose model comes in. Any service, anything that can service requests reaches out. When it receives a request, it reaches out to OPA and it asks for a decision. And most normally it will also provide some data as part of that request. And that data is just JSON. So it says something, I have this information here. Maybe the name of the user and some roles. And here's the endpoints that this user is trying to access. Should this request be allowed or not? And OPA will make a decision based on the policy which we have provided OPA from before. And based on a policy, OPA will make a decision. And that decision is also just JSON. So that kind of that is the secret sauce behind how can OPA work with all these technologies because any product or project or service capable of sending HTTP requests and parsing and crafting JSON can be integrated with OPA. So as for deployment, OPA itself, it's a tiny little self-contained binary. So it's a single file. You just run it and it has everything you need included. And ideally you deploy OPA as close to your service as possible. This would normally mean that you run it on the same host as your application, so on the same machine. Or maybe if you're in the Kubernetes world, you'd run OPA as a sidecar, which essentially means the same thing. You'll run OPA on the same machine as your main application. So when you query OPA, it's always a local host. And the benefit of doing it this way rather than having like a big giant central OPA server is, of course, that the distance between your app and OPA is going to be as short as possible to keep the latency down. The way OPA or your application communicate with OPA is then to the REST API, again, following this policy decision model. But if you have applications written in Go, you can actually use OPA as a library as well. There's also integrations for OPE Envoy is still and you can also compile policies to WebAssembly. So there's really a wide array of deployment options as well. But the one you'll see mostly deployed is the REST API. So policy offering then, how does that work? And how do we provide policies to OPA? The way Rego works is it's really a high-level policy language and a declarative one at that. And what does that mean? It's essentially like SQL where you, rather than saying exactly how you want something done, you say what you want done. And then it's up to the policy engine to figure out the best way to go about that. A policy is pretty much any number of rules. So this maps pretty much to what a policy is also in real life. And a rule is a rule commonly returned something. And that could be any value which is valid JSON. So it could be a Boolean like true or false, whether are you allowed or not, true or false. But it could also be more complex objects like strings or links and pretty common pattern you'll see in policies is of course that you'll return maybe a reason why somebody is denied. I think there's almost 150 built-in functions today. And Rego is not like a general purpose programming language. So these built-in functions, they tend to focus on things that are useful in the context of a policy offering. So you'll see things like validating certificates, checking or verifying JSON web tokens, date and time functions, IP address ranges, things like that and things around identity and permissions. That's really what we're working with here. For testing we open ships with a testing framework so you can actually test your policies in isolation. It's a very well-documented project. So I think one of the better documented projects I've had a benefit of working with. And there's also the Rego playground, which we'll look into in a few minutes where you can actually try policy offering without even downloading OPA. So policies, they're only half of the story because normally a policy can't do much without some knowledge about the world around it. So if we, for example, say that any user to access this endpoint, the user needs to be an admin. Then we also, in order to make a policy decision, we need to know the roles of the user. And that's what we call policy data. And that data can be provided to OPA either as part of the input, like as part of the query. When we ask OPA we can say, I have a user here and this user has these roles. Is she or he allowed to access or to retrieve this data? But we can also push data into OPA beforehand. So the data is already available when we query OPA for decisions. Or we can use the bundle API to pull data from a remote endpoint. And finally, there's also an HTTP send function which is really just an HTTP client which we can use from inside our policies to fetch data at policy evaluation time. Okay, so at this stage I'm actually going to show how simple policy offering might work. And of course I'm going to use now the Rego Playground to encourage anyone watching to do the same. Just see how it works and how we can work with policies. So what we have here to our right, that is the input data, which in this case we're going to simulate a REST API or an API or a microservice. We're using servicing regular HTTP requests. So we have a path. The user is trying to access a path. In this case it's the user's path. And we can see that the request method here is a GET request. And we also have some basic user information. In this case only the name of the user. So our policy then is going to have to decide whether this should be allowed or not. And yeah, a GET request on the user's endpoint that seems pretty fair. Like anyone can probably read all the users given that it's not like any private information or so. So it seems fair that we have a policy that would allow this. So again a policy is just a series of rules. So the first thing we're going to want to add here is a rule. In this case I'm going to name this rule allow. And again, OPA doesn't really care about your names. So allow has no special meaning for OPA. It has a special meaning for us as people. But to OPA it's just the name of the rule. And the anatomy of the rule is kind of like this. So you have a name of the rule and you have a body. And if all the conditions inside of the rule are true, so we do something like this. 1 is equal to 1 and we evaluate this. We're going to see that yes, the allow rule is now true. And if we do something like this, we're going to see, no, it's no longer true, it's no longer nothing. So by default, if a rule does not evaluate, it's just undefined. So if we want to change that, we could do something like this. We could say by that, by default, allow should be equal to false. And now if we query, we'll see that this kind of fallback condition is triggered and we'll always get a response. So if we change back here, we'll see that it's true. And remember I said that all conditions inside here need to be true. So if we do something like this, we're back to the false because this is obviously not true. So the way you can think of this is pretty much like if you come from another program language, you would probably write it like this. And then you'd add, after each line, you'd add an add condition. So if one line evaluates, it will hop on to the next line evaluate that. And once it doesn't, the condition is, or the rule is just going to be undefined. Unless, of course, as we did here, we declared a default value for a rule. Oh, okay. So this is just like silly constants in use here. So let's make something useful. If we say if the path, and we're going to use the global input here, which is, of course, the values we see here to our right. So with the input path is equal to users. Yeah, then this should be true, right? Because the input path is equal to users. So we're going to say that if that happens, yeah, we should allow that. But of course, in this case, the input method could be delete or put or something trying to modify this users. And we don't want that. We don't want to allow that. So we're also going to say that the input method is equal to get. And one thing we might want to change here is how this can add. There's using a string like this. We can't really know is this the application always going to provide us with like a leading slash like this or a trailing slash or so. So what we could do here is we could use opus built in the built in functions. Again, there's 150 built in functions. So what we could do here is we could say the path, if we try and maybe trim these, these, we're going to say, and we're going to trim the input path from slashes. And if we evaluate this now, we should see that both the path and the allow rule is evaluated. And we can see that, yes, indeed, the slashes were trimmed. And sorry about that. And what we might want to do now is rather than working with strings, because if we have more names here later, like somebody's trying to access this, we are going to need to do something like RegExpo or so. So what we'd rather want to do here for the path is that we'd rather want to, what we really want to do here is we want to split that. We're going to split that on slash. So instead of a string, we'll have an array to work with. I think this is often preferable. And this kind of shows how we can work with the built-in functions of OPA. And again, if you check the docs, you'll see there's a reference for each of these, or each of these built-ins are listed there. So, okay, so now we have a path to work with. And of course, we'd rather want to work with this new path that we have. So we're going to do this. So if the first or if the first path component, because in this case, we don't really care as long as the method is get, this could be any user. This could be Anders or this could be Jane. If someone is trying to read a user, we should still allow it. So we're just going to say that if the first path component is equal to users and the method is get, we're going to allow that request. And let's see if that works. Yeah, we can see that allow is still true here. If we change something here, like let's say delete, we should see that now allow is no longer true. So this is kind of how we work with decisions in OPA. And maybe we'd want to add another condition. Maybe we would want to allow some form of modification. Maybe a user should be able to modify their own details, but obviously not of any other users. So how would we add another condition? The easy way to do this is simply just to add another rule. And we can have as many rules as we want with the same name. And the way it works is that if any of these rules are true, then we say that the rule named allow is true. So while the conditions inside of the rule are added together, the rules themselves are warned. So if one of them is true, the results or the decision is true. And of course, again, rules don't necessarily have to be true or false, but they can return any value. But for this example, we're just going to go with a Boolean response. So if we say here that the first path component is users, I think we can stay with that. And the next path component here, that's the name of the user. We're following like the rest conventions here. So if the next path component is equal to the name here, which is the user name. So we say input user name. And the input method is equal to put. Someone is trying to modify their own user. We should allow that. So delete, that should still not work. If we change that to put, it should work. And it does. So we kind of have a way of, in just 16 lines of rego, we have a policy that allows any user to read from any endpoint or from any user endpoint on the user's kind of base path, but users may only modify their own user. So if we change here to Jane, we can see that Anders can no longer modify this user because that's not who I am. So, so yeah, 16 lines of rego, and it's a fairly useful policy. So in kind of a pretty compact way, we've managed to formulate a policy where we decouple these decisions from our application logic. So our applications, they would not need to know about this logic, who gets to access what. So in your business logic, you can focus on actually servicing the request. And OPA will focus on actually on enforce or making these policy decisions. Okay, so back to the presentation then. We saw what we saw there was kind of an example of how we could write policies for an application, a microservice or an API. Another common use case is of course Kubernetes. And how does that work then? The way the Kubernetes API works is that whenever a resource is sent to be persistent, meaning if you try and modify one, if you create something new. So in this example here, if we do kubectl apply, and we're trying to deploy a new application, that request is going to be, it's going to have to pass a series of modules. And these modules are authentication, they're authorization. It's a mutating admission controller. It's a validating admission controller. There's even more modules involved, but kind of try and keep it understandable here. And additionally, all these modules are shainable. So it's not just one, but there could be many authorizers involved. There could be many admission controllers involved. And once the request has passed all these modules, we say it's finally persisted to EtsyD, which is the database of Kubernetes. There's a whole bunch of built-in modules in all these steps. Some of them are allowed by default or enabled by default, and some are not. But what's interesting for us here is of course the webhook. Because for what a webhook is, it's really an HTTP request, and in the case of Kubernetes, it's JSON. And remember, that's exactly what OPPA is meant to do. It services requests sent over HTTP. So OPPA is a perfect fit for authorization, mutating admission controller, or validating admission controller. OPPA of course doesn't do authentication, but for these other three scenarios, OPPA is a great fit. So let's zoom in here on the validating one. And why the validating one? Because it's by far the most popular module to extend. And the reason for that is that it allows us to build policy-based god rails around our clusters. So we can build rules to enforce things like forbid any container to download an image from an external Docker registry. We only want to allow the internal approved company registry, for example. All resources deployed in our cluster must have appropriate labels. So maybe we want to distinguish team belongings, cost anchors, our office department, organizational policies, and so on. Ingress and host path uniqueness, another common use case where we want to check that if we deploy this ingress, it's not going to be a conflict with anything else. Maybe you want to enforce TLS, deny certain attributes, have certain resource allocation limits, and so on. So there's basically no end to the type of policies you can write for an invalidating admission controller. And this, of course, allows us to build these god rails that we mentioned before. The way it works is essentially the same as the REST API policy we saw before. The order here is a bit randomized, but the input to the left was obviously what was up to the right before. We can see it's still just JSON. In this case, it's a pod being submitted for admission control. And in the middle, we see the actual policy. And it's a very simple policy. It just checks if there's not an input request object metadata labels cost center attribute. Then this rule, the deny rule, will return a string or will add a string to the set. And in this case, it will just say that every resource must have a cost center label. So before we finish, just a quick word on OPAS management APIs, because once you can pass the initial stage of having deployed a couple of policies, a couple of OPAS servicing your APIs or your microservices or Kubernetes, you're going to want to manage OPAS scale. And for doing that, there's a couple of API endpoints, which OPAS influence or which OPAS reaches out to for management features like bundles, fetching bundles. So this is really for fetching policy and data from a centralized location. The decision log API allows OPAS to report back on any decisions made during its operation. So any decision ever made is reported back. So we can use that for auditing or for improving the quality of our policies. And there's also a status API for health and health check and status updates. And a discovery API for configuration. So where do you start? My suggestion is usually a start small. Maybe try the rego playground, browse the OPAS docs, and get a feel for the basics and how everything works. And once you can, once you grasp the basics, you can start looking into the applications that you've worked with previously. And again, you can't really choose whether to work with policy or not, like policy is pretty much everywhere. But what you can try and do is identify where do we have policy today? And once you've done that, you can start to slowly delegate some of that responsibility to OPAS. And again, you can start small. You don't need to rewrite your whole authorization logic or your whole policy system, but maybe start with a single endpoint or a single role, like maybe delegate the admin check to OPAS. Deploy and build experience from that. And once you're done with that, you can start scaling up. You can look into things like policy management, decision logging, bundle server, these kind of management features that I mentioned. And for learning, the Styra Academy is a great resource. It's a free resource as well where you have like video tutorials followed by quiz style tests. So I can hardly recommend that. For more management features, there's the Styra Das, which also has a free edition, which you can try out. And finally, join the OPAS Slack community. We're over 4,000 members already. And it's a great way to ask questions, see what others are asking, what are the interesting projects brewing in the OPAS community, and just like general good place to hang out. So do join us there. And with that, I say thank you and I hope you enjoyed this webinar and that you learned something about policy and open policy agents. So thanks.