Skip to content

Migrate from RBAC v1 to RBAC v2

This is a guide for the first phase of migration of existing Insights applications from RBACv1 to Kessel. In this initial phase, the goal is to start using Kessel for authorization enforcement while keeping parity with the current behaviors. Further “workspacification” (i.e. making assets other than hosts workspace-aware) is out of scope for the initial phase and this guide.

Questions? Please raise them in #mgmt-fabric-insights-integrations

We start the migration process by identifying the patterns used by our application. The patterns should be identified individually for each asset type managed by the application. Different asset types may fall under different patterns and hence an application may use a combination of patterns. For example, an application may use a combination of:

  • permission protecting access to an org-wide setting
  • permission protecting access to inventory group aware assets (e.g. host vulnerabilities)

The following patterns are supported:

For the initial migration of RBACv1 applications to Kessel, the following simplified decision tree can be used to identify the appropriate pattern:

  • Is the resource already workspace-aware (e.g. hosts, workspaces)?
    • Do queries return fewer than ~10k results, or are more than ~80% of results accessible to the requesting user? —> Use Native
    • Do queries return more than ~10k results and fewer than ~80% of results are accessible? —> Use Native, workspace-level list
  • Can the resource be conceptualized as an “asset” (customer manages CRUD; we can imagine it as being placed in a Workspace) but is not yet workspace-aware? —> Use Default workspace
  • Is the resource “the organization” as a whole (e.g. managing an organization-wide setting) and the operation is “asset-centric” (we could imagine it being Workspace level in the future)? —> Use Root workspace
  • Is the resource “the organization” as a whole (e.g. managing an organization-wide setting) and the operation is not “asset-centric” (would never belong at a Workspace level)? —> Use Organization-level

See the Migration Pattern Reference for detailed information about each pattern, including request translations and implementation guidance.

Afterwards, we need to convert permission definitions from the legacy RBACv1 format to permission definitions expressed using the ksl language. These permission definitions are stored in .ksl files in the rbac-config repository.

The @rbac.add_v1_based_permission() extension simplifies the process of converting the legacy RBACv1 permission definitions to Kessel permissions by incorporating support for wildcard permissions.

The following rules apply to the @rbac.add_v1_based_permission() extension:

  • The app parameter should be the name of the application.
  • The resource parameter should be the singular name of the resource type.
  • The verb parameter should be the the verb.
  • The v2_perm parameter should be the name of the compound permission in Kessel. Note that the last segment of the v2 permission name will differ from the verb of the v1 permission. The following naming convention should be used:
    • read -> view
    • write -> edit

Example of a permission definition using the @rbac.add_v1_based_permission() extension:

version 0.1
namespace config_manager
import rbac
@rbac.add_v1_based_permission(app:'config_manager', resource:'profile', verb:'read', v2_perm:'config_manager_profile_view');
@rbac.add_v1_based_permission(app:'config_manager', resource:'profile', verb:'write', v2_perm:'config_manager_profile_edit');

Certain RBACv1 permissions apply to host-centric assets. These permissions are typically used to protect access to host-specific data, such as host vulnerabilities, advisories, etc.

In RBACv1, these permissions are evaluated in conjunction with the inventory:host:read permission, which can be restricted to specific workspaces (inventory groups) using attribute filters.

In Kessel, this logic can be implemented natively by combining the compound permission with the inventory:host:read permission. The rbac.add_contingent_permission() extension simplifies this process.

@rbac.add_v1_based_permission(app:'patch', resource:'system', verb:'read', v2_perm:'patch_system_view_assigned');
@rbac.add_contingent_permission(first: 'inventory_host_view', second: 'patch_system_view_assigned', contingent: 'patch_system_view');

which is equivalent to (metalanguage used for illustration purposes):

patch_system_view_assigned = patch_system_read OR patch_system_all OR patch_all_read OR patch_all_all OR all_all_all
patch_system_view = inventory_host_view AND patch_system_view_assigned

Notice that the _assigned suffix is used for the compound permission, which distinguishes it from the contingent permission that incorporates the inventory permission and which should be used for access checks.

Full example:

version 0.1
namespace patch
import rbac
import hbi
@rbac.add_v1_based_permission(app:'patch', resource:'system', verb:'read', v2_perm:'patch_system_view_assigned');
@rbac.add_contingent_permission(first: 'inventory_host_view', second: 'patch_system_view_assigned', contingent: 'patch_system_view');
@hbi.expose_host_permission(v2_perm: 'patch_system_view', host_perm: 'patch_system_view');
@rbac.add_v1_based_permission(app:'patch', resource:'system', verb:'write', v2_perm:'patch_system_edit_assigned');
@rbac.add_contingent_permission(first: 'inventory_host_view', second: 'patch_system_edit_assigned', contingent: 'patch_system_edit');
@hbi.expose_host_permission(v2_perm: 'patch_system_edit', host_perm: 'patch_system_edit');

Once the .ksl file(s) are ready, they need to be added to the rbac-config repository by opening a PR. Note that a separate config exists for each environment (stage, prod), allowing the change to be tested in stage before being released to prod.

There is no need to include the modified zed schema in the PR as an automated job will transpile the new/modified ksl files into a zed schema.

Note that the way these permissions are associated with a role remains the same, i.e. via role files.

In some cases a completely new permission may be defined in the process of onboarding to Kessel. In that case, consider defining this permission in RHEL persona-based roles (e.g. RHEL Viewer) in addition to application-specific roles (e.g. Advisor Viewer). See the Role Definitions section of the Migration Pattern Reference for additional details.

The implementation part typically consists of the following steps:

  1. Import the Kessel client library
  2. Implement the authorization check in the application code
  3. Add tests
  4. Add configuration options for the Kessel client

Kessel client libraries are available for these languages:

Implement the authorization check in the application code

Section titled “Implement the authorization check in the application code”

The authorization check is typically implemented at the middleware layer. The actual implementation differs based on the selected pattern(s).

The implementation of root workspace pattern and default workspace pattern starts with a lookup of the default/root workspace id for the given organization.

The workspace ID can be obtained from the RBAC service via a REST call:

GET /api/rbac/v2/workspaces/?type=root&with_ancestry=true
GET /api/rbac/v2/workspaces/?type=default&with_ancestry=true

The default/root workspace ID for a given organization is immutable. Cache it aggressively to avoid the latency and reliability cost of an RBAC lookup on every request. See the Caching section of the Migration Pattern Reference for details.

With the workspace id resolved, the authorization check call can be made to Kessel. The actual method called depends on the nature of the operation.

Use CheckForUpdate for performing authorization check for

  • any write operations
  • highly-sensitive read operations (e.g. access to read credentials for connecting to a customer’s cluster)

In all other cases, use Check.

The parameters provided for the Check/CheckForUpdate request for the Root/Default workspace pattern should be structured as follows:

object := &kesselv2.ResourceReference{
ResourceType: "workspace",
ResourceId: defaultOrRootWorkspaceID,
Reporter: &kesselv2.ReporterReference{
Type: "rbac",
},
}
relation = // the permission to check for
subject := &kesselv2.SubjectReference{
Resource: &kesselv2.ResourceReference{
ResourceType: "principal",
ResourceId: fmt.Sprintf("redhat/%s", principalID),
Reporter: &kesselv2.ReporterReference{
Type: "rbac",
},
},
}

Default workspace pattern implementations and PoCs:

See the Default Workspace pattern reference for additional information.

The Native, workspace-level list pattern is typically implemented by resolving the ids of all the workspaces for which the given principal possesses the given permission and then using these workspace ids to filter the assets in application database query.

The workspace ids are obtained using the StreamedListObjects method and its parameters should be defined as follows:

objectType := &kesselv2.RepresentationType{
ResourceType: "workspace",
ReporterType: "rbac",
},
relation = // the permission to check for
subject := &kesselv2.SubjectReference{
Resource: &kesselv2.ResourceReference{
ResourceType: "principal",
ResourceId: fmt.Sprintf("redhat/%s", principalID),
Reporter: &kesselv2.ReporterReference{
Type: "rbac",
},
},
}

Native, workspace-level list pattern implementations and PoCs:

See the Native workspace-level list pattern reference for additional information.

