Skip to content

fix: add matchAnyDomain policy condition function - #33385

Open
zhang-arvin wants to merge 7 commits into
open-metadata:mainfrom
zhang-arvin:fix/issue-32970-match-any-domain
Open

zhang-arvin wants to merge 7 commits into
open-metadata:mainfrom
zhang-arvin:fix/issue-32970-match-any-domain

Conversation

@zhang-arvin

@zhang-arvin zhang-arvin commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #32970

Problem

The official docs ("Roles and Policies — Use Cases", Use Case 8) reference matchAnyDomain('Finance') as a policy condition function, but:

  • GET /policies/functions does not return it, because PolicyResource.listPolicyFunctions() only collects @Function methods from RuleEvaluator, and matchAnyDomain only exists in AlertsRuleEvaluator (the notification/alert chain).
  • Policy conditions using matchAnyDomain are recognized by ExpressionValidator but rejected at evaluation time because RuleEvaluator has no such function.

Change

  • Add @Function(name = "matchAnyDomain") to RuleEvaluator: returns true when the accessed entity belongs to at least one of the given domain FQNs. During expression validation it resolves each referenced domain (like matchAnyTag) and returns false.
  • Translate the condition to a search query in RBACConditionEvaluator (case "matchAnyDomain"): builds an OR (should) of domains.fullyQualifiedName term queries so domain filtering applies on search/list paths.
  • Tests: RuleEvaluatorTest (match/no-match/empty/null domains, null resource context, expression validation) and ES/OS RBACConditionEvaluatorTest search-query assertions.

Notes

  • No schema changes; ExpressionValidator already listed matchAnyDomain in its fallback whitelist.
  • Search-side matching uses domains.fullyQualifiedName — the same field hasDomain post-filtering relies on; domains inherited via parent entities may not appear on the index doc (same limitation as existing domain condition search translation).
Greptile Summary

This PR adds matchAnyDomain to direct policy evaluation and search-time RBAC filtering, with Elasticsearch, OpenSearch, and policy-evaluator tests.

  • Direct evaluation compares requested domain FQNs against resource domain references.
  • Search evaluation creates exact domain-term queries and ORs multiple requested domains.
  • The empty-argument search path currently fails open and needs correction before merge.
Confidence Score: 3/5

This PR is not safe to merge until an empty matchAnyDomain() condition is prevented from granting unrestricted search visibility.

The ordinary single- and multi-domain paths are aligned, but the reachable zero-argument path makes search authorization match all resources while direct authorization returns false.

Files Needing Attention: openmetadata-service/src/main/java/org/openmetadata/service/search/security/RBACConditionEvaluator.java

Security Review

The search evaluator turns an empty matchAnyDomain() ALLOW condition into a match-all query, potentially exposing resources that direct policy evaluation would deny.

Important Files Changed
Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/search/security/RBACConditionEvaluator.java Adds search-query translation for matchAnyDomain, but zero arguments produce a fail-open match-all ALLOW query.
openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java Adds direct matchAnyDomain evaluation and validation using resource domain FQNs.
openmetadata-service/src/test/java/org/openmetadata/service/search/security/ElasticSearchRBACConditionEvaluatorTest.java Covers single- and multi-domain Elasticsearch query generation but not the empty-argument case.
openmetadata-service/src/test/java/org/openmetadata/service/search/security/OpenSearchRBACConditionEvaluatorTest.java Covers multi-domain OpenSearch query generation but not the empty-argument case.
openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java Covers matching, non-matching, absent-resource, absent-domain, and validation behavior for direct evaluation.
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  P[Policy condition] --> V[Direct RuleEvaluator]
  P --> S[Search RBAC evaluator]
  V -->|Non-empty arguments| D[Compare resource domain FQNs]
  S -->|Non-empty arguments| Q[Domain term query]
  V -->|Empty arguments| F[False]
  S -->|Empty arguments| B[Empty bool query]
  B --> M[Match all search results]
Loading

Reviews (1): Last reviewed commit: "style: apply google-java-format to match..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

@zhang-arvin
zhang-arvin requested a review from a team as a code owner September 16, 2026 08:00
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment on lines +293 to +299
OMQueryBuilder domainQueryCombined;
if (domainQueries.size() == 1) {
domainQueryCombined = domainQueries.get(0);
} else {
domainQueryCombined = queryBuilderFactory.boolQuery().should(domainQueries);
}
collector.addMust(domainQueryCombined);

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.

P1 security Empty Condition Grants Access

An empty matchAnyDomain() call reaches this branch with no domain queries. The resulting empty bool query is treated as match-all by Elasticsearch and OpenSearch, so an ALLOW policy can expose every searchable resource even though the direct RuleEvaluator returns false for the same condition. Reject zero arguments or emit a match-none query to keep search authorization fail-closed.

How this was verified: A zero-argument expression produces an empty bool query that is retained as the ALLOW must-clause with match-all semantics.

Knowledge Base Used: Platform security and governance

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.

