[keycloak-user] Question about CBAC + Pushing Claims + Authorization Scopes

Álvaro Gómez alvaro.gomez.gimenez at tecsisa.com
Thu Jul 25 09:12:32 EDT 2019


That would be possible but gets a bit messy when you try to mix it with
scoped-resources (we already thought about this situation). If we consider
the original example (sellers and product-managers trying to access
products from different tenants) we could, as you say, build our pushing
claims as follows:

{
  "resource": "product",
  "resource_scopes": ["sell"],
  "claims": {
    "Organization-1": ["sell"]
  }
}

That permission would represent a Requesting Party trying to gain access to
the scope "sell" in the context of "Organization-1". We need to include the
scope reference in the pushing claims since otherwise we won't know in the
future which scopes were granted to which contexts. This is where things
start to get confusing for us. Considering the previous permission is
correct, if the Requesting Party upgrades the permission trying to access
to the "update" scope of the same resource in the context of
"Organization-2" it will result in the following permission:

{
  "resource": "product",
  "resource_scopes": ["sell", "update"],
  "claims": {
    "Organization-1": ["sell"],
    "Organization-2": ["update"]
  }
}

To be able to grant that permission we find two issues:

1.- The policy "Is-Product-Manager", involved in the permission
"product:update", should only check if the Requesting Party is
"Product-Manager" in the context of the tenants which contains "update" in
the values of the claims. In the example, the policy "Is-Product-Manager",
when evaluated for the permission "product:update", should only check if
the Requesting Party is "Product-Manager" in the context of
"Organization-2" since it makes no sense to check if it's "Product-Manager"
in "Organization-1" (The "update" scope was only requested for
"Organization-2"). This could be solved by creating a policy
"Is-Product-Manager:update" (which only checks tenants associated to the
scope "update" in the pushing claims) but we found this solution a bit
tricky.

2.- If a Requesting Party requested access to the scope "update" along with
other scope "read" in the context of "Organization-2" without being
"Product-Manger" in such organization (which implies it can "read" but not
"update" a product in that organization), we would end up with the
following permission (The first time you request an RPT, Keycloak returns
the granted scopes even though they are not the whole requested scopes):

{
  "resource": "product",
  "resource_scopes": ["read"],
  "claims": {
    "Organization-2": ["update", "read"]
  }
}

As we can see, the granted scope is only "read" but the pushed claims in
"Organization-2" are both "update" and "read" (We requested both scopes in
the ticket). The claim becomes inconsistent since the scope "update" should
be removed and keycloak is not able to do so since it does not understand
our custom claims. If claims were natively grouped by scopes, Keycloak
would clean claims from not-granted scopes. Wdyt?

Thanks!
Álvaro.

El jue., 25 jul. 2019 a las 14:16, Pedro Igor Silva (<psilva at redhat.com>)
escribió:

