Skip to content

Conversation

@fstavela
Copy link
Contributor

@fstavela fstavela commented Dec 16, 2025

Overview

This PR is being created to address RHINENG-22377.

It implements the use of bulk access check against Kessel, which makes the API requests faster.

Unfortunately, when we enable Kessel Phase 1, the entire test run takes longer now, despite the API requests actually being faster. I don't think this is caused by an issue on the HBI side though. It looks like the bulk access check uses some caching, or something else that makes it take longer until the hosts sync gets propagated to these checks. Here is a detailed description of the issue: https://redhat-internal.slack.com/archives/C08GTNAFZ55/p1766401500404089?thread_ts=1763656513.607469&cid=C08GTNAFZ55

PR Checklist

  • Keep PR title short, ideally under 72 characters
  • Descriptive comments provided in complex code blocks
  • Include raw query examples in the PR description, if adding/modifying SQL query
  • Tests: validate optimal/expected output
  • Tests: validate exceptions and failure scenarios
  • Tests: edge cases
  • Recovers or fails gracefully during potential resource outages (e.g. DB, Kafka)
  • Uses type hinting, if convenient
  • Documentation, if this PR changes the way other services interact with host inventory
  • Links to related PRs

Secure Coding Practices Documentation Reference

You can find documentation on this checklist here.

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Adopt Kessel bulk permission checks for inventory resources and tighten validation and error handling around permission lookups.

New Features:

  • Add bulk permission checking via Kessel's CheckBulk API for inventory resources.

Bug Fixes:

  • Ensure bulk permission checks fail when Kessel omits resources or returns item-level errors.
  • Prevent update permission checks from incorrectly deriving an edit relation from a view permission and require a non-view permission instead.
  • Disallow empty resource ID lists for bulk permission checks to avoid ambiguous calls.

Enhancements:

  • Refactor construction of Kessel resource object references into a shared helper for reuse across single and bulk checks.
  • Align update permission checks with standard request construction and typed response handling.

Summary by Sourcery

Adopt Kessel bulk permission checks for inventory resources and tighten validation for permission lookups.

New Features:

  • Add bulk permission checking for multiple resources via Kessel's CheckBulk API.

Bug Fixes:

  • Fail bulk permission checks when Kessel omits resources from the response or returns item-level errors.
  • Disallow empty resource ID lists for both standard and update bulk permission checks to avoid ambiguous calls.
  • Prevent update permission checks from using a derived edit relation from a view permission and instead require a non-view permission.

Enhancements:

  • Refactor construction of Kessel resource object references into a shared helper used by single and bulk checks.
  • Align update permission checks with standard request construction and typed response handling.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 16, 2025

Reviewer's Guide

Refactors Kessel inventory permission checks to introduce a shared resource reference builder, use Kessel’s CheckBulk API for bulk read checks with stricter validation, and tighten semantics and typing for update checks and bulk calls.

Sequence diagram for new Kessel CheckBulk read permission flow

sequenceDiagram
    participant Caller
    participant KesselClient
    participant InventoryService

    Caller->>KesselClient: _check_bulk_resources(subject_ref, permission, resource_ids)
    KesselClient->>KesselClient: validate resource_ids not empty
    KesselClient->>KesselClient: build items with _build_object_reference(permission, resource_id) for each id
    KesselClient->>InventoryService: CheckBulk(CheckBulkRequest(items), timeout)
    InventoryService-->>KesselClient: CheckBulkResponse(pairs)

    KesselClient->>KesselClient: compare len(response.pairs) to len(resource_ids)
    alt missing_pairs
        KesselClient->>KesselClient: log warning and return False
    else all_pairs_present
        loop each pair in response.pairs
            alt pair has error
                KesselClient->>KesselClient: log warning and return False
            else not allowed
                KesselClient->>KesselClient: return False
            end
        end
        KesselClient-->>Caller: True
    end
Loading

Class diagram for updated Kessel inventory permission checks

