Using Keycloak for Multi-Tenancy With One Realm
You too can use a combination of Keycloak Roles and Groups in your application stack for a multi-tenant application within a single Keycloak realm. This post explains how I am doing it and may possibly lend information to you to get started doing something similar. I am using Keycloak 10.0.2 for this example.
Introduction to Multi-Tenancy
Multi-tenancy to me at least is a way to define the authentication and authorization (AuthN and AuthZ) of an application and how it relates inside your code. If your code is multi-tenant (think apartment building) then each user/group/organization can see its data and only its data. But there is a single application running to host those users. 2 sets of completely different users can go to the same application / URL and log into the same system. But the data is segmented based on their roles, groups, permissions, and access to only THEIR data.
I explain multi-tenancy in the software world with a similarity to the physical world: an apartment building. You get into the whole apartment building with a key and then into your specific apartment with a key. The rest of the apartments you cannot get into. You pay your own water, electric, phone, Internet, etc. but all people in the building have some kind of shared infrastructure.
Single tenancy = a single family home. You have the whole thing to yourself only. That is my take on it. Hopefully you get the basis of what we are talking about here. If not, read the other article I wrote on this linked below to clear my own head of this question.
Note: Since the tech world seems to always have n+1 definitions for n words, I want to make sure I explain what I mean by multi-tenancy. And I am not sure if multi-tenancy is 2 words, 1 complete word, or hyphenated so I will leave it hyphenated! See my other article below to get into more detail and explanations/examples.
My Specific Problem to Solve
Now that we have that spelled out, let’s get to the specific issue I needed to solve. I have my OpenRMF application I have been working on for almost 2 years that helps people manage all the data, STIGs, scans, reports, and compliance generation for the Risk Management Framework. In the DoD world, it is a big long manual process usually so I worked to solve that for myself and others.
Keycloak documentation and posts I have read talk on multi-tenancy with the use of 1 realm per tenant / group / application / organization. So for different user bases, each has their own realm and with it their client setup, login access, users, roles, etc.
That can work well up to the point of 400+ realms without much performance tweaking of Keycloak and the database. And it works well if you have an application that you want separate groups and separate logins per realm. But what if you have 1 group of people that only want a single login? And you want a single login for all those people in the application, but you only want them to see the data they are supposed to see?
You could do the same 1 realm per group, and link it up every single time to something like Windows Active Directory with their kerberos federation. And then manage each realm for the separation and security of the data. That seems like a PITA for what I wanted though. And each realm would have to do that.
So I asked myself: can I use a combination of a single realm and multiple groups to do something similar?
Short answer: yes you can. I spell out how I did it below.
How I am doing this
Note: This is still a work-in-progress (WIP) for my application. However, it is working well and I have talked to some other senior software folks that have done something very, very similar to this. I brainstormed with them before I worked on this.
I am mixing a single Keycloak realm with multiple groups for more finely grained permissions in my application. A realm in Keycloak can have roles and groups along with other things. With that, I am using a combination of roles (Reader, Editor, Manager, Administrator, etc.) and groups to make sure a person can be a Reader or Editor or Admin generically speaking with roles to match the [ ] type of Authorize statement in my APIs to secure them like below.
[Authorize(Roles = "Administrator,Download,Reader,Editor")]
That gives you an “all or nothing” approach to even call the API itself.
Now for restricting access to data once inside the API, I use Keycloak groups. For each of the groups, I have a key text field on my main data I wish to secure that is similar to the Project Key in SonarQube. So for each “system” in my application, the record has a systemKey that must be unique. That systemKey is saved with the system data it belongs to in my database. When that data is accessed, I match the systemKey of that data to the groups the person belongs to using the called out code below.
Example: For any “GET” I am looking to see that they have “systemKey_Reader” and if they do not I send back an HTTP 401 error. So if a person accesses a system with a key “openrmfpaas” and they are doing a GET to read the record, I am making sure “openrmfpaas_Reader” is in their group listing before I send any data back to the API caller. If so, good to go. If not, throw the 401 error and stop processing.
As you see below with the C# code I have (yes, I do .NET Core sorry!!) you can pull the user claim, and then pull the groups from the claims listing. If you have more than one group it will come back with a listing. Then you can cycle through the list of groups and use the systemKey to make sure what you are looking for matches.
This will be based on the systemKey as well as whatever other group permissions you allow for that specific API call. For a “PUT” update, you may want the systemKey_Editor as an example. For a “DELETE” you may want the systemKey_Administrator.
If not the person does not have that correct group, you send back an HTTP 401 or some other message about unauthorized access. And make sure you log it and store it in auditing separately if required or warranted for security reasons. You will probably want to document the roles/groups against the APIs so you can have it for testing, documentation, security scanning, and the like.
// get the token and information from AuthN and AuthZ
var claims = this.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault();// now get your group names
var groupList = claims.Subject.Claims.Where(x => x.Type == "groups").ToList();// call the security checker
if (!SecurityChecker.CanViewArtifact(art.systemKey, groupList)) {
return Unauthorized();
}...public static bool CanViewArtifact(string systemKey, List<System.Security.Claims.Claim> groupList) {
return (groupList.Where(x =>
x.Value.StartsWith(systemKey+"_")).FirstOrDefault() != null);
}
The Role and Group Relationship with Keys
The one caveat to doing this setup is this: I use roles to wholly block access to APIs. And then I use groups for more fine-tuned access within the API if they can even get to it.
That said: someone with “openrmfpaas_Editor” group access that does NOT have the “Editor” role in my application as it stands now will not get to the API they are calling. That is because I want to block access to my API 100% if you do not have the generic role. And then I want to check group access for fine details. I could just use group access, but then everyone would be able to call every API. I chose not to do that. So I have to manage this.
As I said this is a WIP so I may tweak that as I go along. However, I want to be more security conscious than anything so I will put up with having to manage groups and roles correctly. And when automating the adding of groups for new systems in my application as well as putting people into/out of them, I will make sure my code works correctly so that is covered and documented how it works.
Note: You can use the “kcadm” command in a serverless way or use the Keycloak APIs to administer adding and removing groups correctly. You will need a proper administrator type of Keycloak account to use to do this. That is information to write about for another day and not included here. However, I know you can do that. I do it for my startup script that someone in the OpenRMF community wrote to automagically setup Keycloak for Linux, Mac and then Windows.
Of course, you can also do it manually (yuck) in the Keycloak UI by going to the realm, then Users, and then finding the user and managing their Groups and Roles correctly.
Where to Go from Here
As I usually say when I write these, this is NOT the only way to do this. This is a way I chose to do it. And it is working so far pretty well. We will see what happens if/when a person logs in and there are 500+ systems and they have access to all of them somehow.
Hopefully this may help you figure out things on your end to do something similar. Or maybe it will deter you and have you do it another way completely. Either way I hope this helps you.
I had to search and search and have a few browser tabs open to get this to work correctly. When that happens to me, I tend to write a post here on Medium.com so others won’t have to fight the same battle and get there faster.
I am also (of course) writing this down for myself so I can come back to it with my next project so I don’t have to rely on my fading memory to remember what I did!
Happy Coding!