I want to create a fairly simple role-based access control system using Keycloak's authorizaion system. The system Keycloak is replacing allows us to create a "user", who is a member of one or more "groups". In this legacy system, a user is given "permission" to access each of about 250 "capabilities" either through group membership (where groups are assigned permissions) or a direct grant of a permission to the user.
I would like to map the legacy system to keycloak authorizations.
It should be simple for me to map each "capability" in the existing system to a keycloak resource and a set of keycloak scopes. For example, a "viewAccount" capability would obviously map to an "account" resource and a "view" scope; and "viewTransaction" maps to a "transaction" resource... but is it best practice to create just one "view" scope, and use it across multiple resources (account, transaction, etc)? Or should I create a "viewAccount" scope, a "viewTransaction" scope, etc?
Similarly, I'm a little confused about permissions. For each practical combination of resource and scope, is it usual practice to create a permission? If there are multiple permissions matching a given resource/scope, what does Keycloak do? I'm guessing that the intention of Keycloak is to allow me to configure a matrix of permissions against resources and scopes, so for example I could have permission to access "accounts" and permission for "view" scope, so therefore I would have permission to view accounts?
I ask because the result of all this seems to be that my old "viewAccount" capability ends up creating an "Account" resource, with "View" scope, and a "viewAccount" permission, which seems to get me back where I was. Which is fine, if it's correct.
Finally, obviously I need a set of policies that determine if viewAccount should be applied. But am I right that this means I need a policy for each of the legacy groups that a user could belong to? For example, if I have a "helpdesk" role, then I need a "helpdesk membership" policy, which I could then add to the "viewAccount" permission. Is this correct?
Thanks,
Mark
I know I'm 2+ years late but I figure I'd share what I know and hopefully alleviate some pain for future readers. Full transparency- I am by no means a Keycloak/OAuth/OIDC expert and what I know is mostly from reading the docs, books, good ol' YouTube and playing around with the tool.
This post will be comprised of two parts:
Keycloak 8.0.0
.Some terminology before we get started:
Resource-Based
permissions, you apply it directly to your resource Scoped-Based
permission, you apply it to your scope(s) or scope(s) and resource. is it best practice to create just one "view" scope, and use it across multiple resources (account, transaction, etc)? Or should I create a "viewAccount" scope, a "viewTransaction" scope, etc?
Scopes represent a set of rights at a protected resource. In your case, you have 2 resources: account
and transaction
, so I would lean towards the second approach.
In the long run, having a global view
scope associated with all your resources (e.g. account
, transaction
, customer
, settlement
...) makes authorization difficult to both manage and adapt to security requirement changes.
Here are a few examples that you can check out to get a feel for design
Do note though - I am not claiming that you shouldn't share scopes across resources. Matter of fact, Keycloak
allows this for resources with the same type
. You could for instance need both viewAccount
and viewTransaction
scope to read a transaction under a given account (after all you might need access to the account to view transactions). Your requirements and standards will heavily influence your design.
For each practical combination of resource and scope, is it usual practice to create a permission?
Apologies, I don't fully understand the question so I'll be a bit broad. In order to grant/deny access to a resource
, you need to:
scope
or resource
(or both)for policy enforcement to take effect. See Authorization Process.
How you go about setting all this up is entirely up to you. You could for instance:
Define individual policies, and tie each policy under the appropriate permission.
Better yet, define individual policies, then group all your related policies under an aggregated
policy (a policy of policies) and then associate that aggregated policy with the scope-based
permission. You could have that scoped-based
permission apply to both the resource and all its associated scope.
Or, you could further break apart your permissions by leveraging the two separate types. You could create permissions solely for your resources via the resource-based
permission type, and separately associate other permissions solely with a scope via the scope-based
permission type.
You have options.
If there are multiple permissions matching a given resource/scope, what does Keycloak do?
This depends on
Decision Strategy
Decision Strategy
Logic
value. The Logic
value is similar with Java's !
operator. It can either be Positive
or Negative
. When the Logic
is Positive
, the policy's final evaluation remains unchanged. When its Negative
, the final result is negated (e.g. if a policy evaluates to false and its Logic
is Negative
, then it will be true
). To keep things simple, let's assume that the Logic
is always set to Positive
.
The Decision Strategy
is what we really want to tackle. The Decision Strategy
can either be Unanimous
or Affirmative
. From the docs,
Decision Strategy
This configurations changes how the policy evaluation engine decides whether or not a resource or scope should be granted based on the outcome from all evaluated permissions. Affirmative means that at least one permission must evaluate to a positive decision in order grant access to a resource and its scopes. Unanimous means that all permissions must evaluate to a positive decision in order for the final decision to be also positive. As an example, if two permissions for a same resource or scope are in conflict (one of them is granting access and the other is denying access), the permission to the resource or scope will be granted if the chosen strategy is Affirmative. Otherwise, a single deny from any permission will also deny access to the resource or scope.
Let's use an example to better understand the above. Suppose you have a resource with 2 permissions and someone is trying to access that resource (remember, the Logic
is Positive
for all policies). Now:
Permission One
has a Decision Strategy
set to Affirmative
. It also has 3 policies where they each evaluate to:
true
false
false
Since one of the policies is set to true
, Permission One
is set to true
(Affirmative - only 1 needs to be true
).
Permission Two
has a Decision Strategy
set to Unanimous
with 2 policies:
true
false
In this case Permission Two
is false
since one policy is false (Unanimous - they all need to be true
).
Decision Strategy
is set to Affirmative
, access to that resource would be granted because Permission One
is true
. If on the other hand, the resource server's Decision Strategy
is set to Unanimous
, access would be denied.See:
We'll keep revisiting this. I explain how to set the resource sever's Decision Strategy
in Part II.
so for example I could have permission to access "accounts" and permission for "view" scope, so therefore I would have permission to view accounts?
The short answer is yes. Now, let's expand on this a bit :)
If you have the following scenario:
Decision Strategy
set to Unanimous
or Affirmative
account/{id}
resource is true
view
scope is true
You will be granted access to view the account.
true
+ true
is equal to true
under the Affirmative
or Unanimous
Decision Strategy
. Now if you have this
Decision Strategy
set to Affirmative
account/{id}
resource is true
view
scope is false
You will also be granted access to view the account.
true
+ false
is true
under the Affirmative
strategy. The point here is that access to a given resource also depends on your setup so be careful as you may not want the second scenario.
But am I right that this means I need a policy for each of the legacy groups that a user could belong to?
I'm not sure how Keycloak behaved 2 years ago, but you can specify a Group-Based policy and simply add all your groups under that policy. You certainly do not need to create one policy per group.
For example, if I have a "helpdesk" role, then I need a "helpdesk membership" policy, which I could then add to the "viewAccount" permission. Is this correct?
Pretty much. There are many ways you can set this up. For instance, you can:
/account/{id}
) and associate it with the account:view
scope. helpdesk
role under that policy Scope-Based
permission called viewAccount
and tie it with scope
, resource
and policy
We'll set up something similar in Part II.
Keycloak has a neat little tool which allows you test all your policies. Better yet, you actually do not need to spin up another application server and deploy a separate app for this to work.
Here's the scenario that we'll set up:
stackoverflow-demo
bank-api
client under that realm /account/{id}
for that client account/{id}
will have the account:view
scopebob
under the new realm bank_teller
, account_owner
and user
bob
with any roles. This is not needed right now. Role-Based
policies:
bank_teller
and account_owner
have access to the /account/{id}
resource account_owner
has access to the account:view
scope user
does not have access to the resource or scopeEvaluate
tool to see how access can be granted or
denied. Do forgive me, this example is unrealistic but I'm not familiar with the banking sector :)
cd tmp
wget https://downloads.jboss.org/keycloak/8.0.0/keycloak-8.0.0.zip
unzip keycloak-8.0.0.zip
cd keycloak-8.0.0/bin
./standalone.sh
http://localhost:8080/auth
Administration Console
link Visit Getting Started for more information. For our purposes, the above is enough.
master
realm and click on the Add Realm
button.stackoverflow-demo
as the name. Create
. stackoverflow-demo
instead of the master
realm. Users
link on the leftAdd User
button username
(e.g. bob
) User Enabled
is turned on Save
Roles
link Add Role
bank_teller
, account_owner
and user
Again, do not associate your user with the roles. For our purposes, this is not needed.
See Roles
Clients
link Create
bank-api
for the Client ID
Root URL
enter http://127.0.0.1:8080/bank-api
Save
Client Protocol
is openid-connect
Access Type
to confidential
Authorization Enabled
to On
Save
. A new Authorization
tab should appear at the top. Authorization
tab and then Settings
Decision Strategy
is set to Unanimous
Decision Strategy
See:
Authorization
tab Authorization Scopes
> Create
to bring up Add Scope
page account:view
in the name and hit enter. Authorization
link above Resources
Create
View Account Resource
for both the Name
and Display name
account/{id}
for the URI
account:view
in the Scopes
textboxSave
Authorization
tab, click on Policies
Role
from the the Create Policy
dropdown Name
section, type Only Bank Teller and Account Owner Policy
Realm Roles
select both the bank_teller
and account_owner
roleLogic
is set to Positive
Save
Policies
link Role
again from the Create Policy
dropdown. Only Account Owner Policy
for the Name
Realm Roles
select account_owner
Logic
is set to Positive
Save
Policies
link at the top, you should now see your newly created policies. Do note that Keycloak has much more powerful policies. See Managing Policies
Authorization
tab, click on Permissions
Resource-Based
View Account Resource Permission
for the Name
Resources
type View Account Resource Permission
Apply Policy
select Only Bank Teller and Account Owner Policy
Decision Strategy
is set to Unanimous
Save
See Create Resource-Based Permissions
Phew...
Authorization
tab, select Evaluate
User
enter bob
Roles
select user
Resources
select View Account Resource
and click Add
View Account Resource with scopes [account:view]
to see the results and you should see DENY
. Only Bank Teller and Account Owner Policy
. Let's test this to make sure this is true! Back
link right above the evaluation result account_owner
and click on Evaluate
. You should now see the result as PERMIT
. Same deal if you go back and change the role to bank_teller
See Evaluating and Testing Policies
Permissions
section Scope-Based
this time under the Create Permission
dropdown. Name
, enter View Account Scope Permission
Scopes
, enter account:view
Apply Policy
, enter Only Account Owner Policy
Decision Strategy
is set to Unanimous
Save
See Creating Scope-Based Permissions
Second test run
Authorization
section Evaluate
bob
bank_teller
View Account Resource
and click Add
Evaluate
and we should get DENY
.
bank_teller
has access to the resource
but not the scope
. Here one permission evaluates to true, and the other to false. Given that the resource server's Decision Strategy
is set to Unanimous
, the final decision is DENY
. Settings
under the Authorization
tab, and change the Decision Strategy
to Affirmative
and go back to steps 1-6 again. This time, the final result should be PERMIT
(one permission is true, so final decision is true). Decision Strategy
back to Unanimous
. Again, go back to steps 1 through 6 but this time, set the role as account_owner
. This time, the final result is again PERMIT
which makes sense, given that the account_owner
has access to both the resource
and scope
.Neat :) Hope this helps.