Keycloak Huge JWT Token of master admin - jwt

We have a realm per customer, multi-tenant architecture. (Expected to have around 500 realms) There is a service account we use, a client in the master realm that will manage the customer realms. The problem is the huge JWT token for the master realm admin user/service account, who manage all the realms that increases in size as the number of realms increases. This is due to the 20+ client roles of each new realm. What are the different options we have to keep the token size low?
EDIT:
ps: Reducing roles is not an option either. The service account is an admin of the keycloak admin portal and needs to manage all the realms, so it needs manage-realm etc roles for all the realms. Keyclaok admin portal will not for example allow to delete the realm if it doesnt have delete-realm role.

To reduce the size of the token you can use the following strategies:
restrict the roles that the client access;
use client scopes to narrow down the claims that will be added to the token.
For the first option, you can go to the client in question, tab Scope, disable Full Scope Allowed, and then choose only the roles that the client is really interested.
For the second option, you can go to Client Scopes, create a scope, save it, then go to the Scope tab, add the roles that will be part of that scope. Then everytime you make a request for a token to the client in question you can send the scope as a parameter of that request. This way you will only include in the token the roles that belong to that scope.

I think you are running into this https://issues.redhat.com/browse/KEYCLOAK-1268.
If you look through the discussion there you will see
Tokens created for admin-cli or security-admin-console no longer have
any roles embedded within them. The Admin REST API now checks the
"aud" claim. If the audience is one of those clients, then it ignores
claims in token and just accesses the UserModel directly to determine
if admin has specific permissions.
Use admin-cli or security-admin-console for your client_id. Otherwise you will get every role there is for every realm there is in your token.
In order to use either one of those clients with a token request, you have to make it confidential and enable Direct Access Grants.
Then something like this will work and give you a managable token
curl -i -X POST \
-H "Content-Type:application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "client_secret=<admin-cli-secret>" \
-d "username=my_admin_user" \
-d "password=<my_admin_user_password>" \
'http://master.foo.com/auth/realms/master/protocol/openid-connect/token'

Related

How to solve - Keycloak - Client not allowed to exchange

I tried to implement Keycloak direct naked impersonation from documentation, but in the end I got the error:
"error": "access_denied",
"error_description": "Client not allowed to exchange"
This is the Postman setup, with the admin-cli, clientId and with the user, justin, that I want to get the token for, that exists in the Users section.
The admin-cli secret that I used in Postman:
I followed all the steps that are in the Keycloak Direct Naked Impersonation documentation.
Toggle Users Permissions Enabled to On.
Define a policy for this permission.
Add the client policy, "client-impersonators" in my case, to the users' impersonation permission
This is the request setup that Keycloak recommads to have this direct naked impersonation working. You saw this in my above Postman setup.
curl -X POST \
-d "client_id=starting-client" \
-d "client_secret=the client secret" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "requested_subject=wburke" \
http://localhost:8080/realms/myrealm/protocol/openid-connect/token
Inside Docker I have the setup for token_exchange=enabled and admin_fine_grained_authz=enabled
- name: "KEYCLOAK_EXTRA_ARGS"
value: "-Dkeycloak.profile.feature.admin=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled -Dkeycloak.profile.feature.token_exchange=enabled"
I searched days after days about this topic and I tested all the available options that are on internet right now, about this subject, but without any success.
Please anyone if this worked for you, be nice an share how you solved. Or at least please give some ideas, maybe I missed something.
New Policy created for user-impersonate, where I added the user Justin
Then inside the Permissions I added this Policy
The problem is that the documentation contains an image that is wrong, namely:
It should have been a Client Policy with the client admin-cli instead of the user admin.
For instance:
So, you need 2 policies/permissions:
impersonate (i.e., Policies that decide if administrator can impersonate other users), where you create a Client Policy and pass the client "admin-cli" (in your case);
user-impersonated (i.e., Policies that decide which users can be impersonated. These policies are applied to the user being impersonated), where you create a User Policy and pass the user "justin" (in your case).
Step-by-Step : KC 20.0.3 new UI
(Side note you should use other realm and client instead of the master and admin-cli at least in a production environment)
Enable the secret in the admin-cli:
go to master > clients > admin-cli
set Client authentication to ON
Create the two Policies: 1 Client Policy and 1 User Policy
go to master > clients > master-realm
click on the Authorization tab
click on the Policies sub-tab
click on Create policy, and then Client
name it something (e.g., Client-impersonator)
add admin-cli to the Client field and click Save
Repeat again the aforementioned steps, but this time create a User Policy for the user that will be impersonated (i.e., justin in your case)
Add the Policies to the corresponded scopes/permissions:
go to Users
go to the tab Permissions
set Permissions enabled to ON
click on impersonate
select the policy corresponded to the client admin-cli (e.g., policy named Client-impersonator)
click on Save
go again to Users
go to the tab Permissions
this time select user-impersonated
and select the policy for the impersonated user (i.e., justin)
Perform the request:
curl -X POST \
-d "client_id=admin-cli" \
-d "client_secret=8AEx99Ob4Hc8oricSGnii6x4Rs57g4ny" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "requested_subject=justin" \
http://0.0.0.0:8080/realms/master/protocol/openid-connect/token