Tests should verify that your application correctly constructs and sends authorization requests to Kessel, and properly handles the responses.

  • Authorization middleware: Verify that your middleware sends the correct Check or CheckForUpdate request to Kessel for each protected endpoint, including the correct resource type, resource ID, relation (permission), and subject.
  • Allowed and denied scenarios: Test that a ALLOWED_TRUE response grants access and a ALLOWED_FALSE response returns 403.
  • Error handling: Test that Kessel errors (e.g. connection failures, timeouts) result in an appropriate error response (typically 500), not a silent allow.
  • Principal types: Test with both user principals (identity.User.UserId) and service account principals (identity.ServiceAccount.UserId).
  • Workspace resolution: If using the Default or Root Workspace pattern, test that the workspace ID is resolved correctly and that resolution failures are handled.
  • Feature flag fallback: Test that KESSEL_ENABLED=false cleanly falls back to existing v1 authorization with no side effects, and that toggling it on produces the same authorization outcomes as v1 for equivalent permissions.
  • Parity between v1 and v2: During the migration period, verify that enabling Kessel does not change authorization outcomes compared to the existing v1 path. The insights-rbac parity checker demonstrates this pattern by asserting that v1 operations and v2 relation tuples remain consistent after every mutation.
  • StreamedListObjects (workspace-level list pattern): Test handling of the streaming response, including partial results, empty streams when the user has no access, and correct use of the returned workspace IDs as database query filters.
  • Contingent permissions (host-centric assets): If using @rbac.add_contingent_permission(), test that a user needs both the inventory host permission and the app-specific permission. A user holding only one should be denied.

Mock the Kessel gRPC client in your unit tests rather than connecting to a live Kessel instance. Create a mock implementation of the Kessel Inventory Service client that:

  • Records the requests it receives for assertion
  • Returns configurable responses (ALLOWED_TRUE, ALLOWED_FALSE) or errors

Then assert that:

  • The resource reference in the request has the expected type, ID, and reporter
  • The relation matches the permission being checked
  • The subject reference contains the correct principal ID and type
  • Config Manager (Go) - Uses a mock gRPC client with table-driven tests covering authorization, error handling, and both principal types.
  • insights-rbac (Python) - Uses an in-memory tuple store (InMemoryTuples) as a SpiceDB substitute, with consistency assertions that verify both database state and relation tuples after each operation.

The application needs to connect to two Kessel services: the Inventory API (for authorization checks) and the Relations API (for relation management). The following configuration options should be added:

  • KESSEL_ENABLED — feature flag to toggle Kessel authorization on/off
  • RELATION_API_SERVER — gRPC address for the Kessel Relations API (default: localhost:9000)
  • INVENTORY_API_SERVER — gRPC address for the Kessel Inventory API (default: localhost:9000)
  • Auth credentials per service (e.g. RELATION_API_CLIENT_ID, RELATION_API_CLIENT_SECRET, INVENTORY_API_CLIENT_ID, INVENTORY_API_CLIENT_SECRET)
  • Token URL per service (e.g. RELATIONS_API_TOKEN_URL, INVENTORY_API_TOKEN_URL)

When running in a Clowder-managed environment, the Kessel service endpoints can be resolved automatically using Clowder’s DependencyEndpoints instead of manually configuring URLs.

To enable this, declare kessel-inventory and kessel-relations as dependencies in your ClowdApp manifest:

optionalDependencies:
- kessel-inventory
- kessel-relations

Then resolve the endpoints from Clowder at startup, falling back to the environment variables if the dependency is not available:

from app_common_python import DependencyEndpoints
if CLOWDER_ENABLED:
try:
hostname = DependencyEndpoints["kessel-inventory"]["api"].hostname
INVENTORY_API_SERVER = f"{hostname}:9000"
except KeyError:
pass # fall back to env var / default
try:
hostname = DependencyEndpoints["kessel-relations"]["api"].hostname
RELATION_API_SERVER = f"{hostname}:9000"
except KeyError:
pass # fall back to env var / default

See insights-rbac’s settings.py and Config Manager’s configuration for example implementations.

Follow Using Kessel in production to set up your service to use Kessel in stage and prod.