Skip to content

Reduce resolver work for external authorizers - #5662

Open
dimas-b wants to merge 2 commits into
apache:mainfrom
dimas-b:ext-authz-resolve
Open

dimas-b wants to merge 2 commits into
apache:mainfrom
dimas-b:ext-authz-resolve

Conversation

@dimas-b

@dimas-b dimas-b commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
  • Migrate OPA and Ranger to using .resolveSelections()

  • Use AuthorizationState as the accumulator of Resolvable selectors shared between service endpoint implementations and Authorizers

  • The only non-trivial case of using selectors outside of Authorizer code is in PolarisAdminService.authorizeBasicTopLevelEntityOperationOrThrow()

  • Ranger Auth always uses REQUESTED_TOP_LEVEL_ENTITIES due to its reliance on the synthetic root container.

  • Extract common selection code into NonRBACResolutionSemantics

  • Update TestPolarisAuthorizer to represent general-purpose non-Polaris (external) behaviours in tests.

Relates to #5476

Checklist

  • 🛡️ Don't disclose security issues! (contact security@apache.org)
  • 🔗 Clearly explained why the changes are needed, or linked related issues: Fixes #
  • 🧪 Added/updated tests with good coverage, or manually tested (and explained how)
  • 💡 Added comments for complex logic
  • 🧾 Updated CHANGELOG.md (if needed)
  • 📚 Updated documentation in site/content/in-dev/unreleased (if needed)

@dimas-b

dimas-b commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

FYI: @gracechen09 @sungwy : this is still WIP, but please feel free to post early review comments.