> Considering you are in control on how the ticket is created and how claims
> are set on it, would be an option to use a specific claim for each tenant
> so that in your policies you check tenants based on the claim's key ?
>
> On Thu, Jul 25, 2019 at 9:12 AM Álvaro Gómez <
> alvaro.gomez.gimenez at tecsisa.com> wrote:
>
>> Hi Pedro,
>>
>> We are performing HTTP ticket requests from our application (An API
>> acting as a Resource Server). As an example, having the following endpoint:
>>
>> GET /api/tenants/__TENANT_ID__/products/__PRODUCT_ID__
>>
>> If we use non-scoped resources (In order to simplify the example) the API
>> behaves as follows:
>>
>>   ** The Requesting Party performs this action:
>>
>>      GET /api/tenants/Organization-1/products/Product-X
>>
>>   1.- If there is no "permissions" claim (Or it does not contain the
>> required authorization info, described in step 2) the API performs a ticket
>> request for the resource "Product-X" pushing the tenant "Organization-1" in
>> a claim:
>>
>>     POST
>> https://localhost:8080/auth/realms/***/authz/protection/permission
>>        [{
>>             "resource_id": "Product-X",
>>             "claims": { "tenant": [ "Organization-1" ] }
>>        }]
>>
>>   The Requesting Party uses the ticket to obtain a valid RPT containing
>> the following authorization info:
>>
>>       "permissions": [
>>         {
>>             "resource_id": "Product-X",
>>             "claims": { "tenant" : ["Organization-1"] }
>>         }
>>       ]
>>
>>   ** The Requesting Party performs the following action using the
>> previously obtained RPT:
>>
>>      GET /api/tenants/Organization-2/product/Product-X
>>
>>   2.- The API checks if the specified resource "Product-X" exists in the
>> RPT "permissions" claim and contains "Organization-2" in the "tenant"
>> pushed claim. Since the resource "Product-X" is only provided for the
>> context "Organization-1" the API requests a ticket for the resource
>> "Product-X" in the context of the tenant "Organization-2".
>>
>>    POST
>> https://localhost:8080/auth/realms/***/authz/protection/permission
>>         [{
>>             "resource_id": "Product-X",
>>             "claims": { "tenant": [ "Organization-2" ] }
>>         }]
>>
>>     The Requesting Party uses the ticket to upgrade the previous RPT. The
>> upgraded RPT now contains both tenants in the pushed claims:
>>
>>         "permissions": [
>>             {
>>                 "resource_id": "Product-X",
>>                 "claims": { "tenant" : ["Organization-1",
>> "Organization-2"] }
>>             }
>>         ]
>>
>> This works great with non-scoped resources since, for now on, the
>> Resource server can grant access to "Product-X" in both contexts
>> "Organization-1" and "Organization-2". Also, the Resource Server will
>> obtain new tickets if new contexts (tenants) are requested. However, when
>> we use scoped-resources, since the pushing claims are not specific to the
>> scopes being requested, the Resource Server could not determine if the
>> combination of "Product-X" and some scope is defined for an specific
>> tenant. We could support this use-case removing scopes from the equation
>> and creating non-scoped resources like "Product-X:read", "Product-X:write",
>> etc. However, while we think that this should be implemented using scopes
>> instead of non-scoped resources, we don't know how to manage claims as we
>> discussed in the first mail.
>>
>> Regards,
>> Álvaro.
>>
>> El mié., 24 jul. 2019 a las 22:34, Pedro Igor Silva (<psilva at redhat.com>)
>> escribió:
>>
>>> Hi Álvaro,
>>>
>>> You are not missing anything and that is how claims are handled. They
>>> are a permission-level (resource + scopes) info and not specific to only
>>> the scopes being requested/granted.
>>>
>>> Before finding alternatives, could you tell me how are you pushing these
>>> claims? Are you using our adapters or manually performing HTTP requests
>>> from your app?
>>>
>>> Regards.
>>>
>>> On Wed, Jul 24, 2019 at 10:20 AM Álvaro Gómez <
>>> alvaro.gomez.gimenez at tecsisa.com> wrote:
>>>
>>>> Hi,
>>>>
>>>> We are applying RBAC and CBAC models to evaluate permissions in a
>>>> multi-tenant UMA application. We are using Pushing Claims to let custom
>>>> policies determine if an user has an specific role in a provided context
>>>> (tenant) via Pushing Claims.
>>>>
>>>> Everything works fine if we use non-scoped resources but things get a
>>>> bit
>>>> confusing when we use scoped ones since the pushing-claims (representing
>>>> the tenants) end up mixed in the RPT permission claim without leaving
>>>> any
>>>> trace of the scopes with which they were pushed along. Consider the
>>>> following example:
>>>>
>>>> We have an application which manages products (represented by
>>>> resources).
>>>> There are profiles (represented by roles) which allow users to sell,
>>>> modify
>>>> or delete products (represented by scopes). A certain user may interact
>>>> with one product in the context of a tenant (Determined by the Pushing
>>>> claim) with an specific role and with some different role from other
>>>> tenant.
>>>>
>>>> - Resource:
>>>>   * product (With scopes sell and update)
>>>>
>>>> - Roles:
>>>>   * Seller
>>>>   * Product-Manager
>>>>
>>>> - Policies:
>>>>   * Is-Seller (In the Tenant specified in the Pushing Claim "tenant")
>>>>   * Is-Product-Manager (In the Tenant specified in the Pushing Claim
>>>> "tenant")
>>>>
>>>> - Permissions:
>>>>   * product:sell -> Provides the "sell" scope of the resource "product"
>>>> if
>>>> the "Is-Seller" policy evaluates to grant.
>>>>   * product:update -> Provides the "update" scope of the resource
>>>> "product" if the "Is-Product-Manager" policy evaluates to grant.
>>>>
>>>> - Users:
>>>>   * Alice -> Alice is "Seller" in the tenant "Organization-1" and is
>>>> "Product-Manager" in the tenant "Organization 2" so she should be able
>>>> to
>>>> sell products in the context of the tenant "Organization-1" and update
>>>> products in the context of "Organization-2" but neither "update"
>>>> products
>>>> in the context of "Organization-1" or sell products in the context of
>>>> "Organization-2".
>>>>
>>>> 1.- Alice requests an RPT using the following ticket:
>>>>      { "resource": "product", "resource_scopes": ["sell"], "claims": {
>>>> "tenant": ["Organization-1"] } }
>>>>
>>>>     Since Alice is "Seller" in the "Organization-1" (meaning the Policy
>>>> "Is-Seller" will evaluate to "grant" if the provided claim value is
>>>> "Organization-1" and the evaluated Identity is Alice) an RPT is emitted
>>>> with the following "permission" claim:
>>>>
>>>>     [{
>>>>        "resource": "product",
>>>>        "resource_scopes": ["sell"],
>>>>        "claims": { "tenant": ["Organization-1"] }
>>>>     }]
>>>>
>>>> 2.- Alice upgrades the previous RPT with the following ticket:
>>>>      { "resource": "product", "resource_scopes": ["update"], "claims": {
>>>> "tenant": ["Organization-2"] } }
>>>>
>>>>    Here is were things get confusing to us. We'd expect Alice to be
>>>> granted
>>>> when requesting the scope "update" in the context of "Organization-2"
>>>> since
>>>> Alice has the role "Product-Manager" in that tenant. That would be what
>>>> happened if Alice was requesting the RPT for the first time instead of
>>>> upgrading a previous one. However, since we are upgrading the RPT
>>>> obtained
>>>> in Step 1, when the policy "Is-Product-Manager" is evaluated, the claim
>>>> "tenant" is mixed with the one in Step 1 (Since they are not grouped by
>>>> scope) resulting in the following permission:
>>>>
>>>>    {
>>>>       "resource": "product",
>>>>       "resource_scopes": ["sell", "update"],
>>>>       "claims": {
>>>>           "tenant": ["Organization-1", "Organization-2"]
>>>>        }
>>>>    }
>>>>
>>>>   The policy can't evaluate to grant since Alice is not
>>>> "Product-Manager"
>>>> in both tenants "Organization-1" and "Organization-2" (Obtained through
>>>> $evaluation.getPermission().getClaims()). When evaluating this policy we
>>>> would only be interested in the pushing-claim `{ "tenant":
>>>> ["Organization-2"] }` which was pushed along with the scope "update"
>>>> (which
>>>> is the one being evaluated by the permission "product:update" associated
>>>> with this Policy).
>>>>
>>>>    Shouldn't the claims be grouped by the scopes which with they were
>>>> pushed along? (See example at the end of this text), Are we missing
>>>> something?
>>>>
>>>>    Example:
>>>>    {
>>>>       "resource": "product",
>>>>       "resource_scopes": [
>>>>           { "name": "sell", "claims": { "tenant": ["Organization-1"] }
>>>> },
>>>>           { "name": "update", "claims": { "tenant": ["Organization-2"]
>>>> } },
>>>>       ]
>>>>
>>>> Thanks in advance,
>>>> Álvaro.
>>>> _______________________________________________
>>>> keycloak-user mailing list
>>>> keycloak-user at lists.jboss.org
>>>> https://lists.jboss.org/mailman/listinfo/keycloak-user
>>>
>>>


More information about the keycloak-user mailing list