Hi All,
I would like to harness the power of token exchange implementation of Keycloak, but I cannot restrict that it could be used only by a sole client.
I tried various combinations of the following Permissions setups to deny access:
- Turn on and off fine grained permissions for super-service and evil-service clients.
- Using the client.resource.<clientid> resources which was created automatically when I turned on Permissions on the client details.
- Using a manually created resource token.exchange.resource which only contained token_exchange scope and didn't define anything else.
- Defined positive and negative policies for super-service client
- Defined positive and negative policies for regularuser user
- Completely removed everything from realm-management->Authorization (default decision should have been deny)
- Evaluate tab seemingly showed in many cases that token_exchange should be denied still both impersonation and the access->refresh token call succeeded.
Could this be a bug? Please advise how can I achieve this restriction. Otherwise evil-service would be able indefinitely fetch a new refresh_token abusing token_exchange functionality. For this it only needs to receive an access token once (eg as part of a legitimate REST call).
Steps to reproduce:
- Environment: Official Keycloak 15.0.2 docker image started with token_exchange and admin_fine_grained_authz enabled.
- Realm: imptest was created
- Clients: super-service (confidential) and evil-service (public) openid-connect were created.
- Roles: super-service service account has the realm-management.impersonation role.
- There are two users regularuser and nonimpuser. No role assignment to them whatsoever.
Get an access token for its service account by the legitimate super-service client:
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=super-service' \
--data-urlencode 'client_secret=5fc33bce-c52a-4312-bea8-e416a630e806' \
--data-urlencode 'grant_type=client_credentials'
Super-service extracts the access_token from the response (referred as {{superservice_access_token}}).
Then super-service impersonates regularuser. Notice that this should be the only allowed token-exchange request.
As a side note: I could not prevent this call by defining fine grain authz. Only removing realm-management.impersonation role from the service account was effective (but that is not a fine grain setting).
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=super-service' \
--data-urlencode 'client_secret=5fc33bce-c52a-4312-bea8-e416a630e806' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{superservice_access_token}}' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_subject=regularuser'
Super-service extracts the access_token from the response (referred as {{impersonated_access_token}}), and may use it as part of a REST API call on evil-service (curl command omitted for brevity).
In turn evil-service is able to exchange the received {{impersonated_access_token}} to a refresh_token abusing token_exchange and from that point on refresh it indefinitely.
This is the action I would like to prevent, but couldn't figure out how.
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=evil-service' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{impersonated_access_token}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:refresh_token'
Appreciating any help,
Attila