Keycloak Policy Enforcement with URI that maps to multiple resources

85 Views Asked by At

I'm using Keycloak (21.0.0) to manage the authorization for my Spring Boot (2.7.10) application.
I have configured some resources that can only be accessed by certain users.
This works great!
Now I want to allow the admin to access all resources.

Here is an overview of my Keycloak configuration.

** Authorization - Settings **  
Decision strategy: Affirmative 

**Authorization - Resources**      
name: all comments
URIs: /api/comments/*
Authorization scopes: GET

name: first comment
URIs: /api/comments/1
Authorization scopes: GET

**Authorization - Scopes**  
name: GET

**Authorization - Policies**      
[role policy]
Name: admin
Roles: admin

[user policy]  
Name: fubar
Users: fubar

**Authorization - Permissions**  
[Scope based]  
Name: admin_can_read_all_comments
Resources: all comments
Authorization scopes: GET
Policies: admin

[Scope based]      
Name: fubar_can_read_first_comment
Resources: first comments
Authorization scopes: GET
Policies: fubar

When I evaluate Fubar in the Keycloak web client, the all_comments resource evaluates to DENY and the first_comment resource evaluates to PERMIT. When I evaluate admin in the Keycloak web client, the all_comments resource evaluates to PERMIT and the first_comment resource evaluates to DENY.

I would expect that both fubar and admin can GET /api/comments/1
But in reality the admin get's 403 when he tries to GET /api/comments/1
Is this behavior expected?

I'm guessing this behavior is related to the implementation of the keycloak-policy-enforcer. What will the policy enforcer do when a URI maps to multiple resources with a different outcome?

Here is my Spring Boot Config:

keycloak:
  securityConstraints:
    - authRoles:
        - "*"
  securityConstraints[0]:
    securityCollections[0]:
      patterns[0]: /*
  principal-attribute: preferred_username
  auth-server-url: http://kc-host:8080/auth
  realm: my-realm
  resource: my-authz-client
  bearer-only: true
  credentials:
    secret: s3cr3t
  policy-enforcer-config:
    http-method-as-scope: true
    lazy-load-paths: true
    enforcement-mode: enforcing
    path-cache-config:
      lifespan: 0
1

There are 1 best solutions below

5
On

In Keycloak, evaluating a user's access to a resource typically involves the use of policies and permissions defined within Keycloak's Authorization Services.

You need to enable and configure Authorization Services in Keycloak. This involves setting up policies, permissions, resources, and scopes.

Resources : The things you want to protect, such as APIs, web pages, or any other resource. It is target of API.

Scopes: It is action, like a read, write, delete to performed on the resource.

Permission: the association between resources and scopes. It decide allow or not to access a resource.

Policy : Define the conditions under which access to a resource is granted.

So The "fubar" user is assigned the "fubar" role. The "admin" user is assigned the "admin" role.

The "fubar policy" has two roles, admin role and fubar role. The "admin policy" has one role, admin role only.

The "fubar permission" has fubar policy and 1st comment resource. The "admin permission" has admin policy and all comment resource.

Evaluate scenario 1,2,3,4 cases

Case 1 : admin user access all comment resource

Case 2 : admin user access 1st comment resource

Case 3 : admin user access all comment resource

Case 4 : admin user access 1st comment resource

Reason explain

See the following picture. (Why)

Case 1 : all comment resource has admin policy by all permission admin policy has admin role, admin role has admin user. so, admin user can access all resource.

Case 2 : 1st comment resource has fubar policy by 1st permission fubar policy has admin role, admin role has admin user. so, admin user can access 1st resource.

Case 3 : all comment resource has admin policy by all permission admin policy has admin role, admin role has admin user. so, fubar user can't access all resource.

Case 4 : 1st comment resource has fubar policy by 1st permission fubar policy has fubar role, fubar role has fubar user. so, fubar user can access 1st resource.

enter image description here

If the "admin" user is accessing both resources despite being assigned only the "admin" role

This fubar_policy can do it assign role for both my-client admin & my-client fubar role

enter image description here

This admin_policy only assign role for my-client admin role

enter image description here

Client roles

enter image description here

Assign user for roles

enter image description here

enter image description here

Resource setup

enter image description here

Scope & Permission setup

enter image description here

Mapping policy and permission

enter image description here

Result

Admin permit both resources

enter image description here

fubar only permit for first_comment

enter image description here

Evaluate API

{Keycloak URL}/admin/realms/my-realm/clients/{cileint id}/authz/resource-server/policy/evaluate

enter image description here

Payload

{
  "roleIds": [],
  "userId": "{user uuid}",
  "resources": [
    {
      "name": "first_comment",
      "owner": {
        "id": "{client id}",
        "name": "{client name}"
      },
      "ownerManagedAccess": false,
      "displayName": "{display resource name}",
      "attributes": {},
      "_id": "{resource id}",
      "uris": ["/api/comments/1"],
      "scopes": [
        {
          "id": "{scope id}",
          "name": "GET",
          "iconUri": ""
        }
      ],
      "icon_uri": ""
    }
  ],
  "entitlements": false,
  "context": { "attributes": {} }
}

enter image description here

Response Example

{
    "results": [
        {
            "resource": {
                "name": "first_comment with scopes [GET]",
                "_id": "d8d416f3-c4eb-4157-a038-2828f279ae90"
            },
            "scopes": [
                {
                    "id": "97406598-494a-47c4-bcdb-ff763d18717a",
                    "name": "GET"
                }
            ],
            "policies": [
                {
                    "policy": {
                        "id": "33b2fe31-293f-4029-8e7d-25d4dd15f853",
                        "name": "fubar_can_read_first_comment",
                        "description": "",
                        "type": "scope",
                        "resources": [
                            "first_comment"
                        ],
                        "scopes": [
                            "GET"
                        ],
                        "logic": "POSITIVE",
                        "decisionStrategy": "AFFIRMATIVE",
                        "config": {}
                    },
                    "status": "PERMIT",
                    "associatedPolicies": [
                        {
                            "policy": {
                                "id": "90413ad9-6db7-49fa-889f-c408d3f6db63",
                                "name": "fubar_policy",
                                "description": "",
                                "type": "role",
                                "resources": [],
                                "scopes": [],
                                "logic": "POSITIVE",
                                "decisionStrategy": "UNANIMOUS",
                                "config": {}
                            },
                            "status": "PERMIT",
                            "associatedPolicies": [],
                            "scopes": []
                        }
                    ],
                    "scopes": []
                }
            ],
            "status": "PERMIT",
            "allowedScopes": [
                {
                    "id": "97406598-494a-47c4-bcdb-ff763d18717a",
                    "name": "GET"
                }
            ]
        }
    ],
    "entitlements": false,
    "status": "PERMIT",
    "rpt": {
        "exp": 1710303168,
        "iat": 1710302868,
        "jti": "5c9dd6b4-2adc-42e4-bf91-00268a25a369",
        "aud": "my-client",
        "sub": "fe955869-c533-4433-b947-e9d91f00217a",
        "typ": "Bearer",
        "azp": "my-client",
        "session_state": "31e60b44-3c4b-4b6c-b06c-c8d215040ec3",
        "acr": "1",
        "allowed-origins": [
            "http://localhost:8180/api/*"
        ],
        "realm_access": {
            "roles": [
                "offline_access",
                "uma_authorization",
                "default-roles-my-realm"
            ]
        },
        "resource_access": {
            "my-client": {
                "roles": [
                    "fubar"
                ]
            },
            "account": {
                "roles": [
                    "manage-account",
                    "manage-account-links",
                    "view-profile"
                ]
            }
        },
        "authorization": {
            "permissions": [
                {
                    "scopes": [
                        "GET"
                    ],
                    "rsid": "d8d416f3-c4eb-4157-a038-2828f279ae90",
                    "rsname": "first_comment"
                }
            ]
        },
        "scope": "email profile",
        "sid": "31e60b44-3c4b-4b6c-b06c-c8d215040ec3",
        "email_verified": true,
        "preferred_username": "fubar",
        "email": "[email protected]"
    }
}

enter image description here