 Hello everybody and welcome to this video. My name is Lucas and I am a senior engineer at Upstart. Today I wanted to show you how EZ Octa makes it to enable developer self-service OAuth application security utilizing their authorization servers as well as their Terraform provider. A couple weeks ago I posted an article that outlines the benefits of OAuth and why your company should consider using it. To implement OAuth with Octa you need a couple things. You need an OAuth application, you need scopes for that application, access policies and access policy rules to name a few. Typically you'd request these from the IT team. It would take a couple days for them to receive and triage the request. Then they would have to get approval to do all those things via change management control. This process could take a couple days if not a couple weeks. With the Terraform module that's mentioned in that article on medium this can be done in the amount of time it takes for a person to review a pull request in an Octa Terraform repository as this module creates a client side application, a server side application, the app policy, the app policy rule and the scopes for the application. Also to note if you want to change things or you need to add scopes to your application you don't have to submit another IT request if you have this Terraform module all you have to do is go and add the scope to the module declaration in your Octa Terraform repository making iterations so much quicker and everyone's a lot more happy. So with that being said let's jump into the code and see how it works. All right so what we see here is a simple flask app with a couple different routes. There's the root route and then a mix cookie route. So let's get this app running. See okay it's running. Let's go ahead and click that. This app is dope. Okay so we are running successfully. Now we see that this route slash mix cookie accepts a post request and it will mix our cookie dough. So let's go ahead and do that. So we'll do curl dash x post and then grab the URL which is this and then add the route to it. Great message cookie dough mix status code 204 which is what we'd expect here. All is fine and well and now we have mixed cookie dough. So this is great. However what if we don't want anyone to be able to mix the cookie dough. A funny example let's say you're running this easy bake oven server on your internal network for some reason and someone hacks your Roomba which is also on the internal network. Then somebody that hacks into your Roomba might be able to talk to your easy bake oven server and mix the cookie dough. You don't want that to happen. So what we want is to add a verification or protection to this route and how we can accomplish this is via OAuth and what that'll look like. So let's start writing some code here. We'll say okay proceed is going to be a variable. And let's say it's going to call a verify method with the request made to the server and maybe the scope that we need to be able to do this. Great. So we have this function and the variable and then this variable will probably be a Boolean like if proceed is allowed or if proceed is true then go ahead and return this message saying that the cookie dough is mixed. Else return message error and status code 500 or something like that. Not 5000. That would be real bad. Okay great. So we have this workflow now that we're going to call some function verify with the request details and the scope that's needed to perform this request. So let's go ahead and make this verify function and it's going to take request context as well as the scope needed to verify this call. So what we're going to do is we're going to get the request headers which would contain our authorization token from the OAuth server and we want to get that token and do an introspection on it. So octa offers the introspection endpoint in their OAuth API and that will provide us details on things like whether or not the token is active the scopes provided for the token and things that will help us verify that their request is true. So I just wrote a couple lines of code. If the request headers if the authorization header does not exist we will return false and that will mean that the status code is equal to 500 the cookie dough did not get mixed. So now that we have this let's go ahead and restart our flask server and then let's go ahead and run this post request again. We'll get a 500 because this post request did not contain the authorization header. Great. So that works perfect. Now else because we want to be able to make a request here successfully with some criteria and this is basically saying now we know authorization exists because it's not none. We will do an introspect call which I will define soon and the token is going to be equal to request headers. Get authorization and then it's going to be a bearer token. So we're going to replace that in the string because when we request the authorization header it's going to say bearer space token value. So let me also put a t here so that makes sense. We're going to introspect this token and then if there is no response we are going to return false let's say so if we introspect the token and there's a error introspecting it we will return false and then if not we are going to say json is equal to r not json so we're going to get the json response of the introspection and we will return our json.get active is equal to true and scope in r underscore json.get scope. So a couple things that I want to explain here. When we introspect the token with octas introspection endpoint there is an active attribute in the response and there is also a scope attribute in the response. If we look at the documentation we see that active it indicates whether the token is active or not and then there's a space to limited list of scopes in the response. So what we want is we want the scope that we're going to pass into this verify function do.cookie.mix to exist in the list of scopes and we want the token to be true. So that will give us the confidence that this token is legit and it can mix the cookie. So we have to now define this introspect function. So this will handle the token introspection to octa and we will it will take a token as the argument and then we will do a try r is equal to request.post we're using the python request library for this and then there is a config file that has all the variables we need here and we're going to use the default auth server with this module and we will hit the introspection endpoint and then our headers are going to be content type and then we will do application as x s w w dash form dash url encoded so this all this stuff can be found in the api documentation and then the data that we're going to supply we're going to supply the client id of the application that's performing the introspection this is typically going to be hosted on your server versus client and then we will get the client secret of that as well all this is defined in the config file so I don't expose my secrets to the world and then the token is going to be the token that's passed in from the function argument we'll do r dot raise for status just to blow anything up if something doesn't work and then if it works we will return r and then we wrap this in a try block so we're going to accept quests dot exceptions dot request exception as e and then we will just print e return false so now that we look at this introspection function oops we'll see that we will return a response an HTTP response if there is no error introspecting the token otherwise we'll return false so that's what introspect is going to do and if we combine that with the verify function um we'll return false if not r otherwise we will return the value of the token being active and the scope being in the token so great now we have this hopefully now if we request the token with the correct scope we will be able to make a successful request however these scopes that you configure we need to configure on the octa side so this is where the terraform module piece comes in so let's hop over to that window all right now that we have our application set up to perform an oauth request and introspect let's go ahead and set up our octa resources using that terraform module so we're going to head to the module documentation um and we're going to look at an example usage of this module to see how we can use it so pretty simple um we'll just copy this and then we'll fill out the rest step by step and we'll kind of explain how exactly it's being used so we will go ahead and declare the module with the source great so we have our source just to get hub link to the repository with the 2.0.1 release and we'll just flip back and forth we are going to use the um documentation provided in the uh read me so let's go ahead and take a look so in this module there are a couple inputs access token lifetime minutes so this is going to be how long you want your tokens to be able to perform actions before you need to provide another uh it's not required and the default is five so we will leave that there five minutes is okay um client credentials we'll skip that and come back to that because we're going to handle that in a little bit label this is the name of your service so the name of the application is definitely required um and there is no default value so let's go ahead and add one pop back over to the window type label is equal to let's say easy bake oven great okay so we have our label um oh yeah it's type string so we know that next variable we have oauth config uh this has the oauth config for your application um and the default value is application web grant types client credentials authorization code response types or code uh we're going to stick with this um this is for the client credentials flow for server-to-server authentication and um i'm not going to mess with the different types of grants responses um that are available the uh can the spec for oauth gets pretty confusing in that aspect so we're going to keep it simple client credential flow server-to-server so we aren't going to include this um we'll just accept that that's the default value and move along uh policy description this sets the description for your app's access policy so let's go ahead and say that um pop back over to the window so we'll do policy description and i believe that was a string if it makes sense yep so the policy description this policy allows only my easy bake oven to or maybe not allows my computer to access my easy bake oven i don't know something like that great policy description set redirect uri is kind of the same thing as oauth config there are a couple dependencies combined between some of these values and the redirect uri whether or not it's necessary um we're just going to make it simple and keep the default value as google.com um as this doesn't have any uh client-facing redirects or anything but uh due to some nuances in octa's oauth implementation we have to include this so we'll just keep it as simple as google.com and then scopes so we see scope is a list of objects that have a name and a description so this is the default value just some dummy scopes so like service dot object dot read service dot object dot admin um we are going to provide our own list of scopes as a typical developer would so let's go ahead and pop over here and we'll do scopes is equal to and it was a list of objects such that the um objects contain name and description keys so if we remember um see if i could pop back over to our application uh we are expecting to be able to mix cookies we need the scope doe dot cookie dot mix so we're going to declare that scope in our application so let's go ahead and do name and this is going to be doe dot cookie dot mix and then it needs a description so we will put that oops and this will be grants the ability for caller to mix cookie doe great okay so we have that scope lovely now one last thing that we have to do uh if you remember a couple minutes ago i said we are going to handle client credentials a little different there is a great article that i found on dealing with secrets uh in terraform because currently uh secrets are stored in the terraform state file this is an aside and that state file is in plain text more or less so if you decide to use this there's a disclaimer you should definitely encrypt your data at rest for your terraform state file um typically uh an s3 backend we should have encryption on that bucket um so to to be as secure as possible otherwise there isn't a way yet in terraform to not store secrets in the state file um however that doesn't mean that we should declare them in plain text in our uh terraform configurations that's an even worse thing to do so how i handle it and how i've seen other people handle it is they use a secrets vault like aws secrets manager to store the secrets and then pull them in with variables uh and data sources and terraform to load them into uh whatever they need because this way we won't be sharing any values it's just going to be a variable so um how we do that or how i do it at least and this is uh just a little bit of knowledge of the aws terraform provider let me pop back over to the code window here we go we are going to declare a data source so aws secrets manager secret version and then we'll just call it creds and the secret id is going to be local dot secret so we're going to look up a secret based on a local value and we just call it secret so now we have to declare our locals um and secret id is going to just be called test secret so you'd have this configured on your end and secrets manager already um and then creds is equal to json decode data dot aws secrets manager secret version dot creds dot secret string so if you look into the provider documentation for secrets manager this is a viable way to pull in your secrets as a json object or i guess a map or just plain object in terraform's language um so we're going to decode the json into an object and store that as a local variable called creds oh hold on there's a bug we'll get rid of that so great now we pulled in the secret and then we decoded it into a variable called creds so now let's go ahead and get rid of that terminal um and declare our client credentials here it's equal to and then pop over the documentation to see what they expect they expect an object with client with these values client client id client client secret server client id and then server client secret um the client will be making the request for the token and the server will be doing the introspection that's why there's two different ones um so so now uh we will just do and this is kind of uh bulky so just hang with me um client client id is equal to and then we'll just um oh shoot okay client client secret and then we'll just change this to server and we'll do the same down here server client id and server secret okay now we need to figure out what these are so if you remember we stored that our secret json decoded as the creds so we'll do local dot creds and then we're going to index this way and i'm going to try to make this as quick as possible because this is a lot of typing a client dot client secret and then we want to do the same thing here but server client id and then server client secret okay cool so we have our client credentials that we provided to the module so now this module is declared this should provide us with scopes our applications and access policy rules access policies and the client id so lovely now we do some terraform stuff typically in a production environment this would be behind some type of cicd deploy pipeline but this is all local stuff for example so i'm just going to run some commands terraform init all right so we run terraform init great terraform repository has been initialized so we should be good to go let's run a terraform apply and we'll see what the output is i would expect like five things to be created here um so let's take a look at what the module is going to create for us make this a little bigger for everybody it's going to create our client side up great um easy bake up in client it's going to create our server side application for the introspection and then it's going to create the app policy for the authorization server and it's going to create the app policy rule for the authorization server and then it will create the scope that we needed it to create and awesome all that looks lovely we will enter a yes for terraform to apply okay perfect so those resources are created and we can head over to octa to see them created all right so here we are in octa we can see that our easy bake oven and our our client and our server applications are deployed and then the other pieces that we need are the api pieces so we'll go to the auth server we will edit it and we see the scope doh dot cookie dot mixes there great we go to access policies we have the easy bake oven policy and this is for the client and let's check out the easy bake oven policy rule it'll say okay um if this and it targets the easy bake oven client so if the client requests doh dot cookie dot mix we will grant the token for five minutes awesome perfect that's what we want so now that we have our octa configured with that terraform module we should be good to go back to our application and start making some requests to verify that it works so you don't see it but i have a config file with all those credentials which i would imagine you would since you're providing your own credentials to these applications so with that being said if you remember the last request i made to the server was a post request to mix cookies and we get a 500 error code so great we know that if we have no authorization header that we are going to get denied um now what we want to do uh i have a test request file um just for ease of use i'm just gonna have it pre-typed out and we are going to make a request to the server all this should work you could see we're doing doh dot cookie dot mix we're asking for that scope we have the client config client id which if you remember from our access policy rule if it requests this scope we are good to go through the client credentials flow it's gonna get that token and then it's going to make a request to the server with mix dot cookie um with the token as the authorization header it's a bear token and then we're going to print the response just to see that it works so if we run poetry run python three test requests we should see a 200 great okay cookie dough mixed lovely love a mixed cookie dough um with the 204 status code so now you're saying okay but that could just be because it only has authorization header how do i know that this bear token actually works um so what we can do is just take this out and then we can go ahead and make that same request and we will get a 500 great message error status code 500 so great we see that the token generation works and that we can send a valid token um but let's say we send an invalid token um let's change this to a stf 1234 courty let's see what happens then awesome invalid token we get a status code 500 error obviously if you have a more built out application you can kind of custom tailor your error messages or something to say invalid token but yeah so all the octa resources are set up ready to go for your application's authorization and it was just as simple as making a terraform apply um in your octa terraform repository now that we know this works let's go over what we saw today so a couple things we saw today that hopefully peaked your interest or would make you want to dive in more are that one um all this was done by one person uh you we didn't even have to log into octa to verify that that configuration was there if we have the terraform repository set up correctly we are good to go with just a pull request and merge it into main and since you provided your own credentials you probably had all of your own um access policy setup on those secrets so literally all you had to do that was left from developing your application was just getting all the octa resources set up how how much easier could that be we didn't have to wait for communications between teams the possibility of someone entering something in error and also we have that repository set up if you needed to add another scope it was as simple as adding the name in the description of the scope so if you wanted to add like maybe cookie dot bake um as a scope and a cookie dot and a cookie underscore bake route to your application um it's as simple as developing the application on your side and then making a four line code pull request adding that scope thanks for tuning in everybody i hope you enjoyed the content let me know if you have any questions or want to talk about this some more thanks