Transcript
This transcript was autogenerated. To make changes, submit a PR.
My name is Gabriel, and although
this session recording from home
remotely, I like to travel. This is one of my hobbies.
And usually when I travel I'm
using two documents. First one is on the left
side, my passport, and the right
one is the flight ticket.
And let's talk about the difference between those documents because
they are probably both allow me
to get on a plane and travel. But I have different
roles. The passport is a form
of my identity. I declare, I verify my identity
with a passport, but I cannot go on a flight, only with
a passport. I need to authorize myself
to a flight. A passport also, for example, is something that
I'm renewing once for ten years.
But flight ticket is something that I get
again and again for every flight that I'm going to.
The details on both documents are different.
One is the details that verify my identity,
like a biometric image picture or
my birth date. The other is the particular
details that allow me to go on the flight, like the seat number
and the passport is something I'm
using. Once in the airport, the flight ticket is
something that I'm using again and again. I'm showing it in the check in,
and then I'm showing when I check the luggage, then in the security,
then in the gate. And if someone start fighting me on
my seat, I'm taking my flight ticket out and tell
him hey, one a is my seat,
right? So others are many difference between passport
and flight ticket. But what is matter for Javascript or
software? It's matter because there are two terms in
access control that usually confuse
developer authentication and authorization.
Access control is a term, is a general term to describe
who allowed access to our application. Every application
has some level of access. It could be node JS
applications. And this is what we are going to talk about today.
Authentication is the step where our users verify
their identity, right? Same as they do in password,
in passport, yeah, they do it with password, but password is
not that safe. So there are features like biometrics,
multifactor authentication. But authentication
happened once and then we maintained session, but authorization,
the determination of the user permitio to
know if a particular user that already authenticate themselves is authorized
to do an action in our software. It is
something that happened again and again require
permitio model, right. We need to understand
what a user can or cannot do. It has evolved
much more data than authentication session include.
It's not something to revoke because it exists or
valid only for one time that we are checking for
permitio. So the difference between authentication
and authorization are similar to passport and flight ticket.
But authentication has also many advanced features.
It's not as simple as it seems like multifactor authentication.
We want to make sure that the user that just put a password is the
real user. So we are trying like maybe sms or authentication
app. We want also to let user use their
own identity. So we give them like social,
they log in with their Google account or GitHub.
We might want to let them use biometric or passwordless
mechanism to authenticate. We also need to manage all
our users, right? We need some identity provider
to manage identities. We also need to manage the
sessions, manage the registration of the user, all the flows,
the login flows, the sign up flow. We need to verify
account. And there are many, many features in
authentication. So authentication is not that
simple, right? But for developers it's simple because
there are many, many software as a service providers
that let you feel all that advanced feature
as a service. To implement, for example clerk into
your Nodejs or JavaScript app, you need only three
lines of code, four lines of code, and then all the advanced
authentication feature is implemented into your app.
But in authorization, which is not simple
than authentication, and we'll go through it and you'll see
that authorization also require some features,
right? We still work really hard.
It's still the old way of trying to code everything
in authorization ourselves. Burn a lot
of time my name is Gabriel and today
I'm going to talk about how can we do better
authorization in Javascript application short
about intro about myself I am in
software development for many years, JavaScript considered
as my others language. Every time that I just need
to sketch something on a paper, I'm doing it in
JavaScript. But I'm also a big
fan of front end and security and this is
how I got to everything related to access control in applications.
And now I'm leading the developer relation in permitio.
Permit IO is a startup that do authorization as a
service and today I'm going to share with
you a lot of learns that I learned along my career about
having better authorization on application.
But before we dive into authorization, let's try
to understand what happened maybe
started ten years ago that let developers
create simplest authentication using providers like we
saw like OSO or clerk. Two innovations
happened a couple of years ago. The first is auth.
OAuth is a protocol that allowed to
decouple the authentication server from
any kind of application. Here we can see in the center API,
gateway and also API services. Could be any
kind of language or technology for any of them.
We can once connect the authentication server
which in their turn return a token.
The base of OAuth is creating token based authentication
instead of managing sessions. And every time that we
need to know if a user authenticated, we are calling
a session directory and check if session exists. We are using
token token that we can validate and we can do it in a decentralized
way. So every application that needs to do
authentication can call the auth server to make
authentication flow with the user and then by use
token verify the identity of the user.
Another thing that came to public
lately, or maybe it's not lately anymore, it's like I think already
ten years, is JWT Json web token.
This is a format of token that specially work well in
web that can be verified without calling the
authentication server. So in the architecture we see here,
the user is made the authentication against the OAuth server
and then each API, microservice or application
can probably verify their authentication
with this architecture service provider like cloud service
provider. Or if you just develop a platform, you can do it
yourself. You can create auth server
that creating centralized authentication and decentralized
authentication in one and a secure way using JWt.
That's in general the way that all the authentication
service provider works. But how is go is authorization.
So the most simple form of authorization is the if statement
that we see here, right? We can ask if a user
is admin and only then allow them to delete something.
It's a fan code because the user delete themselves. We don't
want to allow admin delete themselves, but that's a demonstration.
How can we code authorization? Probably this
way is not the smartest way to do. First, every time
we are changing something, we need to change the code and
we involve the code in the logic that we need to do so.
It's also hard to read. So a more clean way to code authorization
is using middleware. Here we can see an example of
express middleware. It's a piece of JavaScript code
for delete endpoint of a user.
And instead of mixing the business logic of checking if
a user is admin with the
function itself, we are taking it out into a middleware. And then
we check if a user can remove something.
This is better than just ask if a user is admin.
But it's not easy because for example we
need more permitio models than just asking for
a user role. We can see here in example that we
need more granular level of permitio
to make sure a user is allowed to do some action.
This level of granularity, this level of granularity
can change. It could be like role, as we saw
in the middle of role required, but can also be based on
attributes of a user or maybe data from a different
service. Well, data from a different
service is also not that good, because sometimes it's not part of
the request itself. So middlewares,
as we can see in the code here in the right hand side, we are
trying to know what is the tier of the user.
This tier of the user is not something which is part of the
request itself. It's something that we need to go to ask
somewhere in the middle of the business logic.
So no matter how we will make our
middleware granular, sometimes we will have a
need where we need to do authorization decision in
the middle of the code, right? I think we already
saw enough code to understand that the way we
are coding authorization, it may be work, but it's
not that clean. Because the requirement of authorization
with permissions model with roles, or maybe more
granular permitio models and endpoint,
and maybe more granular level of permission enforcement on
endpoint. It's getting complex and complex.
For example, we can see that users getting
decisions that need only for partial
authorization and not for the whole scope of the function.
And this code is getting dirtier and dirtier.
But clean code is not the only problem of the way we are doing authorization
today. Sometimes we need a different environment
with different permitio, right? We do continuous integration,
we want to create, test, and we want maybe
the admin in the staging environment to be allowed to do
what super admin allowed in production.
How can we separate these policies,
these permissions between environments? It's not
only staging in production, it could be different
tenant environment, it could be different customer
environment. So as you can see, having policy
or having enforcement or having decision,
mostly, most accurately having decision of authorization in
the code. It's something that can be complex when your
application getting complex. And also the real fact is
we love javascript, but sometimes we need to do a different app,
we need to do a data application. So we are using Python
and we want to keep the same logic across our applications,
across our stack. And also let's
go a bit back to the clean code thing.
Authorization decision is something that could cost performance.
And when we need to debug those performance, when we
need to get the audit of what the authorization
decision does, it's something that can get
a hell of decision in case we are doing it
in just coding the decision logic as part of
our application. Right? Oso, the problem
of creating permitio and authorization as part of
application is clear. Let's see from the
examples we saw what we can do to make sure that
authorization service. Let's say that we are now creating an authorization
microservice, a dedicated one. What it has to be
first, and for the last thing, we want it to be declarative.
The way that we are coding complex imperative code
to get authorization decision. It's something that hard
to read and also hard to manage and audit.
We want it also to be generic,
right? We don't want it to be in a level of roles
or attributes. We want it to have to
support any kind of permitio models that we want.
Permitio decision could be simple as if is admin, but could
be complex as if is admin and a paid user
and own the document. We want it also
to be unified in one place, so we can run it in multiple
application, we can manage it for multiple environments. We want
it to be agnostic to the language that we are using. And because we
want it to be agnostic, we want it to decoupled from the code. So every
time we need to change, every time a PM come and wants us to change
authorization decision, we don't
want to change the application code, we want to decouple it.
And we also want it to be easy to audit, because access control is
something that can create very high level security
vulnerabilities. So we really want it to be easy to audit
when we are getting maybe wrong decision.
So what is the way to build this authorization microservice?
So let's start with a simple five steps.
Maybe not simple, but I'll try to make it simple.
The first is model. Before we are doing
authorization, we need to model the permission model.
What do you mean? What does that mean?
Authentication is something that can create out there.
We decide who are giving us identities, we decide
how do we authenticate users. But authorization is something
that driven by our application needs,
and hence we need to model what our application need.
What is the easiest way to model is think about
three principle. In our application, we'll always have three
principles when we want to get a decision, a user
or other principle, maybe a service, an action,
what they want to do, and a resource, the resource that they want to do
on. So for example, a permission decision is. Is a
monkey allowed to eat a banana? A monkey is the user,
eat is the action and banana is the resource. Yeah,
but in a real world it's. Of course if an admin
allowed to delete a document, or if an admin allowed
to delete a document created today or if an
admin allowed to delete a
document that they are not owning.
Right? Oso every authenticate authorization decision
is happening with these three principles so that help us to
model all our authorization rules.
Second point is the level of authorization application
stack built usually from let's say UI
or gateway, right the way that we are existing to
the user, the application logic and the
data itself, the database in application logic,
which is the main part of our code. We want to do enforcement. We want
to have a simple function that get these three principles,
user action and resource and return. True or false. We don't
want to do the decision in the application logic.
We want us to do the logic of the decision in a different service
and in the application itself only enforce the permission.
We also sometimes need to do feature toggling in the UI
or the gateway to make sure that users are
not getting the resources or not allowing to do
or to see what they need to see. And we
also sometimes want on the data itself do data filtering. So to not return
the user something that they are not supposed to get.
So we know what is the principles, how this enforcement
function signature should look like accepting these three principles
and returning either data filtering or true or false result.
And then we need to design a permission model. Why it is important
to design a permitio model because that help us to
create a good picture of how the
authorization policies looks in our application.
Let's talk about four common permission model,
why and when and where use each. First is the
access control list. Access control list is a very old one. I consider
it as an end of life model. It's usually used in system
and firewalls and switches, et cetera. It's maintain
a list, a long list of users and
the list, let's say title is an action and a resource or only
a resource. And the users that appear in the list are actually
the one that allowed to do something. In this kind
of permission model it's hard to scale because we need to maintain all those lists
and we need to use data that is saved in
the list. If for example, we want to get the role of a
user, or we want to get a decision based on a
user that assigning to some list,
but it's not appearing the list is in a different system. It's really
hard. So ACL is not fit for modern application.
RbaC role based access control is like the generic
name for authorization. Why? Because it's
very simple to assign users roles. We can take a user and tell
this user is admin, this user is a moderator
and then we can easily assign permissions for
particular resources and actions of these roles,
right? So if we want to give a nice user experience for
our application admin, we will use RBaC because
then we can extend hey, if you want your users
to be able to do a particular action on a resource,
just assign them the role. While arbac
is easy to define and also to use and audit, because every decision
we make we can easily get here, hey this got this decision because they
are role. It's hard to inspect into resource, right?
If for example, we want to allow users to do something only
on their own resources,
we don't have the granular level to do that. Because as we can see
here, the granularity of RBAC is only in the resource
level, not in the resource instance level.
Also, others is no resource. Also there is scalability
is limited because if we need, for example to have more
roles or more granular way on looking on users,
it's getting hard. IBAc on the other hand,
is the most complex way to model policies
yet. It's important to understand that it might be complex,
but it still gives our users a very easy way
to define policy rules. If you have a way to define policy rules,
then you can take attribute of the users and the
resource, right? In software, everything probably has attributes,
user as attributes like name, role, job description,
address, city, whatever it is,
resources as attributes. And then we can create policy
roles that consider set of attributes together with
an action and decide if a user is allowed
or not allowed to do something. While ABAC is the
most granular policy level, it's hard when
we, for example, need relationships. So for example, if we
think about Google Drive, Google Drive has account
in account, there are folders, in folders, there are documents.
A document belong to a user's not user,
not because an attribute, a document because it's a
part of a folder. So we need to find a way
to propagate or derive policy rules
based on relationships in application. So to solve
this level of granularity in resource instances
and derivation of permissions, there is a model
called relationship back substance based access control
reback where we can create a graph
database of connections between things and make
our policy rules based on this graph and say
if there is a tuple a relationship between
resources, then we can take the granular level
of resource instances and create policy rule
based on them. So now we
understand the first point model,
our application permission. We have in mind the
model that we need. Maybe it's RBaC maybe RBaC with RebaC,
maybe RBaC with ABAC. We understand who are
our users, what are our resources and what are the policy
rules that we need to create. We draw it nicely on a whiteboard
and we want to author it, right? So we want to have like
authorization microservice with all these policy rules.
So one way is of course do it imperative as we did
before, like create code that do it. But then
it means we are coupling ourselves again to application
code. So how about the idea of
create contracts for creating policies?
What if for creating, let's say Arbac policies or arbac
rules. We have a new language that dedicated for
that. And this is one of the things that I want to introduce
you today. Policy languages. Policy languages is
a trend that coming by for the last years. Every couple
of months you see a new policy language release. This language
is not a real programming language, it's more a configuration language.
It's not a language usual. It's not a language that you need to learn from
scratch, but to know how to use that.
There are also policy languages that based on Yaml or Json.
So you donts need to learn a new syntax. Today we are going to
talk about, I'm going to mention three policy language.
The first one is the most famous one,
open policy agent. Open policy agent is a decision engine.
Like think about a compiler that we can run
a policy program and get a decision if it's allowed or not.
Open policy agent has a language called Oprigo
where you can declare your own policies and then enforce
them by the decision that happened in the open policy agent.
There is also a language called Cedar. Cedar is a
language released by AWS as an open source. This is the language
that we are going to demonstrate today. And there are type
of languages for Openfga, for Google Zanzibar.
Google Zanzibar will not expand on it too much,
but we mentioned relationship based access control.
And sometimes relationship based access control require much
more sophisticated policy engines
for that. So if you need a specific case of relationship
based access control, let's say that you have tons of resources
and instances and millions of users, you might need this
language. But let's dive for the cedar language.
Here we can see, and sorry it looks small in the
left side here we can see three examples of
rules that declared in cedar language. Let's focus
on the one in the middle so
we can see that we are declaring a rule of permit. Permit means
a user is allowed to do something. And as you can see, one of
the things in cedar is that we are declaring policy
with the three principles of authorization, the user,
the action, and the resource. So we say here like
an ABAC policy, that said, a user is permitted to
do update, list, create task, update task,
or delete task on a resource, any resource
or any users only when this user is one
of the resource editors, right? So see how in
what a clean way we can describe a
policy rule. Let's see on the left hand
side, which is a most sophisticated ABAC rule.
And we say if a resource as owner and
the owner is a principal, then we allowed something in
the left hand side. In the left hand side in the small letters
is an RBAC policy. Like we say, only a user with the role
admin is allowed to do something. Now we can
have one policy repository where we create
all our authorization rules, okay?
And all our application use the same authorization rule.
All what we need to do is call the cedar agent that loaded
with these rules and make an authorization
decisions or authorization enforcement based on it.
Another option of course is to have your authorization service
with some UI. Here is what we built in permitio to
manage policies and then you can just let your
product manager or whatever it is to edit the policies
without even knows the code. We are using our
UI to create and configure policies and
then all these policies
is created or generated as policy as code. And then you
can use the UI for what? Easy to config
and use the code for the most invest use cases.
So we understand how we author the policies,
but how we check that these policies is what we mean. So there
are agents. Agent is like a decision maker
where we are passing three principles,
a particular user, an action description and a
resource, or maybe attributes of a resource, and this agent
returning a result. True or false allowed or not allowed.
Right? So once we have an agent,
we're starting to form a complete authorization
service. We have a policy repository
where we author all the policy. Then we have the
agent where we can get the queries. And then what we
left is to enforce, right? So we can enforce it by
calling the policy agent. Here we can see
that we are calling the Cedar agent in an HTTP interface.
As you can see, we are passing the principal action and a
resource from our request to the cedar agent.
And the context is also something that we can use to expand
the data that we are passing with the policy
enforcement request. And in this case or
with this example, we are not writing the
decision logic in our application anymore. We are keeping
our application logic only in enforcing permitio
and think about this is great. We actually decouple the policy from
the code. We have a unified place for our policy.
We can make it generic to the language because we can do
it in an HTTP API or some other RPC
forms. It's easy to audit because all the decision
happen in this agent. So if we need to audit something
we're just going and see what happened in the decision and
it's declarative. So if someone want to understand what
happened or how our policy configured,
it's easy to see just by looking at the code.
Great right? So we are enforcing policy. But not only that,
there is a nice framework called Castle for Front end
where we can load all our authorization,
all our policy decision that we need for our front
end from our cedar agent and then cedar
agent and then we can in the front end feature toggle
everything we need when we
need to audit. It's easy to see because all
the cedar agents and all the policy engine are
probably auditing what happened when we got the
authorization decision. So we understand the framework
and let's see in practical how is that tools like.
So let's say that we want to have now authorization microservice
that we can easily use. So authorization include control
plane as we describe where we author the policy and
a data plane where we get the decision and the application when
we do the enforcement. And on the other side the data sources.
We need to enrich our policy with data
to make better decision. Right? Policy decision maybe
need data from external sources.
How we do all that not from scratch.
We can use OPal. Opal is an open source tool that
lets you with one docker compose file spin up
in minutes everything we saw before.
Create a complete microservice that
can help you with do authorization in
all your application using one interface
with audit. Here you can see the QR code. The QR code will lead
you to a GitHub repository of Opal
that actually you can see others. Everything related to OPA
and how to spin it up. We'll also do a demo soon. And Opal,
as you can see here is a complete authorization service
that you need for all your application. OPA include Opal client
and Opal server. The server is probably the policy
store. It's actually connected to a git repository where
you store all your policy. You can also separate it to environment and
maybe even application. Also you configure. You can configure
in Opal what is the sources that your data need.
And then the Opal client is running
as a sidecar in your application or for
multiple applications. And those applications can
get decisions that based on the logic that exists
in the central part. If you see at Hopeal, this is
actually giving you the same as JWT and OOH
server bring to authentication. You have one server
to configure all the policy needed like OSO server.
And you have instead of making the
calls to the configuration themselves, you are calling the
sidecar like verifying JWT that's sitting close to
the application, maybe even as a binary and making
very fast decision.
Now it's a good time for a demo.
So here is the code that I want to discuss
about. First let's talk about the Opal Docker
compose. This repository exists on GitHub.
I'll share a link later. And actually
the first part of this repository is Docker compose
that has Opal and also some
infrastructure that we create running it locally.
Let's start from the bottom where we are loading opal. So we are loading
the OPA server image. Remember opal server is actually
where we are storing the policy and configuring the data.
So as you can see here, I'm saying the policies sit in
this git repository. This git repository is actually a
mock repository that I'm launching in another
docker container here. And I also say I need data
from external resource. This external resource can be let's say your
CRM system or
CRM service. Also here I'm just mocking
it from an NginX server. But I'm configuring
the policy configuration and then I spin up
the Opal client, the decision agent. I actually wrap decider
agent with the Opal client. The nice thing is that in production
you can scale this OPA client again and again for every
application that you need and you will always get the
same decision. Let's see now how our applications
look like. So I have here a simple nodejs application.
This application has route for like let's say a blog.
There is article we can create, article we can update,
we can get it, we can delete it. And we have
a middleware. But a difference than a middleware that we
saw in the beginning that can dirt our code. This middleware
actually always do the same logic. No matter how
complex is the decision, we need to do it only enforce
it by this tree principle, right? So we can see
here we call the opal client that actually is the
cedar agent. And even if we for example need
to do this authorization in the middle of the code, we are not
dirting the business logic, we are just doing the
same authorization function again and again.
So let's see how it is work. So I already run
opal here. Opal is getting the git. As you can
see here is pulling the policy git from
our repository and let's see how our rules
looks like. So here is the folder of the policy.
First we have an admin policy. The admin policy
said that every user with the role admin actually
can do anything on the system. The user
policy said that a user can do only get
right? Think about it like anonymous user. So a
user with no role can do only
the get action writer can do more
actions than that. For example, a writer
can do post and put.
Of course it will also derive the user attribute
and writer can also post and put
permitio. Now I already ran this node
JS application. Let's try to do something
in a user.
So I'm trying here to post an article as a user.
And as you can imagine, I'll not be able to do that. Access denied.
But what happened if I'll try to do the same one as a
writer? Okay, so here
I'm doing as a writer. I can see that the article created,
right? So all the decision happened not in
the application, happened as a result of the policy.
Cedar of the cedar policy. The nice thing is
that if I want to change my policy, so for example,
I want now to do ABAC, okay, I want
the writer, I want them to be allowed to write
article and publish it immediately.
Only if, let's say they are senior in the system.
If they have lot of karma. If they don't have this
karma, I don't want them to
be allowed to publish published articles.
So in a traditional way, I should
go to my application and write
this policy logic in a policy as code way.
I'm just having to change the policy
here. So as you can see, I limit the permission.
I limit the permission to do post and put of
writers into writers with more than 1000
karma. But that's valid only
if the article is published. If the article is not published,
I'm still allowed them to do it. Okay,
where is karma getting from? So if you remember,
we have here this data json. This data json.
Think about it as an identity provider,
which is something also that we are connecting to the authorization microservice.
So the admin will be allowed to do everything. It's not part
of the ABAC, but the writer, the senior writer
will be able to do the publish. True, because he had
more karma than the writer that has only 800
karma now we change the writer cedar
file and what we need to do is just
commit this file into the git repository,
right? Let's do this way git commit
minus a minus m abac
policy. Now I committed
the file and as you can see here,
as soon the opal will get an update
that a new change is happened to
the policies code soon. We can see
that it takes a bit of time to update sometimes,
but it's still really fast because you don't need all the software development
CI CD to make it. And as you can see here it
got a notification of a new policy that updated.
If I'll try now to do this call
in the same of writer, what will happen? The access
will be denied because this writer has not
enough karma. But if I'll do the same but
with the senior writer user
the article will create it. It's amazing. I haven't
changed any kind of user data or any
kind of application code and the authorization microservice
did everything for me. And also remember there are not much
than just implement like the Opal
SDK application. And one of the
most amazing thing I also have here a
python application that actually consume
the exact same authorization service.
So we can take one authorization microservice and
ship everything into all of
our application. Isn't that amazing?
So Opal probably can help you reach
in open source everything you have with authentication
provider for authorization using policy
as code and in the most advanced way you can see
permitio. In the other end is the commercial
tool on top of Opal. I'll not
stick on it too because as I say it's a commercial tool but I'm really
inviting you to try it. It has also
the option to edit all the policies in the UI and
scale it and store it and much more feature than
Opal offer. But to start Opal is definitely enough
and I really want to ask you to give
a star for Opal. OPal is an open source OSO you can
support also in contribution, but the first basic support for
open source is of course giving it a start,
giving our more power to continue maintain it to continue
the way for a better access control
for all the application out there. Thank you
very much.