@dimas-b dimas-b changed the title WIP: Minimize resolver work for external authorizers WIP: Reduce resolver work for external authorizers Sep 30, 2026
@dimas-b
dimas-b force-pushed the ext-authz-resolve branch 4 times, most recently from ca1c1b9 to b27c77e Compare September 30, 2026 23:22
when(resolutionManifest.resolveAll()).thenReturn(successStatus);
when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
when(resolutionManifest.getIsPassthroughFacade()).thenReturn(false);
doAnswer(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This mock happens to be unnecessary 🤷

@dimas-b dimas-b changed the title WIP: Reduce resolver work for external authorizers Reduce resolver work for external authorizers Oct 1, 2026
@dimas-b
dimas-b requested review from adutra and sungwy October 1, 2026 01:14
@dimas-b
dimas-b force-pushed the ext-authz-resolve branch from 1aa359b to 49d87e4 Compare October 1, 2026 01:26
@dimas-b
dimas-b marked this pull request as ready for review October 1, 2026 01:26
Comment on lines +76 to +78
public void resolve() {
if (selections.isEmpty()) {
resolutionManifest.resolveAll();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I find this semantic a bit confusing - can we just have the Authorizer selectAll() before resolving?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The built-in Authorizer does that.

For OPA and Ranger some selections come from the authorizer, others from PolarisAdminService.authorizeBasicTopLevelEntityOperationOrThrow(), but we can (should) only resolve once.

I guess we can lose the "select all" flag if selectAll() is called first, and select(X) later... I'll fix that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated... PTAL.

import org.jspecify.annotations.NonNull;

/**
* Utility class for processing {@linke AuthorizationRequest} in context that do not involve

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: typo

Suggested change
* Utility class for processing {@linke AuthorizationRequest} in context that do not involve
* Utility class for processing {@linke AuthorizationRequest} in contexts that do not involve

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thx - fixed

if (intent instanceof TargetlessAuthorizationIntent) {
authzState.select(REQUESTED_TOP_LEVEL_ENTITIES);
} else {
intent.visitSecurables((securable) -> mergeSelections(authzState, securable));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the consumer based approach 👍

import org.junit.jupiter.api.BeforeAll;

@QuarkusTest
@TestProfile(Profiles.RestCatalogFileIntegrationProfile.class)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be worth a variant where the test principal has no Polaris grants at all (skip makeAdmin), so we prove loadTable doesn't depend on RBAC state anywhere?

To my understanding PolarisRestCatalogIntegrationBase creates the principal, principal role and the grants prior to the tests. It would be worthwhile having a variation of this without the principal and grants

Also okay to keep this out of scope

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. It increases the test matrix a bit, because I think we still have to test with internal principals too (mixed auth mode), but it's worth having that coverage - will add.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, I looks like ExternalPrincipalKeycloakOpaIT covers that case... WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From the name it sounds like it should, but I see that it creates a test Principal:

ClientPrincipal principal = super.createTestPrincipal(client, principalName, principalRole);

But this does feel like an enormous scope creep. I feel this would be good to write for what you have planned for step2 - I think having this test that relies on an external Authenticator that avoids creating a Principal will help us identify if there's a gap, or if the forced grant loading in the resolver doesn't result in errors anymore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated this test to not create / use local principals at all. In fact, it did not rely on local principals before, auth type and credential-mode were "external"... but now it does not event create them :)

PolarisResolutionManifest resolutionManifest = newResolutionManifest(referenceCatalogName);
resolutionManifest.addTopLevelName(topLevelEntityName, entityType, false /* isOptional */);
AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
if (referenceCatalogName != null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, couldn't we change the authorization intent to include the catalog as a securable? Wouldn't it be resolved automatically in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The use case coming from listCatalogRolesForPrincipalRole() is asymmetric. The authZ check does not need the catalog, but later service code needs it.

To make it symmetric, we'll need to authorize LIST_CATALOG_ROLES_FOR_PRINCIPAL_ROLE on the catalog (as a securable), which will require extra grants in the native RBAC, AFAIK.

I'm open to doing that, if you think it makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense conceptually to require the catalog to be a securable when authorizing listing catalog roles. However, let's defer this work until after this PR is merged.

AuthorizationIntentResolver.resolve(resolutionManifest, intent, prependRootContainer);
authorizeRangerOrThrow(
polarisPrincipal,
resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since we never resolve CALLER_PRINCIPAL_ROLES or CALLER_CATALOG_ROLES, this is now unused it seems, and will always return empty. Should we just remove this parameter?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(it's only used for logging anyways)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense... Yet, I'd like to keep the scope of this PR narrow. Let's do that change separately. Would that work?

@Test
void resolveAuthorizationInputsResolvesAll() {
void resolveAuthorizationInputsResolvesSelections() {
// resolveAll() is intentionally used for compatibility and is expected

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thx - removed

@@ -133,14 +131,6 @@ void setUp() throws Exception {
when(resolutionManifest.resolveAll()).thenReturn(successStatus);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if we still need this stubbing?

Comment thread CHANGELOG.md Outdated
- Minor adjustment to the semantics of `PolarisAuthorizer.resolveAuthorizationInputs()`.
Existing implementations that used to call `PolarisResolutionManifest.resolveAll()`
are expected to be compatible with the new Polaris code. Still, adjustments are recommended as
noted in javadoc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The note is a bit cryptic imho. It doesn't say what changed, and where the "adjustments" should be found (which javadoc?).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll redo the CHANGELOG entry when I open this PR for the next review round.

* delegate to {@link PolarisResolutionManifest#resolveSelections(Set)}, otherwise it will
* delegate to {@link PolarisResolutionManifest#resolveAll()}.
*/
public void resolve() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

resolveSelections(Set) is public and takes its selections as a parameter. If a caller injects
selectors via select(...), an implementation that calls resolveSelections(...) on the
manifest would drop the caller's selectors.
Is going through AuthorizationState.resolve() intended to be required once the PR is merged?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@gracechen09 : I'm not sure where the "drop" can happen. Right now the logic is cumulative... unless I missed something 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PolarisResolutionManifest can (and probably should) only be resolved once.

Also, according to the dev thread about multi-entity changes, I believe we should aim at having at most one PolarisResolutionManifest object per request.

https://lists.apache.org/thread/qm60lz3xtnv15swwfb3w6xc1stmflsv3

@dimas-b
dimas-b marked this pull request as draft October 5, 2026 18:40
@dimas-b
dimas-b force-pushed the ext-authz-resolve branch 2 times, most recently from c0203de to 90bfa2c Compare October 7, 2026 00:12
@dimas-b
dimas-b marked this pull request as ready for review October 7, 2026 00:56
@dimas-b
dimas-b requested review from adutra and sungwy October 7, 2026 00:57
@dimas-b
dimas-b force-pushed the ext-authz-resolve branch from 90bfa2c to a4f32a7 Compare October 7, 2026 14:43
@dimas-b

dimas-b commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

I had to squash to simplify conflict resolution during rebase - PTAL.

The commit message is a mess. Please refer to the PR description for change summary.

Relates to apache#5476

Restore resolveAuthorizationInputs

Restore resolveAuthorizationInputs mocks

Rename AuthorizationState.resolve()

resolveAuthorizationInputsResolvesSelections()

Add CHANGELOG

typo

review: Clarify selectAll / resolve logic

review: fold ExampleNonRBACAuthorizer into TestPolarisAuthorizer

review: rename BasicResolutionSemantics to NonRBACResolutionSemantics

review: authorizer javadoc

review: remove irrelevant comment

review: Do not create local principals in ExternalPrincipalKeycloakOpaIT

add CHANGELOG entry (breaking changes)
@dimas-b
dimas-b force-pushed the ext-authz-resolve branch from a4f32a7 to 8b042b7 Compare October 7, 2026 18:55

This branch has not been deployed

No deployments
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.

4 participants