Fixed in e661598. matchAnyDomain/matchAnyCertification now switch on argument count and call collector.setMatchNothing(true) for the zero-argument case (same fail-closed primitive hasAnyRole/inAnyTeam use), so an ALLOW rule with an empty condition translates to match_none rather than an empty (match-all) bool query.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@zhang-arvin
zhang-arvin force-pushed the fix/issue-32970-match-any-domain branch from 2c69e86 to f027fb9 Compare September 29, 2026 08:38
@zhang-arvin

Copy link
Copy Markdown
Contributor Author

Addressed the greptile P1 "Empty Condition Grants Access".

  • Finding: matchAnyDomain() with an empty argument list reached the final branch and emitted an empty bool query (should: []), which Elasticsearch/OpenSearch evaluate as match_all, so an ALLOW policy granted every searchable resource.
  • Confirmed: the empty list fell into the else branch and called addMust(boolQuery().should([])) — an empty bool with no clauses. ConditionCollector.addMust only skips null/empty holders, so the clause was kept as match-all.
  • Fix: matchAnyDomain() now switches on the argument count: 0 -> collector.setMatchNothing(true) (fail closed), 1 -> the term query directly, >1 -> the should bool. No empty bool is ever produced. The same fix is applied to matchAnyCertification(), which had the identical shape.
  • Tests: added testMatchAnyDomainWithNoArgumentsDoesNotGrantAccess to both ElasticSearchRBACConditionEvaluatorTest and OpenSearchRBACConditionEvaluatorTest, asserting the generated query is the never-matching form and contains no domain clause, no empty should, and no must clause.
  • Verification: openmetadata-service test-compile passes; google-java-format reports no differences.

AI agent generated.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

…guments

matchAnyDomain() with an empty argument list produced an empty bool query
(should: []), which Elasticsearch and OpenSearch evaluate as match_all. An
ALLOW rule carrying such a condition therefore granted access to every
searchable resource, diverging from the direct RuleEvaluator which returns
false for the same condition.

Emit no clause and mark the rule match_nothing instead, so an empty
condition degrades to deny-all. Applied to matchAnyCertification as well
(same pattern, currently unreachable via the SpEL signature but identical
bug class). Adds regression tests on Elastic and OpenSearch asserting the
generated query is match_none and contains no domain clause and no empty
bool clause.
@zhang-arvin
zhang-arvin force-pushed the fix/issue-32970-match-any-domain branch from f027fb9 to e661598 Compare October 8, 2026 05:49
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

…tionEvaluator

matchAnyDomain was inserted before the closing brace of matchAnyServiceType,
so it became a nested method declaration. That is an illegal start of
expression and broke compilation of openmetadata-service (CI: illegal start
of expression at RBACConditionEvaluator.java:412). Close matchAnyServiceType
first so matchAnyDomain is a proper class member.
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@gitar-bot

gitar-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 closed / 2 findings

🔴 High risk · Adding matchAnyDomain to policy evaluation and search filtering changes authorization; incorrect domain matching could expose resources or deny legitimate access.

Adds matchAnyDomain policy condition function for direct evaluation and search-time RBAC filtering. Restored missing closing brace in RBACConditionEvaluator and fixed $.bool.must PathNotFoundException in tests.

✅ 2 closed
✅ Bug: $.bool.must read throws PathNotFoundException, so the new tests fail

📄 openmetadata-service/src/test/java/org/openmetadata/service/search/security/ElasticSearchRBACConditionEvaluatorTest.java:1717-1720 📄 openmetadata-service/src/test/java/org/openmetadata/service/search/security/OpenSearchRBACConditionEvaluatorTest.java:509-512
With no arguments, matchAnyDomain() makes the query builder return matchNoneQuery(), which is built as bool(b -> b.mustNot(matchAll)). must is never set on that bool, so the ES and OpenSearch clients leave the must key out of the JSON. The tests parse with the default JsonPath configuration, and nothing in the tests calls Configuration.setDefaults or SUPPRESS_EXCEPTIONS, so reading the definite path $.bool.must throws PathNotFoundException instead of returning null. That makes both testMatchAnyDomainWithNoArgumentsDoesNotGrantAccess tests fail even though the fail-closed behavior is correct. Fix: check for the key without reading it, for example with an indefinite path read like $.bool[?(@.must)], or with Map parsing.

✅ Bug: Missing closing brace nests matchAnyDomain in matchAnyServiceType

📄 openmetadata-service/src/main/java/org/openmetadata/service/search/security/RBACConditionEvaluator.java:410-424
This commit removed the closing } of matchAnyServiceType after collector.addMust(...should(clauses)); on line 410. As a result, public void matchAnyDomain(...) on line 412 is declared inside another method's body, and Java does not allow that. The class will not compile, and the service build and every test in this PR (including the new fail-closed regression tests) will fail. To fix it, put the closing brace back after line 410 and remove the stray blank line before the brace at line 427.

Review coverage

📋 Rules No rules evaluated

🧪 Functional validation Not enabled · Set up

🤖 Auto-approval Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

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.

matchAnyDomain() policy condition function is documented but does not exist in the codebase (any version, incl. main)

2 participants