 Hi, everyone. My name is Tim Hinrichs. I'm the CTO and co-founder at Styra and one of the co-creators of the Open Policy Agent Project. Today, I'm going to be talking about security by construction and how you can weave authorization into a modern application stack using the Open Policy Agent Project. So the jumping off point for this talk is really just a few comments around some of the megatrends that we're seeing around microservices, DevOps, automation, self-service, CICD, cloud and cloud native. The key observations here are that if you compare the way things used to work in terms of application development to the way things work today with all these new technologies and processes in place, we see a pretty tremendous impact on security. If you think about all the microservice APIs, all the APIs that exist across the cloud native spectrum of technology, what you see is a huge increase in sort of an attack surface from an attacker's point of view. We just have more APIs as APIs are now not only publicly available, but they're also available privately within your microservice architectures. If you think about the cloud itself, certainly the cloud, we're running all kinds of workloads on hardware and networks that we have no physical control of and so there's just less overall control over the networks and data centers that we're using. If you think about CICD and automation, one of the things that leads to is people deploying applications, all kinds of applications or even parts of applications to all kinds of data centers all over the planet. The environment in which we're deploying software is simply far more dynamic than it used to be. And if you think about DevOps and self-service, we end up with just more people who are in control of our production applications, our production infrastructure as well. And so that kind of process change, that kind of organizational change again has tremendous impact on security. You've got more people able to execute a broader range of APIs and functionality in far more dynamic environments when we have even fewer controls over the actual hardware that is running all of that. And so while there's a whole bunch of different aspects of security that we could focus on today, we're going to look at authorization. So remember, authorization is a problem with insecurity of controlling which actions are allowed to be taken. And so there are a whole bunch of different dimensions to that here where I've called out a few. So maybe you're thinking about which people or which machines can perform which actions, which APIs on which software and maybe even in which environments, maybe at what time of day, maybe based on what kinds of attributes on resources, all of those different things make up the conditions on which an authorization decision is allowed or denied. And so this authorization problem, keep in mind, is very different from authentication. They're often confused, but authentication is just the problem of who are you. Is it Tim or is it someone else who is actually talking to the software? So we're focused on authorization. And now if you think about authorization in this broader context of cloud and cloud native and all those megatrends we talked about on the last slide, there are a few pretty powerful and new requirements that come up when you start thinking about authorization. So one of which is that you do need to be able to write pretty sophisticated policies. If for no other reason then the space of all these possible decisions is quite enormous these days and you need more sophisticated policies in order to be able to express the granularity. Whatever granularity you care about in order to make decisions. We are also in this world where long gone are the days of enforcing policies just at the firewall and just at maybe the API gateway. Now we're in this world where because we're deploying software so quickly and in such dynamic environments that we want authorization enforced pervasively on every piece of software that gets released. And the place where we see this the newest is when we start thinking about microservice applications where what we want to start doing is enforcing authorization at each and every microservice. And you see all kinds of folks doing that today. The third requirement here is really around the policy life cycle. So it's certainly what we want to guarantee is that as software evolves that the policy evolves right along with it. And so we want to be able to ensure that that not only is authorization or authorization policies enforced with and next to every single piece of software but also that those two that the policies as well as the software themselves stay synchronized. And so what that means is that what we want is a life cycle for policy just like we have a life cycle for software. So as we go through this talk we'll touch on each of these three requirements and discuss how to achieve them. Now let's take a quick look at what sort of a modern application stack might look like. Hopefully this looks sort of representative to you. I'll call out the major things here. We've got a CI CD pipeline on the left. We've got our cloud whether it's private or public down below. We have a number of hosts running on that cloud some of which are running databases some of which are running containers. We've got some orchestration going on there. And so the idea is that this is sort of a fairly canonical or representative sample of what a modern application stack looks like. And the observation I'll call out when it comes to authorization is that each and every one of these different categories of software have their own unique way of solving the authorization problem. Well one of these has a different GUI, a different API, a different even model for how you think about solving authorization for controlling which actions can be taken on that software. And the same holds true even within a single category. If you thought about like microservices for example, without quite a bit of forethought if you give 10 different teams the responsibility to build 10 different microservices, I would guess that you're going to end up with 10 different implementations, 10 different APIs for authorization. The same thing is true of databases and public clouds and CI CD pipelines. Each and every one of these different pieces of software have their own way of solving that authorization policy problem. And obviously from a user's point of view, if you're given a PDF or a Word document or an email or a Wiki that says here's a policy that needs to be enforced. Here is who is allowed to perform which actions. If you're trying to enforce that across your stack of software, you're going to have to figure out and learn those many different APIs, GUIs and models. So because of all this, one of the things that we did a while back was we created a project that became known as the Open Policy Agent Project. Its goal at the end of the day was to provide a unified solution to authorization across all of this cloud native ecosystem. So the idea here being that you have one language, one framework, one set of tooling to go ahead and enforce policy authorization across all these different pieces of software. Now, not only was OPPA designed to give you this unified solution to authorization, but it was also designed in a very cloud native architectural way so that OPPA is a very lightweight process. You can really run it next to, meaning on the same server as, whatever piece of software needs authorization decisions. And in so doing, you can realize this goal of pervasive enforcement, right? Run not just one OPPA, but 10 or 100 or 1000 OPPAs to make sure that OPPA, that policy can be enforced almost as part of that underlying piece of software. But at the same time, you have decoupled the policy from that piece of software so that you can provide a unified solution to authorization across the stack. Now, OPPA, we founded it at Styro back in 2016. We then donated it to the cloud native computing foundation as a sandbox project in 2018. It was promoted to incubating about a year later. The last level of this of the CNCF would be graduating. That's where you'll find projects like Kubernetes and Prometheus. So if you're interested in contribution stats, I definitely check out the CNCS DevStat portal. In terms of users, this is typically how I like to engage with a new project. It's to go and listen to how some users are actually using the software. So I feel a little bad about talking about another venue here. But nevertheless, this is a good KubeCon at US 2019, so the last physical KubeCon, is a place where you'll find a bunch of folks talking about how they're using OPPA in production at scale. And so here there's really one portion of these come from the standard KubeCon session talks, but what we also did at that KubeCon was have the host the first OPPA summit, and here we just had another half dozen great talks. So this would be a great place to go if you want to learn more about OPPA from end users. So the question then becomes, well, what is OPPA? How does it work? We've kind of talked about the benefits up to now. So OPPA is what we like to call a general purpose policy engine. Its mission in life is to make authorization and more generally policy decisions. It's not responsible for enforcing them. That's another piece of software's responsibility, but OPPA is responsible for making those decisions. And so the way this works is the service at some point in time decides it needs some sort of authorization decision. And so it sends a policy query over to OPPA. OPPA makes a decision and returns that decision to the service. That service goes ahead and does the enforcement. Now this sort of paradigm is the very same paradigm that works across all those different use cases that you saw in that last couple of slides back. And the way that that works, the reason it works, OPPA works for so many of these different services is that OPPA does not have built within it a bunch of knowledge about Kubernetes or microservices or your gateways or Kafka or Terraform. Instead, whenever a service sends a policy query to OPPA, that policy query from OPPA's perspective is simply an arbitrary chunk of JSON. Now, despite the fact that OPPA has no knowledge of what that JSON represents in the real world, I as a policy author, I know what that JSON represents. If that service is, let's say, a microservice, it's handing over a JSON object that has a method, a user, and a path. Well, I, when I'm writing policy, I know what a user method and path are, even if OPPA doesn't. And so I can write the appropriate policy. If that service is the Kubernetes API server, then when Kubernetes hands over that JSON object, this time it's, you know, 50, 100, maybe it's 500 lines of JSON that represents whatever new pod or ingress the end user of Kubernetes is asking Kubernetes to spin up on the service. And so again, OPPA doesn't know what a Kubernetes pod or ingress is, but I, when I am a policy author, I know what those couple hundred lines of JSON represent in the real world and so I can write the policy to make the correct decision. So that's pretty key and that's that kind of flexibility is why OPPA works for so many of these different use cases and it's why it is so easy to integrate with OPPA because you don't have to do a bunch of ETL to get your data into a format that OPPA understands. What that sort of implies is that the policy language that OPPA provides is flexible enough to allow you as a policy author to make whatever authorization and policy decisions you want over that arbitrary JSON data. So a good example of this would be let's say you want to write a policy that says only people who are, that says all, let's say pods that are all workloads that are deployed or all containers deployed onto a Kubernetes cluster need to come from a trusted image registry. In order to do that, you need to be able to dig down through sort of arbitrarily deeply nested JSON data. You need to be able to find that, in order to find that list of containers, you need to then be able to iterate over the array of containers and for each container extract the image name for that container and then you need to do some string manipulation to figure out exactly whether that image is coming from a trusted registry or not. And so all those kinds of functionality are things that OPPA's policy language called REGO was designed to be able to do. The next thing of sort of working our way around the slide on the lower right-hand side there is showing that data or JSON can also be fed into OPPA. One of the things that we designed OPPA around was the idea that, was that a lot of policies, especially in this very dynamic world, need to be context aware. They need to be aware of what's going on in the world around them. And so this is why when we designed OPPA we designed it so that you can inject arbitrary data into OPPA. That arbitrary data represents the state of the world. And then you, as a policy author, have more JSON data to use in order to make whatever policy decision that you like. A good example here would be, let's suppose that you want to write the policy that says, only people who are on call can SSH into production servers, can make changes on a production Kubernetes cluster, and can run maybe in read-only mode the APIs on your production microservices. Well, who's on call? The servers don't know, Kubernetes doesn't know. Your microservices certainly don't know. Even LDAP doesn't know, right, or your identity provider. Really, who's on call is information that's usually stored in some third party service like PagerDuty. So what you can do with OPPA is extract the data out of PagerDuty, inject it as JSON into OPPA, and now you know exactly who's on call. So that's a pretty important feature again that a lot of people leverage in a number of different ways. Finally, the policy decisions that OPPA makes. Certainly, we've talked about the problem of security and authorization as a part of that, and how important that is, and that's certainly how a lot of people use OPPA. But OPPA was designed to be a general-purpose policy engine, where policy is sort of a generalization of authorization. And so because of that, OPPA allows you to return policy decisions that are more than simply allowed an eye or true-false. It allows you to return really any kind of JSON data back to that service. So maybe you want to return, maybe the policy you write for in a microservice world, return is not just allowed an eye, but maybe in addition you need to return the list of new custom headers that need to be injected into the API request before that request is handed over to the actual back-end service that's handling the request. Or maybe you're making a rate-limiting decision. Then maybe you need to return a number. Or maybe you're writing a policy that describes where to deploy an application. Well, then you need to return an entire array of different cluster names. So in all those cases, the common thread there is that OPPA is simply making a decision that looks like a JSON object. So at the end of the day, OPPA was designed to help you decouple these policy decisions so you're no longer hard coding them into the service, and it was built in a flexible enough way that you can use OPPA for really any kind of authorization decision that you want. Let's look at a couple of examples. So here was the picture we saw a little bit. Let's zoom in on some of these. So first let's look at microservice authorization. The idea here is let's imagine, and the common question we get is, well, look, does that last slide mean that I have to change my microservice in order to delegate my policy decisions to OPPA? The answer is no. What we often see people do is use a network proxy in order to actually do the integration with OPPA. So here we have our network proxy. There are a whole bunch of choices here. We've got a few shown. And the idea is that any, and the way this works is that you can figure that network proxy to send and receive all the network traffic to and from that microservice, hence the name network proxy. The idea is that the request comes into the network proxy. You do the integration with OPPA with that network proxy. So then the proxy sends the request over to OPPA. OPPA makes a decision, returns that back to the proxy. If OPPA rejects the request, then the proxy is going to drop it or return a 403. Otherwise, the proxy will send that request in step three over to the microservice. The microservice does what it always does, returns a response. And then typically what we see is that that response gets returned back to the caller. So this is a very simple example. The nice thing here is you don't actually need to modify your microservice. Still some people choose to. And sometimes there are cases where that's the right thing to do. But what you can also do is add a network proxy and do the integration between OPPA and that network proxy. And if you do that, then what you're sort of guaranteed is that this is a pretty native integration. There's no way to circumvent using OPPA to make authorization decisions because you sort of enforced that policy at the network level. Let's look at a second example here, Kubernetes. So this is, again, remember one of the more popular integrations with OPPA. Here the idea is that any time a user is trying to create a new pod, ingress, or any other resource on Kubernetes, the Kubernetes API server is designed to send that request through a whole number of several phases. Here we just show three of them. One of which is authentication, like who is this user. The next is what Kubernetes calls authorization. So that would be like RBAC. It's basically like can this user, if they're trying to create a pod, can this user create a pod at all? And then there it goes to admission. So admission control is where we typically see OPPA hooked up. You can hook OPPA up at the authorization stages as well. But at the admission stage, you get the ability to make a decision as to not just whether this user can create a pod at all, but rather you get to make a decision, can the user create this pod with these containers, with these configuration settings, with these memory and CPU limits, et cetera, et cetera. So at the admission control stage, you get full access to the full 50, 100, 200 lines of JSON or YAML that define the configuration for that resource. Again, just like the last one, this is a native integration. So there's no way to circumvent these protections. The Kubernetes folks did a great job of ensuring that these different functions within the API server are in fact, pluggable, and that functionality is what we're using there to hook up OPPA. This use case has become so popular that it spawned its own sub-project within OPPA called Gatekeeper. Definitely recommend you check it out if you're interested in Kubernetes. A third example here is looking at a CI CD pipeline. So we all know how to set up CI CD pipelines, and hopefully by now it's pretty clear that it would be fairly straightforward to go ahead and set up the CI CD pipeline with a job in one stage of that pipeline that goes ahead and runs OPPA policies against whatever files actually somebody might be trying to check into a repository. And so this is again another use case, and it is in some ways different, but in some ways similar to the other use cases. You might be checking files into the repo that represent the Kubernetes resources that are about to be deployed on the Kubernetes, or maybe it's just a bunch of Python I&I files that you're wanting to check. But in any case, this particular use case has become so popular that there is a dedicated sub-project called ConfTest. I definitely recommend you check it out if you're interested in running policies over files on a file system. So up to now, we've really talked about how you can use OPPA to provide a pervasive way of enforcing authorization, because OPPA works with all the different kinds of software in a cloud native ecosystem. What we've also talked a little bit about is how if you use OPPA, then you also have the ability to write those sophisticated policies. So we've checked off the boxes on those two requirements that we talked about at the beginning of the session. Now, the other things that you want to think about doing is sure, we've decoupled policy from the underlying piece of software so that now you put your policies in OPPA and those policies get enforced, nevertheless, by that service. But what you also want to ensure is that as that service evolves, so too do the policies evolve with it. That is, what you want is for the software development lifecycle and the policy development lifecycle to overlap to coincide. And I think part of the observation here is that that requirements gathering phase, there's a policy writing phase, there's a CI phase, deployment, monitoring, logging, just like all those apply to software, same things apply to policy writing. And so the other thing that OPPA does is it provides a number of tools that you can use to help you work through and provide that policy development lifecycle. For sake of time, I won't go through these in detail here, but what I will call out is that here around the tooling, there is a tool for handling all the usual phases of software development. So when you're writing policy, OPPA provides a REPL, there's a playground, you'll see that in a little bit, there's a VS code and IntelliJ plugins, those all just help you write policy more effectively. There's a build, evaluate, compile step, there's testing, unit testing, just like you write tests for code, you should be writing tests for policy as well. You can check coverage, you can format and do dependency analysis, there's debugging too. So if you want to trace a decision and understand why it was made the way it was, you can do that. You can do profile and benchmark too if you're interested in performance. So here is the OPPA playground, you can get to it by just going to the Open Policy Agent website, openpolicyagent.org, and then there's a playground linked to it. So the idea behind any one of these policies as we're writing them is pretty straightforward. The idea here is that we're just going to write some rules, and those rules could look like round and eye rules. I'll show you one that's a little bit more sophisticated than that. Here what we're going to look at is writing a policy for a Kubernetes use case. So the idea is that the input that the Kubernetes API server hands over to OPPA is shown over here on the right-hand panel. And the idea is that it's just a chunk of JSON. So here importantly, this happens to be a pod, someone is trying to create a new pod, and that it has one container, and the image here is nginx, and it's got some volume mounts and so on. So let's suppose that we're going to write a policy with OPPA that goes ahead and decides whether or not this new input is safe or not to deploy into the cluster. The policy we're going to write says that all images must come from a trusted registry. To do that, we're going to write a deny rule, and we're going to deny, the deny rule is going to look like this, input.request.kind.kind equals pod. All right, so here the first thing we're going to do is actually check what type of resource the user is requesting, and I've done this a couple of times obviously, so I know that it's input.request.kind.kind, and I'm going to check if that's a pod. And then the next thing I'm going to do is, well what I want to do is effectively iterate over all the containers. So I need to figure out what the path is to that input.request, and then here we're going to go into object.object.and it's going to be spec after that, spec.containers, spec.containers, and then I know that I need to iterate, so I'm going to say, well this is an array, containers is an array, so I'm going to say, well there's some I, and then within the containers array here I'm going to go ahead and grab the image name. And so image, so image colon equals that. Okay, so colon equals here's assignment, and now the last thing I want to do is check whether or not this image name starts with hui.com. Let's say that hui.com is our trusted registry. Alright, and okay, so that seems pretty good, hopefully I didn't make any typos here. One of the things that I always do whenever I'm writing policies, I'll go ahead and evaluate this. I'll go ahead and once I've written a rule, I'll go ahead and evaluate it right here. And so what we see here is that deny evaluates to true. Okay, so that's exactly what we wanted here, right? Because the image here is an nginx, and nginx comes from the public Docker hub, I didn't want that, I wanted to ensure that all my images come from my trusted registry. Now one of the things that you can do, if you remember, is that you can write policies that are not simply allows and denies, but that are more sophisticated JSON objects. So I could do a couple of different things here. The standard way though that we see people doing this is to return error messages, and actually explain to the user why it is they're not getting, why it is that they're the request was rejected. So say image should say pod contains an image with unsafe registry v, and then we do, let's say image. Alright, so every time we find an image that comes from an unsafe registry, we're going to return an error message to the user. So now let's see if I had any errors, nope. Okay, so this time, again, we did an evaluation and this time, syntactically, what this is doing is it's saying add a message to the deny set only when these conditions hold. So what you see over here on the right-hand side when we do the evaluation is it denies a signed set or here an array of error messages. So there's just one. Okay, so that's a quick look at writing some policy with OPA. Obviously, you need to understand the schema of the input, but then once you understand that, and then you understand a bit of the rego syntax and you can go ahead and write whatever policies you want. That does it for me. Thank you so much for giving me some of your time. Hopefully you found this helpful and definitely feel free to reach out. If you have any questions or comments, we definitely have an active Slack channel for Open Policy Agent, so please feel free to reach out.