Keycloak - prevent assignment of roles - oauth-2.0

I am working on service to service authentication using the client credentials flow.
I am using keycloak as the authorization server.
Let's say, there are two services S1 and S2.
Every service will allow only certain other services to call its APIs. It might also happen that two different APIs of one service might permit different set of callers.
I have created roles. I have created scopes and mapped the roles to the scope.
When I am passing the scope in the get token call (/realms/realm_name/protocol/openid-connect/token), I am getting the token with roles that are mapped to the passed scope.
What I am not able to figure out is how do I restrict the assignment of different roles to different services?
Only service owner or admin should be able to login to keycloak and should be able to assign the roles corresponding to his/her service to other services. He should not be able to do anything with roles not meant for his service.
Eg:
S1 has owner O1 (a user in keycloak). S1 has three APIs A1, A2 and A3. To call A1 API, caller should have R1 role, and to call A2 API, caller should have R2 role.
Now if service S2 wants to call S1's API A1, only admin or user O1 should be able to attach role R1 to S2's client in keycloak (or add role R1 to scope attached to service client S2).
This might be related to limited access to perform different operations in keycloak.

If I understood your request correctly you can try to use client finer permission. Run keycloak with the following flag:
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
Then in your client you can choose which user can do what with the client:
For example, you can client on the permission view and then create a policy based on the user, and then configure the rest accordingly.
You can in the Keycloak official documentation read more about Managing policies.

Related

Restricting service account access within a service

Background:
I have 2 services, A and B, both requiring access to service X, all running on Cloud Run.
Service X requires authentication, since both A and B have their own service accounts, I gave them the invoker role on service X, made sure access tokens are set on headers, so everything works.
Actual question:
I want to restrict access to specific APIs within service X. Ex: I want Service A to be able to only use READ endpoints, but B to be able to also use WRITE endpoints.
Is there any way to include custom scopes or claims to access tokens generated by GCP from service accounts so I can have granular access control at the API level?
Note: I come from an Azure background, where we could setup and API with scopes, and assign those scopes to accounts. Trying to understand if there's an equivalent on GCP.
In gcp we can create a custom role service account while calling API, In your case you need to create 2 service account with custom role, one with viewer role and other with writer or editor role.
Here is a blog explaining the process of creating a service account with custom roles.
While calling an API make sure you are using the correct service accounts for performing read and write operations respectively.

Distinguishing between machines with client credentials flow

It seemed like client_credentials flow was appropriate for a machine-to-machine communication between our system and third parties for importing and exporting data.
But if I have two third parties p and q say and users on our system u and v say, then I need to know which of p and q can acces the data of which of u and v.
For example: user u grants access to p (but not to q) and v to q (but not p).
I can give different client secrets to p and q but when they present thir secret to IdentityServer in order to obtain a token I need to know which of p and q it is and add a claim to the token that my controllers can use to determine which of u ad v's data is visible.
I can implement ICustomTokenRequestValidator to intercept the secret and look it up in the configuration context, but the Id column is not in the model class, so I could abuse the Description column as a foreign key to my table of third parties -- seems hacky.
Is there a standard/recommended way to resolve this situation -- that different machines have different data visible to them (different claims in their token)?
If you use client_credentials flow then there is no user-interaction involved.
Each third party should have it's own client with corresponding client credentials. Do not share the same credentials over boundaries, i.e. between different third parties. By having separate clients for separate parties, you minimize the security risk and minimize the impact when having to revoke or change the credentials.
With that said, in the Client Credentials flow the Authorization Server returns a token if the request is valid - it does not know from the request if the user authorized the request or not because the user is not involved. But it authenticates the client. So, if you want to differentiate between several third parties, give them individual clients. If you can't add a claim to the token.
Scopes and claims are the tools to define what a client requests to do. Some authorization can be done in the Authorization Server. For example, an Authorization Server typically only allows a client to request certain scopes.
Use then claims (the data in the token) for a fine grained authorization in the API. It depends on your use case. If the API is supposed to return a filtered result, i.e. only return data from users that are ok with it, then the API needs to be able to look this information up somewhere, as #gary-archer states. Use an applicable claim from the token that enables the API to identify the client or third party. It can then use this data for the lookup. For example, use the client_id claim but it really depends on the Authorization Server which claims it adds to the token.
If the client already knows beforehand which user's data it is going to access, it could add the user-id when requesting the token and - once again - depending on the capabilities of the Authorization Server - get a token with a user_id in the claim set. Then the API can use the client_id and user_id for a lookup.
How you collect the users' consent is out of scope and happens out of bound. It's part of your business logic.
At the end it is all about designing the token. Here is another article on Centralizing Identity Data
The standard option here is to include custom claims from your business data at the time of token issuance. Rather than machine to machine I would describe your scenario as B2B.
EXAMPLE USE CASE
Consider an API called by business partners who act as suppliers of inventory to your system. In this case a useful access token might look like this, and is what I would aim for:
{
client_id: 1hvf367g
supplier_id: 42
exp: ...
}
API requests for stock items could then authorize based on the supplier_id value in the access token. Eg by running a SQL query on the business data, filtering on the supplier ID. All of this keeps your API code simple.
DATA MAPPING
For this to work you need to design onboarding. Eg a button click to create a supplier in your system might create the OAuth client using Identity Server, then save the client_id to a suppliers table in the business data.
Not all identity systems support issuing custom claims in the above manner. An alternative design is to just include the client_id in the access token, and look up the supplier ID from business data when your API receives an access token. This tends to add complexity to API code though.
SUMMARY
I think your question is really about designing business permissions, and OAuth alone cannot solve it. If I'm right then the Claims Best Practices article may be useful.

Checking app role assigned to a managed identity transitively

Input:
A list of managed identity
The name of app role that we are interested, for example User.Read.All on Microsoft Graph API resource.
Output:
All managed identity in this list that have this role assigned either directly or indirectly via AAD group. For the latter, the role is assigned to an AAD group which the managed identity is a member of.
For the direct assignment we can use the following rest endpoint described in the following doc:
https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignments?view=graph-rest-1.0&tabs=http
For the latter, we need to be able to list the app role assignment transitively, we have tested the API above, it does not handle the transitive assignment. Interestingly, such transitive capability is available if app role is assigned to an AAD user.
https://learn.microsoft.com/en-us/graph/api/user-list-approleassignments?view=graph-rest-1.0&tabs=http
Our question:
Is there an endpoint that can provide such transitive checking?
We did come up with a work around. It is not perfect but at least it avoids the N rest endpoint call where N is the number of groups a managed identity belongs to.
Call Graph API to find all principals an app role is assigned to using app role list assigned-to endpoint.
https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignedto?view=graph-rest-1.0&tabs=http
This will include both the service principal and the AAD group as the assignee. The result is a set called S1.
Call Graph API to find all groups a managed identity belongs to. The result is a set called S2.
Add the principal Id of the managed identity to S2
Calculate the intersection of S1 and S2. If the intersection is empty, the app role is not assigned and if the intersection is non-empty, the app role is assigned. The approach requires two rest calls.
Could you help validate this work around if there is no better solution? It seems to us this is a common use scenario, we wish there is a simple solution than the work around above in the future.

How can I authorize an OAuth2 machine-to-machine token between a microservice and its authorization server?

I have an OAuth2 server that is able to give me an access token based on my client ID & secret.
I have a microservice where other servers can perform actions respective to its job.
However, I need to authorize the token to ensure it has the permissions to do that.
Is it common for the server to forward the access token to the auth server and get a response back that includes the details such as scope? etc.
Yes and no.
For the entry point, you definitely need to check the access token's scope by asking it to oauth2 provider, but you probably don't want to do it everytime it's following code to call the provider whenever it requires scope check.
In Spring framework, that authorized information is stored in SecurityContext, and any code in the same VM can access it by calling SecurityContextHolder.getContext().getAuthentication(). Probably you can implement similar thing with your language and platform you are using, so that authorized information can be shared by others, with thread-safe way.
However, in microservice world, each microservice could or could not have the scope its client used. For example, let's say there is client A, calling microservice B's C method, and B's C calls another microservice's H's I method.
Depends on what scope is required for the method B.c() and H.i(), even if your code can propagate the OAuth2 scope to the following microservice, client A's call could end up having insufficient scope exception and fails.
If you are lucky, then yeah client's call will succeed.
I had the same issue, and there wasn't any clear silver bullet or best practice for what to do with OAuth2 scope in Microservice world. I feel like it just all depends on what's your service topology look like and how it is designed.

How is the correct way to add specific information for Resource Server inside OAuth2 access token?

I have a scenario where my Resource Server (RS) can connect to multiple databases and it can be multi-tenant. It has an environment configuration where I can say: Env A points to database A and belongs to tenant A; Env B points to database B and belongs to tenant B;
Our desktop apps store that information in a data structure that is sent to RS on every call and our web apps store it inside ASP.NET Session. The user select the environment at login.
Now we are moving our APIs completely to REST services and protecting them with oAuth2 using IdentityServer4.
I need to send that environment value to the APIs and I think that should be part of the access token.
The first question is: Is that correct? Can a access token have that kind of information?
The second is: Which is the best IdentityServer4 service that I should extend to inject that value as a claim inside access token and consequently inside ClaimsPrincipal.
The third is: Sometimes I don't have a user to select the environment at login (client credentials grant, for example). In this case, is the correct treat environment as a client claim? There is a way to have dynamic client claims?
Sorry about the long question!
Regards,
Diogo
If that claim is about the users (or clients) identity - yes - it is a candidate for the token.
Add the claim to the resource scopes that represent your API - this way the claim type will be requested in the profile service and you can add it to the token.
Client claims are not dynamic though.

Resources