Keycloak token exchange across realms

We use Keycloak 12.02 for this test.
The idea is that we have a lot of customers, that we all have in their own realms. We want to be able to impersonate a user in any non-master realm for an admin/support user in the master realm.
The flow would be to:
login using a super-user/password to login into the master realm
get a list of all available realms and their users
craft a request to exchange the current access token with a new access token for that specific user.
It is the last step I cannot get to work.
Example:
Login to master realm
token=$(curl -s -d 'client_id=security-admin-console'
-d 'username=my-super-user' -d 'password=my-super-pass' \
-d 'grant_type=password' \
'https://login.example.net/auth/realms/master/protocol/openid-connect/token' | jq -r .access_token)
(we now have an access token for the super-user in the master realm)
The Keycloak server has enabled token exchange (-Dkeycloak.profile.feature.token_exchange=enabled) as described here https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange.
Attempt to impersonate a user in another realm (not master):
curl -s -X POST "https://login.example.net/auth/realms/some_realm/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d 'client_id=some_client' \
-d "requested_subject=some_user" \
-d "subject_token=$token"
However, this does not work. The result is: {"error":"invalid_token","error_description":"Invalid token"}
(Doing this inside a single realm work)
What am I doing wrong here? This seems like a very normal feature to utilize in a real-life deployment, so any help is much appreciated!
UPDATE:
First of all, I found the very same use-case here: https://lists.jboss.org/pipermail/keycloak-user/2019-March/017483.html
Further, I can get it to work by working through some major hoops. As described above, one can use the broker client in the master realm as an identity provider:
Login as super-user adminA -> TokenA
use TokenA to get a new external token, TokenExt from the master identity provider.
Use TokenExt to do a token exchange for the user you want to impersonate
The caveat with the above is that the user adminA is created in each of the realms you log into with this method, so still not ideal.
as far as I know what you are describing is not possible. I'm wondering where you are, more than a year afterwards... did you solve your issue?
Before going further, note that I have found Keycloak discourse a good forum for Keycloak questions: https://keycloak.discourse.group/
Second, this is what I understand: for Keycloak, 2 realms or 2 different Keycloaks is the same. There is nothing common, they are 2 completely different id providers. So any reasoning that supposes shared trust or shared users between realms will not work.
For logging in to the other realm, you need a token that is trusted. There is no reason for the other realm to trust the master realm. The way to set that trust is to set up the master realm client as an identity provider to the other realm (I understand that this is what you do not want to do), so that tokens signed by the master realm will be trusted by the other realm.
And once you have that set up, I have not seen any other way of exchanging than having the token exchange create a federated "admin" user in the other realm (I configure it to be created each time from scratch, to avoid any synch). Also, 2 mappings are going to come in to play, the ID provider mapping, and the client mapping, for creating the resulting JWT.
If this doesn't match with your findings, please correct me.
Ah yes: there is also the question of using token exchange as defined in OAuth, with the may_act claim, which would be perfect here. But it would come after the exchange between realms, in addition. See https://datatracker.ietf.org/doc/html/rfc8693#section-4.4
EDIT: to "create the user each time from scratch"
go to "identity providers" / / "settings"
select "sync mode" to "force"
This is the relevant extrant from the tooltip:
The sync mode determines when user data will be synced using the
mappers. Possible values are: 'legacy' to keep the behaviour before
this option was introduced, 'import' to only import the user once
during first login of the user with this identity provider, 'force' to
always update the user during every login with this identity
provider.
so when you choose "force", basically the user will be overwritten at each login.
Ok so it's not really a creation, but as close as you can get :-)
The idea here is to not care about it, which is fine for prototyping. But I guess that in production you may want to optimize this.

