Implement bulk authorization methods in KeycloakAuthManager - #70647
Conversation
Implements batch_is_authorized_connection, batch_is_authorized_dag, batch_is_authorized_pool, batch_is_authorized_variable, filter_authorized_connections, filter_authorized_pools, and filter_authorized_variables in KeycloakAuthManager to reduce the number of HTTP calls made to Keycloak for authorization checks, following the existing pattern used by filter_authorized_dag_ids. Closes apache#70582
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
|
This implementation matches the implementation in |
|
Thanks for the suggestion! Looking at _is_batch_authorized/_get_batch_payload, it doesn't currently support per-permission context attributes (claim_token), which is_authorized_dag/is_authorized_connection/etc rely on for resource_id-level checks. Would extending _get_batch_payload to support per-item attributes be in scope here, or should this refactor apply only where resource_id isn't needed? |
|
looking at the code, I'm not clear on how the resource_id is being used in policy enforcement, given that the standard keycloak client uses resource type to make decisions based on role / group membership. I think its therefore fine to drop it from the batch authorization requests. Happy to be corrected if I've missed something. |
…refactor - Rewrite batch_is_authorized_* tests to mock _is_batch_authorized instead of _is_authorized - Redesign filter_authorized_connections/pools/variables tests as all-or-nothing (resource_id was dropped from batch requests, so per-item filtering is no longer possible) - Drop 3 cache-hit tests: single_flight caching was removed from these methods in the batch refactor - Mark test_batch_is_authorized_dag_with_access_entity as xfail pending decision on whether dag_entity attribute should be preserved in batch calls
|
Thanks for the pointer to _is_batch_authorized — I've refactored batch_is_authorized_connection, batch_is_authorized_dag, batch_is_authorized_pool, batch_is_authorized_variable, filter_authorized_connections, filter_authorized_pools, and filter_authorized_variables to use it. This reduces each of these to a single Keycloak call instead of N parallel calls. A few things worth flagging explicitly since they change behavior:
Let me know your thoughts on 1 and 2 in particular — I can adjust the implementation depending on which tradeoffs are acceptable. |
vincbeck
left a comment
There was a problem hiding this comment.
Given the last comment, I am requesting some changes. The goal of Keycloak auth manager is to have an auth manager that support fine grained access. I have not looked at the code but given this comment , the last developments actually REMOVED the fine grained access feature. Big no no
|
Digging into this further to understand the actual conflict between the two suggestions: I confirmed that Keycloak resources are registered generically per-type, not per-instance — see resources.py's KeycloakResource enum and commands.py:350 (standard_resources = [(resource.value, scopes) for resource in KeycloakResource]). There's a single "Connection" resource in Keycloak, not one resource per connection ID. Per-instance authorization for connections/pools/variables/dags works entirely through the resource_id claim, pushed via claim_token in _get_payload (see _is_authorized, line ~422). The problem: Keycloak's batch UMA-ticket endpoint (_get_batch_payload) takes a single permission array and does not support a claim_token at all — there's one shared claim context for the whole request, not one per permission item. That means there is no way to batch N different resource_id values into a single Keycloak call while still discriminating between them. This is different from filter_authorized_menu_items, where each menu category (Assets, Connections, etc.) actually is its own registered Keycloak resource — no resource_id claim is needed there, since the resource name itself carries the identity, so batching works cleanly. So @vincbeck is right that the current batch refactor silently drops fine-grained access — and @stephen-bracken's suggestion, while a great optimization for resources that are individually registered in Keycloak (like menu items), doesn't hold for connections/pools/variables/dags, which rely on the resource_id claim mechanism instead. Proposal: revert batch_is_authorized_connection, batch_is_authorized_dag, batch_is_authorized_pool, batch_is_authorized_variable, filter_authorized_connections, filter_authorized_pools, and filter_authorized_variables back to the original N-parallel-calls-via-is_authorized_* approach (with ThreadPoolExecutor + single_flight caching, as before) — this is the only way to preserve per-instance checks given how these resources are registered. I'll keep _is_batch_authorized reserved for cases where the resource itself is individually registered in Keycloak. Let me know if this matches your understanding, or if there's a way to register per-instance resources in Keycloak that I'm missing. |
Yep please do that and we'll go from there. But then what is the change you are proposing? Please keep your explanations concise |
…ble auth Keycloak resources are registered generically per-type (see resources.py's KeycloakResource enum and commands.py's standard_resources loop), not per-instance. Per-instance authorization for connections/pools/variables/dags relies entirely on the resource_id claim, pushed via claim_token in _get_payload. Keycloak's batch UMA-ticket endpoint (_get_batch_payload) does not support claim_token — there is a single shared claim context per request, not one per permission item. This means true single-call batching is not possible for these resource types without silently losing per-instance authorization (all-or-nothing instead of per-resource). Reverts batch_is_authorized_connection, batch_is_authorized_dag, batch_is_authorized_pool, batch_is_authorized_variable, filter_authorized_connections, filter_authorized_pools, and filter_authorized_variables back to the N-parallel-calls approach via ThreadPoolExecutor + single_flight caching, which is the only approach that preserves fine-grained per-resource authorization given how these resources are registered in Keycloak. _is_batch_authorized remains reserved for resources that are individually registered in Keycloak, such as menu items.
|
Reverted batch_is_authorized_connection, batch_is_authorized_dag, batch_is_authorized_pool, batch_is_authorized_variable, filter_authorized_connections, filter_authorized_pools, and filter_authorized_variables back to the original N-parallel-calls approach (ThreadPoolExecutor + single_flight caching) — fine-grained per-resource authorization is fully restored, no behavior change from before this PR touched them. _is_batch_authorized stays unused by this PR now (it's only exercised by the pre-existing filter_authorized_menu_items, which is safe to batch since menu items are individually registered Keycloak resources). Net result: this PR now only adds the new batch_is_authorized_* / filter_authorized_* public API surface (per #70582), with the same call pattern and semantics as the existing is_authorized_* methods underneath. All 248 tests pass. |
|
Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions. |
Closes #70582
Implements the missing batch/filter authorization methods in
KeycloakAuthManager, following the existing pattern used by
filter_authorized_dag_ids (parallel HTTP calls via ThreadPoolExecutor,
with caching via single_flight).
Tests added covering normal filtering, empty input, all-denied, and
cache-hit scenarios for each new filter method.
^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named
{pr_number}.significant.rstor{issue_number}.significant.rst, in newsfragments.