classDiagram
    class KesselClient {
        - inventory_svc
        - timeout
        + _build_subject_reference(current_identity Identity) subject_reference_pb2.SubjectReference
        + _build_object_reference(permission KesselPermission, resource_id str) resource_reference_pb2.ResourceReference
        + _check_single_resource(subject_ref subject_reference_pb2.SubjectReference, permission KesselPermission, resource_id str) bool
        + _check_bulk_resources(subject_ref subject_reference_pb2.SubjectReference, permission KesselPermission, resource_ids list~str~) bool
        + _check_single_resource_for_update(subject_ref subject_reference_pb2.SubjectReference, permission KesselPermission, resource_id str) bool
        + _check_bulk_resources_for_update(subject_ref subject_reference_pb2.SubjectReference, permission KesselPermission, resource_ids list~str~) bool
    }

    class KesselPermission {
        + resource_type ResourceType
        + resource_permission str
    }

    class ResourceType {
        + name str
        + namespace str
    }

    class InventoryService {
        + Check(request check_request_pb2.CheckRequest, timeout float) CheckResponse
        + CheckBulk(request check_bulk_request_pb2.CheckBulkRequest, timeout float) CheckBulkResponse
        + CheckForUpdate(request check_request_pb2.CheckRequest, timeout float) CheckForUpdateResponse
    }

    KesselClient --> InventoryService : uses
    KesselClient --> KesselPermission : uses
    KesselPermission --> ResourceType : uses

    KesselClient ..> subject_reference_pb2.SubjectReference : builds
    KesselClient ..> resource_reference_pb2.ResourceReference : builds
    KesselClient ..> check_request_pb2.CheckRequest : builds
    KesselClient ..> check_bulk_request_pb2.CheckBulkRequest : builds
    InventoryService ..> CheckResponse
    InventoryService ..> CheckBulkResponse
    InventoryService ..> CheckForUpdateResponse
Loading

Flow diagram for single and bulk update permission checks

