Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hello everyone and welcome to Con 42
JavaScript. 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.
The 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,
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 telling hey,
one a is my seat, right? So there are
many differences 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 J's 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 permissions
to know if a particular user that already authenticated themselves
is authorized to do an action in our software.
It is something that happened again and again
require permission 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
permissions. 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 multi factor authentication.
We want to make sure that the user that just put a password is the
real user. So we are trying maybe sms or
authentication app. We want also to let user
use their own identity. So we give them like sociology
login 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 signup flow.
We need to verify account and there are many, many features
in authentication. So authentication is not that
simple, right? But for developers is 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 node J's 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.
Batting authorization, which is not simple
than authentication. And we will 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, but 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 mother 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 permit IO. Permit IO is a startup that do
authorization as 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 applications.
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 Ozero.
Two innovations happened a couple of
years ago. The first is Oauth. 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 need to do authentication can call the OAuth
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 especially
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 are just develop a platform, you can do it
yourself. You can create all of 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 always go with 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 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 permission models than just
asking for a user role. We can see here in example that
we need more granular level of permissions
to make sure a user is allowed to do some action.
This level of granularity this level of
granularity, granularity can change. It could be like
role, as we saw in the middle of all 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 sometime is 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 permission 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 permissions, right? We do
continuous integration, we want to create tests 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 cutting
authorization decision. It's something that could cost performance.
And when we need to debug those performances,
when we need to get the audit of what the
authorization decision does, it's something that
can't get a hell of decision
in case we are doing it in just coding the decision
logic as part of our applications,
right? So the problem of creating
permissions 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 permission models that we want.
Permission decision could be simple as if his admin,
but could be complex as if his 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 decouple from the
code. So every time we need to change, every time a PM comment 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 then 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
needs. What is the easiest way to model is think
about three principles. 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 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 is of course, if an administrator 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.
So 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 applications
stack built usually from let's say UI
or gateway, right. The way that we are exposing 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
application itself only enforce the permission.
We also sometimes need to do feature toggling in the UI or
the gateway to make sure the user 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 permission
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 usually used in system and firewalls
and switches, etcetera. It's maintain a list,
long list of users.
And in 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 explain hey, if you want your users to be able
to do a particular action on a resource, just assign
them the role. While RBAC is easy to define and also to
use and audit because every decision we make we can easily get
see hey this got this decision because they enroll.
It's hard to inspect into resource, right? If for example
we want to allow users to do something only on their owned
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 there 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 is getting
harder. ABAc in 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 let give 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 has 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 is belong to a user, not user,
not because an attribute of 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 applications. So to solve this
level of granularity in resource instances
and derivation of permissions, there is a model called
relationship. By accessing some based access control rebuild
where we can create a graph database
of connections between thing 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 we
can the idea of create contracts
for creating policies, what if for creating,
let's say RBAC policies or RBAC 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
usually 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 don't 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 allowed or not. Open policy agent has a language
called Rigo 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 Cder. Cder 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 CDer
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.
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 9th thing in CDER
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 policy rule.
Let's see on the left hand side which is a most sophisticated ABAC
role. And we say if a resource as owner
and the owner is a principal, then we allowed something
in the last side, in the left hand side did a small letters
is a 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 CDR 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 permit IO
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 this policy is, 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 invested 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 the result. True or false allowed
or not allowed, right? So once
we have an agent, we starting to form
a complete authorization service. We have a
policy repository where we over 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 in a resource from our request
to the seeder 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 permission. 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 a 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 SIDAR agent
and then SiDAR agent and then we can in the front end
feature toggle the everything we
need when we need to audit.
It's easy to see because all the seeder 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 looks 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 described
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 can 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 there everything related
to Opal 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.
Opal 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 applications. 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 couple of
four multiple applications. And those applications can
get decisions that based on the logic that exists
in the central part. If you see it hopal, this is actually
giving you the same as JWT and OAuth server
bring to authentication. You have one server to
configure all the policy needed like oo server.
And you have instead of making the calls
to the configuration themselves, you are calling the sidecar like
verifying JWT that 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 to discuss about. First let's
talk about the Opal docker compose. This repository
is exist 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 opal 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 resources. This external resource
can be let's say your CRM system
or CRM service. Also here I'm just mocking it
from a Nginx server but I'm configuring
the policy configuration and then I spin
up the opal client, the decision agent. I actually wrap the
see their agent with the opal client. The nice thing is
that in production you can scale this opal 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 looks like. So I have your simple
node 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 different than
a middleware that we saw in the beginning that can dart 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 these three principles.
Right? So we can see, 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 dirtying 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 git,
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 more actions than that. For example,
a writer can do post and put.
Of course it will also derive the user attributes
and writer can also post and put permissions.
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 still doing
the 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,
the policy seeder of the cedar policy.
The nice thing is that if I want to change
my policy, so for example, I want now to do a bank,
okay, I want to the writer, I want them
to be allowed to write article and
publish it immediately. Only let's say they
are senior in the system. If they have like lot of
karma. If they don't have this karma,
I don't want them to do the opera
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 a limit the permission.
I limit the permission to do post and put of writers
into writers with more than thousand 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 this karma getting from? So if you remember we
have here this data JSON, this data JSON.
Think about it as a 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 m
a back policy. Now I committed
the file and as you can see here the
soon the opal will get an update
that a new change is happened to
the policy as 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 for 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 permit IO. 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 so
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 maintaining it, to continue
the way for a better access control for
all the applications out there. Thank you very much.