Why does keycloak hide the "Service-account-admin-cli" user under "Users" section?

I was trying to call keycloak's REST api to create new users under a realm and I was following this tutorial:
https://www.appsdeveloperblog.com/keycloak-rest-api-create-a-new-user/
I was able to configure the Admin-cli client as below:
and I was able to get the access token by using the client id and secret:
However, when I make a POST request to /auth/admin/realms/myapp/users with the bearer token, it fails to create a user and I got an "unknow_error"
I searched through the internet and community and documentation but there was no clue. Eventually after hours and hours of trying, I found a solution:
You need to first go to clients --> admin_cli --> Sessions:
Then click on the user "Service-account-admin-cli" and configure such that it has admin role
Then, the previous POST request will successfully create a new user.
I cannot understand why this user "Service-account-admin-cli" is hidden under the users section:
Why would it be hidden??? How are people supposed to find this user (Service-account-admin-cli) and configure it? Does keycloak expect people to find it by clicking clients --> admin_cli --> Sessions and then see the user from there??
IMO if one is starting to learn Keycloak one should avoid using the master Realm or change the admin_cli configuration like that without a very good reason.
From the Keycloak documentation one can read:
When you boot Keycloak for the first time Keycloak creates a
pre-defined realm for you. This initial realm is the master realm. It
is the highest level in the hierarchy of realms. Admin accounts in
this realm have permissions to view and manage any other realm created
on the server instance. When you define your initial admin account,
you create an account in the master realm. Your initial login to the
admin console will also be via the master realm.
We recommend that you do not use the master realm to manage the users
and applications in your organization. Reserve use of the master realm
for super admins to create and manage the realms in your system.
Following this security model helps prevent accidental changes and
follows the tradition of permitting user accounts access to only those
privileges and powers necessary for the successful completion of their
current task.
So typically you would create a different Realm, and create the users there. Unless, you really want to create a user on the master realm, typically admin-alike users.
That being said to create the user using the Keycloak Rest API, one just need to request from the admin-cli client a token on behalf of the admin user by providing its name and password, for instance as follows:
TOKEN=$(curl -k -sS -d "client_id=admin-cli" \
-d "username=$ADMIN_NAME" \
-d "password=$ADMIN_PASSWORD" \
-d "grant_type=password" \
http://$KEYCLOAK_IP/auth/realms/master/protocol/openid-connect/token)
from the $TOKEN object extract the access token (let us named $ACCESS_TOKEN).
And then create the user as follows:
curl -k -sS -X POST https://$KEYCLOAK_IP/auth/admin/realms/$REALM_NAME/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "$USER_JSON_DATA"
$USER_JSON_DATA will be the json data representation of the user to be created. There is no need to add the role admin to the master admin deployed with Keycloak by default.
Does keycloak expect people to find it by clicking clients -->
admin_cli --> Sessions
If setup normally, you would just need to know (as I already described) the admin's name and password, which is configured in the initial setup anyway.
Following this setup then you:
You need to first go to clients --> admin_cli --> Sessions:
you would see the following:
The difference now is that if you click on the admin user > roles, you would see the following:
The admin user, has already the admin role. No need for:
configure such that it has admin role
Now if you change the admin_cli configuration exactly as you did then you need to add to the Service-account-admin-cli user the role admin.
I cannot understand why this user "Service-account-admin-cli" is
hidden under the users section:
It is an implementation detail, unfortunately, I could not find online a an explanation for that either. But I do agree that without further context it does not look very user-friendly. Now that begs the question of why the tutorial did not warn their readers about it.
Now, if one speculate a bit, the reason to hide that user from the list of users could be because:
it is not a real user in the conventional sense; not meant to be used to login into Keycloak. If you try to set the password of that user you get always an error.
the list of users is for the users explicitly created for that realm that one can actually explicitly authenticate. In other words, "Service-account-admin-cli" is used so that it represents the "one" performing the call to the admin-cli, since when one changes the grand type from password to client-credentials there is no explicit user authentication anymore (i.e., admin username and password). So "Service-account-admin-cli" is used as a place holder.
Naturally, one could argue why not just make "Service-account-admin-cli" have the admin role by default?!, but again it is implementation details, that only the developers behind can justify.
Another good reason to not blindly change the original setup of the admin-cli.
Just did the same tutorial and had the same problem. Thank you for your post! It helped me to figure out the solution. I don't know if that is due to a newer Keycloak-version but I don't even see the session of the service-account.
Instead of going through the session-menu you could instead go to:
clients -> admin-cli -> service account roles
And then add "manage-users" from:
client roles -> realm-management
I think that's how it's supposed to work.

"Access Denied. Provided scope(s) are not authorized" error when trying to make objects public using the REST API

I am attempting to set permissions on individual objects in a Google Cloud Storage bucket to make them publicly viewable, following the steps indicated in Google's documentation. When I try to make these requests using our application service account, it fails with HTTP status 403 and the following message:
Access denied. Provided scope(s) are not authorized.
Other requests work fine. When I try to do the same thing but by providing a token for my personal account, the PUT request to the object's ACL works... about 50% of the time (the rest of the time it is a 503 error, which may or may not be related).
Changing the IAM policy for the service account to match mine - it normally has Storage Admin and some other incidental roles - doesn't help, even if I give it the overall Owner IAM role, which is what I have.
Using neither the XML API nor the JSON version makes a difference. That the request sometimes works with my personal credentials indicates to me that the request is not incorrectly formed, but there must be something else I've thus far overlooked. Any ideas?
Check for the scope of the service account incase you are using the default compute engine service account. By default the scope is restricted and for GCS it is read only. Use rm -r ~/.gsutil to clear cache in case of clearing cache
When trying to access GCS from a GCE instance and getting this error message ...
the default scope is devstorage.read_only, which prevents all write operations.
Not sure if scope https://www.googleapis.com/auth/cloud-platform is required, when scope https://www.googleapis.com/auth/devstorage.read_only is given by default (eg. to read startup scripts). The scope should rather be: https://www.googleapis.com/auth/devstorage.read_write.
And one can use gcloud beta compute instances set-scopes to edit the scopes of an instance:
gcloud beta compute instances set-scopes $INSTANCE_NAME \
--project=$PROJECT_ID \
--zone=$COMPUTE_ZONE \
--scopes=https://www.googleapis.com/auth/devstorage.read_write \
--service-account=$SERVICE_ACCOUNT
One can also pass all known alias names for scopes, eg: --scopes=cloud-platform. The command must be run outside of the instance, because of permissions - and the instance must be shutdown, in order to change the service account.
Follow the documentation you provided, taking into account these points:
Access Control system for the bucket has to be Fine-grained (not uniform).
In order to make objects publicly available, make sure the bucket does not have the public access prevention enabled. Check this link for further information.
Grant the service account with the appropriate permissions in the bucket. The Storage Legacy Object Owner role (roles/storage.legacyObjectOwner) is needed to edit objects ACLs as indicated here. This role can be granted for individual buckets, not for projects.
Create the json file as indicated in the documentation.
Use gcloud auth application-default print-access-token to get authorization access token and use it in the API call. The API call should look like:
curl -X POST --data-binary #JSON_FILE_NAME.json \
-H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
-H "Content-Type: application/json" \
"https://storage.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME/acl"
You need to add OAuth scope: cloud-platform when you create the instance. Look: https://cloud.google.com/sdk/gcloud/reference/compute/instances/create#--scopes
Either select "Allow full access to all Cloud APIs" or select the fine-grained approach
So, years later, it turns out the problem is that "scope" is being used by the Google Cloud API to refer to two subtly different things. One is the available permission scopes available to the service account, which is what I (and most of the other people who answered the question) kept focusing on, but the problem turned out to be something else. The Python class google.auth.credentials.Credentials, used by various Google Cloud client classes to authenticate, also has permission scopes used for OAuth. You see where this is going - the client I was using was being created with a default OAuth scope of 'https://www.googleapis.com/auth/devstorage.read_write', but to make something public requires the scope 'https://www.googleapis.com/auth/devstorage.full_control'. Adding this scope to the OAuth credential request means the setting public permissions on objects works.

Keycloak: grant_type=password in custom Identity Provider

I added a custom OIDC Identity Provider to my realm and i want to use the Direct Access Grants flow (or grant_type=password) but this doesn't work.
Is it possible with Keycloak?
When try with Authorization Code flow every thing works fine but with
grant_type=password the error
{
"error":"invalid_grant",
"error_description":"Invalid user credentials"
}
is returned.
I'm trying to get the access token e the refresh token doing the following request:
$ curl -X POST 'http://localhost:8080/auth/realms/master/protocol/openid-connect/token'
-H 'content-type: application/x-www-form-urlencoded'
-d 'grant_type=password'
-d 'client_id=test-client'
-d 'client_secret=834a546f-2114-4b50-9af6-697adc06707b'
-d 'username=user' // valid user in custom Identity Provider
-d 'password=password' // password in custom Identity Provider
And this is the Identity Provider configuration:
this is the Identity Provider configuration
Please have a look below curl command
curl -X POST -k -H 'Content-Type: application/x-www-form-urlencoded' -i 'https://135.250.138.93:8666/auth/realms/<Realm-Name>/protocol/openid-connect/token' --data 'username=<userName>&password=<Password>&client_id=<Client-ID>&grant_type=password&client_secret=7df18c0d-d4c7-47b1-b959-af972684dab0'
In above command you have to provide these details
Realm-Name - Realm name against which you want token
userName - You should have a user which can access the above realm
Password - Password for above user
Client-ID - Client Name(Generally its a String Value) under the
Client-Secret - Client secret of above client which you can find [Realm->Client List->Select the client->Credential tab]
I was also stuck with this issue as well. In the beginning I also suspected that it looked like a bug. However, the turning point is that I tried with the master realm and the client_id=admin-cli with my admin user. I can retrieve the token with grand_type=password. It's just failed for my own realm and client_id like reported here.
I figured out my issue is that the user I used wasn't activated after I tried to login into my realm's console(eg: http://localhost:18080/auth/realms/quarkus-workshop-labs/account/). I need to reset my password, so it can be finally activated. Then the password grant_type just starts to work.
(note that by default, your new created user needs to reset password before it can use.)
Keycloak doing below validations before the DirectGrant auth flow.
username
password
otp (if configured)
if the user is enabled
if the user is locked due to brute force direction (only if it's enable)
You can customize this in Authentication -> Flows and select Direct grant flow.
For example you can disable Direct Grant - Conditional OTP to genarate token without checking otp.
Yes it is possible.
You need to enable/Grant Direct access in Keycloak settings for the particular client.
You need to set a client with test-client and the user should be available in the realm. Though cannot get what you wanna achieve with this 🧐
I got exactly same scenario it looks like a bug to me. I had to unlink the account from IDP, set the password and remove pending user actions. It is not a solution but in my case was ok as I needed only test user account for API tests and don't have that scenario on production.
I was able to use DAG if I set the (automatically provisioned) user's password in Keycloak to something and with that password I was able to get the token from the external iDP. I used this to investigate an Okta token. Hope this helps.
I think this is actually the right answer: answer.
To summarize: You can't do exactly what you requested, because Keycloak is not storing the password in DB, so password grant type flow is not valid for this user.
The alternative is to use Token Exchange feature that allows you to login to OIDC directly and use it's access token to retrieve keycloak access token.
There is an example code in the linked answer.