flowchart TD
    A[Start bulk update check
    _check_bulk_resources_for_update] --> B{resource_ids empty?}
    B -- Yes --> C[Raise ValueError
    resource_ids can't be empty]
    B -- No --> D[Iterate each resource_id]
    D --> E[_check_single_resource_for_update]

    subgraph SingleUpdateCheck
        E --> F{permission.resource_permission == view?}
        F -- Yes --> G[Log error
        Raise ValueError]
        F -- No --> H[Build object_reference
        via _build_object_reference]
        H --> I[Build CheckRequest
        with relation = permission.resource_permission]
        I --> J[Call inventory_svc.CheckForUpdate]
        J --> K{response.allowed == ALLOWED_TRUE?}
        K -- No --> L[Return False to bulk]
        K -- Yes --> M[Return True to bulk]
    end

    L --> N[Bulk returns False]
    M --> O{all resource_ids processed and allowed?}
    O -- Yes --> P[Bulk returns True]
    O -- No --> N
    N --> Q[End]
    P --> Q[End]
Loading

File-Level Changes

Change Details Files
Introduce a shared helper to build Kessel resource object references and reuse it in single and update checks.
  • Add _build_object_reference helper that constructs ResourceReference from a KesselPermission and resource_id.
  • Update _check_single_resource to construct its CheckRequest using the new helper instead of inlined ResourceReference creation.
  • Update _check_single_resource_for_update to reuse the shared object reference helper.
lib/kessel.py
Replace per-item Check calls with Kessel’s CheckBulk API for bulk read permissions and enforce stricter response validation.
  • Change _check_bulk_resources to construct a CheckBulkRequest with one item per resource, using the shared object reference helper.
  • Call inventory_svc.CheckBulk with a timeout and type the response as CheckBulkResponse.
  • Validate that the number of response pairs matches the number of requested resources, logging a warning and denying when they differ.
  • Treat any item-level error in the bulk response as a denial, logging a warning, and require all items to be explicitly allowed.
  • Reject empty resource_ids lists in bulk read checks by raising ValueError to avoid ambiguous requests.
lib/kessel.py
Tighten update permission checking semantics and bulk update behavior.
  • Prevent update checks from deriving an edit relation from a view permission by raising ValueError when resource_permission is 'view' and logging an error.
  • Use the original permission.resource_permission for CheckForUpdate instead of constructing a derived relation.
  • Type the CheckForUpdate response as CheckForUpdateResponse and keep the allowed check consistent with other methods.
  • Reject empty resource_ids lists for bulk update checks by raising ValueError and document that CheckBulk is not used for update checks, iterating over single-resource update checks instead.
lib/kessel.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@fstavela fstavela force-pushed the fstavela/kessel-bulk-check branch from 3879767 to ad0d76d Compare December 16, 2025 13:11
@fstavela
Copy link
Contributor Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In _check_bulk_resources, consider validating that len(response.pairs) matches the number of request items and logging or failing if they differ, to guard against partial or malformed bulk responses.
  • The update_permission = permission.resource_permission.replace("view", "edit") pattern is a bit brittle (e.g., if the string format changes or contains view elsewhere); consider centralizing permission mapping in a helper or enum rather than relying on string replacement.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_check_bulk_resources`, consider validating that `len(response.pairs)` matches the number of request `items` and logging or failing if they differ, to guard against partial or malformed bulk responses.
- The `update_permission = permission.resource_permission.replace("view", "edit")` pattern is a bit brittle (e.g., if the string format changes or contains `view` elsewhere); consider centralizing permission mapping in a helper or enum rather than relying on string replacement.

## Individual Comments

### Comment 1
<location> `lib/kessel.py:164-165` </location>
<code_context>
+            for resource_id in resource_ids
+        ]
+
+        bulk_request = check_bulk_request_pb2.CheckBulkRequest(items=items)
+        response: CheckBulkResponse = self.inventory_svc.CheckBulk(bulk_request, timeout=self.timeout)
+
+        # Check that all resources are allowed
</code_context>

<issue_to_address>
**issue:** Consider short‑circuiting for empty `resource_ids` instead of calling `CheckBulk` with no items.

The old version returned `True` for an empty `resource_ids` list via `all(...)` and made no RPCs. The new version will always send `CheckBulk(items=[])`, which may be wasted work or even fail if the service doesn’t treat empty batches as a no‑op. Adding a guard like `if not resource_ids: return True` before constructing `items` would preserve the previous behavior and avoid a potentially problematic call.
</issue_to_address>

### Comment 2
<location> `lib/kessel.py:202-205` </location>
<code_context>
-            if not self._check_single_resource_for_update(subject_ref, permission, resource_id):
-                return False
-        return True
+        # For updates, we might need a different relation like "edit" or "write"
+        # This depends on how permissions are modeled in Kessel
+        update_permission = permission.resource_permission.replace("view", "edit")
+        return self._check_bulk_resources(subject_ref, permission, resource_ids, relation=update_permission)

     def ListAllowedWorkspaces(self, current_identity: Identity, relation) -> list[str]:
</code_context>

<issue_to_address>
**🚨 question (security):** Clarify whether bulk update checks should use `CheckBulk` or the `CheckForUpdate` RPC for consistency with single‑resource checks.

Single‑resource updates use `CheckForUpdate`, but the bulk path now goes through `_check_bulk_resources`, which calls `CheckBulk` on the standard check service and only swaps the relation string. If `Check`/`CheckBulk` differ from `CheckForUpdate` in semantics or auditing, bulk updates may be authorized under different or weaker rules. Please either add a bulk variant that mirrors `CheckForUpdate` semantics, or explicitly confirm/document that `CheckBulk` is the correct mechanism for update checks as well.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@fstavela fstavela force-pushed the fstavela/kessel-bulk-check branch 4 times, most recently from a147d86 to e03fba2 Compare December 16, 2025 14:59
@fstavela
Copy link
Contributor Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The new ValueError on empty resource_ids in both bulk methods changes behavior from a no-op to an exception; consider whether callers expect empty lists and, if so, either treat them as trivially allowed or validate earlier at the boundary where the IDs are collected.
  • The update check logic relies on the literal permission name 'view' for validation; using an enum/constant or a more explicit notion of read vs write permissions would reduce brittleness if permission names change.
  • In the bulk check, you treat any length mismatch between response.pairs and resource_ids as a blanket denial; if the API can partially succeed, consider correlating pairs to resource IDs and failing only the missing ones rather than all of them.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `ValueError` on empty `resource_ids` in both bulk methods changes behavior from a no-op to an exception; consider whether callers expect empty lists and, if so, either treat them as trivially allowed or validate earlier at the boundary where the IDs are collected.
- The update check logic relies on the literal permission name `'view'` for validation; using an enum/constant or a more explicit notion of read vs write permissions would reduce brittleness if permission names change.
- In the bulk check, you treat any length mismatch between `response.pairs` and `resource_ids` as a blanket denial; if the API can partially succeed, consider correlating pairs to resource IDs and failing only the missing ones rather than all of them.

## Individual Comments

### Comment 1
<location> `lib/kessel.py:176-179` </location>
<code_context>
+        # Check that all resources are allowed
+        for pair in response.pairs:
+            # If there's an error for this item, treat as denied
+            if pair.HasField("error"):
+                logger.warning(f"Kessel CheckBulk error for resource: {pair.error.message}")
+                return False
+            if pair.item.allowed != allowed_pb2.Allowed.ALLOWED_TRUE:
</code_context>

<issue_to_address>
**suggestion:** Include resource context in CheckBulk error logs to ease debugging.

Right now the warning only logs `pair.error.message`, which makes it hard to know which resource failed when many IDs are checked. Please include at least the resource ID (and ideally the relation) in the log message, e.g. `logger.warning("Kessel CheckBulk error for resource %s (%s): %s", pair.item.object.resource_id, pair.item.relation, pair.error.message)` to make production debugging easier.

```suggestion
            # If there's an error for this item, treat as denied
            if pair.HasField("error"):
                logger.warning(
                    "Kessel CheckBulk error for resource %s (%s): %s",
                    pair.item.object.resource_id,
                    pair.item.relation,
                    pair.error.message,
                )
                return False
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@fstavela fstavela force-pushed the fstavela/kessel-bulk-check branch 4 times, most recently from 3ead85d to 479616b Compare December 22, 2025 09:03
@fstavela fstavela marked this pull request as ready for review December 22, 2025 12:33
@fstavela fstavela requested a review from a team as a code owner December 22, 2025 12:33
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In _check_bulk_resources, when logging item-level errors, consider including the resource_id (e.g., by correlating pair back to the original request item) to make troubleshooting which resource failed more straightforward.
  • The new ValueError on empty resource_ids in both bulk check methods changes the previous behavior; if that’s intentional, double-check that all callers validate input or handle this exception to avoid unexpected failures at runtime.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_check_bulk_resources`, when logging item-level errors, consider including the `resource_id` (e.g., by correlating `pair` back to the original request item) to make troubleshooting which resource failed more straightforward.
- The new `ValueError` on empty `resource_ids` in both bulk check methods changes the previous behavior; if that’s intentional, double-check that all callers validate input or handle this exception to avoid unexpected failures at runtime.

## Individual Comments

### Comment 1
<location> `lib/kessel.py:179-180` </location>
<code_context>
+            for resource_id in resource_ids
+        ]
+
+        bulk_request = check_bulk_request_pb2.CheckBulkRequest(items=items)
+        response: CheckBulkResponse = self.inventory_svc.CheckBulk(bulk_request, timeout=self.timeout)
+
+        # Check that all resources are present in the response
</code_context>

<issue_to_address>
**question (bug_risk):** Consider clarifying/guaranteeing how response pairs map to requested resource_ids

The logic currently only checks `len(response.pairs) == len(resource_ids)` and then relies on iteration order, which assumes:
- The backend returns pairs in the same order as `items`, and
- There is at most one result per `resource_id` (even if there are duplicates).

If either assumption is false, pairs may be misaligned with the intended `resource_id`, leading to incorrect accept/reject decisions. If the protocol can echo back an identifier (e.g., `resource_id` or correlation id), consider mapping by that field instead of relying on count and order alone.
</issue_to_address>

### Comment 2
<location> `lib/kessel.py:191-196` </location>
<code_context>
+            return False
+
+        # Check that all resources are allowed
+        for pair in response.pairs:
+            # If there's an error for this item, treat as denied
+            if pair.HasField("error"):
+                logger.warning(f"Kessel CheckBulk error for resource: {pair.error.message}")
+                return False
+            if pair.item.allowed != allowed_pb2.Allowed.ALLOWED_TRUE:
+                return False
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Double-check that `allowed` belongs on `pair.item` rather than on `pair` or a separate result field

This code assumes `allowed` is on `pair.item` (`pair.item.allowed`). Many bulk APIs instead attach the decision at the pair level (e.g., `pair.allowed`, `pair.result.allowed`), with `item` echoed unchanged. If that’s true here, `pair.item.allowed` will always be unset and this check will misread the result. Please confirm the bulk response schema and ensure we’re reading `allowed` from the correct field, consistent with the non-bulk `CheckResponse.allowed` path.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the code 👍
I'm bit uneasy about the pipeline not testing it, but I'll trust your judgement, I belive you've tested this manually, right?

Copy link
Member

@rodrigonull rodrigonull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ezr-ondrej ezr-ondrej force-pushed the fstavela/kessel-bulk-check branch from 479616b to 2634843 Compare December 22, 2025 16:55
@fstavela fstavela force-pushed the fstavela/kessel-bulk-check branch 2 times, most recently from a673775 to c0f6db5 Compare December 23, 2025 15:54
@ezr-ondrej ezr-ondrej force-pushed the fstavela/kessel-bulk-check branch from c0f6db5 to a360a5d Compare December 24, 2025 10:58
@ezr-ondrej ezr-ondrej force-pushed the fstavela/kessel-bulk-check branch from a360a5d to 04623f3 Compare December 24, 2025 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants