Skip to content

Secured view: restrict_to_alias binding + DLS pre-filter scoring - #15

Open
DarshitChanpura wants to merge 3 commits into
mainfrom
feat/secured-view-e2e
Open

Secured view: restrict_to_alias binding + DLS pre-filter scoring#15
DarshitChanpura wants to merge 3 commits into
mainfrom
feat/secured-view-e2e

Conversation

@DarshitChanpura

Copy link
Copy Markdown
Owner

What

Adds a secured view to the security plugin: a per-grant restrict_to_alias
flag on a role's index-permission, composed with the existing DLS pre-filter
scoring bridge.

1. restrict_to_alias access binding (net-new)

index_permissions:
  - index_patterns: ["cardiology_view"]   # an alias
    restrict_to_alias: true
    allowed_actions: ["read"]

When set, a grant on an alias authorizes access only through the alias; a
direct request for the alias's backing concrete index is denied. Opt-in per
grant, default false, so existing alias grants are unchanged.

Closes both alias→index inheritance sites (the evaluator tries the stateful
path first and falls back to the static one, so both are handled):

  • IndexPattern.matches() — a restrict_to_alias pattern no longer walks up
    from a directly-requested backing index to its parent alias/data stream.
  • RoleBasedActionPrivileges — a restrict_to_alias grant seeds only the alias
    name into the precomputed privilege map, not the backing sub-indices; the
    static IndexPrivileges merge AND-combines the flag across grants for the same
    (role, action) so a normal grant on the same pattern still reaches the
    backing index (union-of-privileges semantics).

Requires the nextgen (v4) privileges evaluator, which preserves the alias name
in the resolved indices; the legacy evaluator resolves aliases to concrete
backing names before the check and cannot distinguish alias access from direct
access.

2. DLS pre-filter scoring bridge

When plugins.security.dls.pre_filter_scoring is enabled, filter-level and
lucene-level DLS wrap the combined (DLS filter + user query) in a
constant_score, so relevance scores reflect only the documents the role can
read. Default off; existing behavior unchanged.

Testing

  • SecuredViewRestrictToAliasIntegrationTest (v4, 3/3): view user reads the
    alias, is denied the backing index; a control user with the same grant without
    the flag still reaches the backing index (no regression).
  • IndexPatternTest unit case on the static matcher.
  • RoleBasedActionPrivilegesTest (8/8) — no regression.
  • DlsPreFilterScoringIntegrationTest (3/3) for the scoring bridge, incl.
    dfs_query_then_fetch.

Adds a per-grant boolean restrict_to_alias on a role's index-permission. When
set, a grant on an alias authorizes access only through the alias itself; a
direct request for the alias's backing concrete index is denied. Opt-in per
grant, default false, so existing alias grants are unchanged.

Closes both alias->index inheritance sites (the evaluator tries the stateful
path first and falls back to the static path, so both must be handled):
- IndexPattern.matches(): a restrict_to_alias pattern no longer walks up from a
  directly-requested backing index to its parent alias/data stream.
- RoleBasedActionPrivileges: a restrict_to_alias grant seeds only the alias name
  into the precomputed privilege map, not the backing sub-indices; the static
  IndexPrivileges merge AND-combines the flag across grants for the same
  (role, action), so a normal grant on the same pattern still reaches the
  backing index (union-of-privileges semantics).

Requires the nextgen (v4) privilege evaluator, which preserves the alias name in
the resolved indices; the legacy evaluator resolves aliases to concrete backing
names before the check and cannot distinguish alias access from direct access.

Verified by SecuredViewRestrictToAliasIntegrationTest (v4, 3/3) and an
IndexPatternTest unit case; RoleBasedActionPrivilegesTest confirms no regression.
Adds an opt-in setting, plugins.security.dls.pre_filter_scoring (default
false), that makes DLS apply the role's document filter as a pre-filter:
the combined (DLS restriction + user query) is wrapped in a constant_score
so BM25 collection statistics reflect only the documents the role can read,
instead of being computed over the whole shard and filtered afterward.

Without this, a term that occurs only in documents outside the DLS
restriction affects the BM25 score of a document the role can read (its
corpus-wide document frequency lowers the term's IDF), so relevance scores
depend on documents the role cannot see. Pre-filtering keeps scores a
function of the visible subset alone. The returned document set is
unchanged; only relevance scoring is suppressed. Intended for the common
case where the restricted view does not need relevance ranking.

Both DLS search paths are covered:
- filter-level (DlsFilterLevelActionHandler): wraps its re-issued query.
- lucene-level / default ADAPTIVE (DlsFlsValveImpl): wraps the DLS-scoped
  query injected into the search context, where the user query was otherwise
  added as a scored Occur.MUST clause over whole-shard stats.

The setting is dynamic: the valve registers a settings-update consumer and
the filter-level handler reads the live value per request, so a runtime
transient/persistent update takes effect without a node restart. Also
registers the previously-unregistered plugins.security.dls.mode setting.

Verified on a live 3.8.0-SNAPSHOT cluster in ADAPTIVE mode: a term confined
to restricted docs scored 2.19 vs a nonexistent term's 7.14 (scores varying
with non-visible data); with the flag on both score 1.0, document isolation
intact. Integration test DlsPreFilterScoringIntegrationTest (2/2) asserts the
filter-level path. Default off preserves existing behavior.

Follow-up: constant_score suppresses ranking; ranked restricted views (real
relevance over visible-only statistics) are a separate filtered-statistics
effort.
Adds scoresAreVisibilityIndependent_underDfsQueryThenFetch to lock in DFS-phase
coverage. dfs_query_then_fetch gathers term statistics in a separate phase before
the query phase; the pre-filter constant_score wrap is applied in the query phase
(onPreQueryPhase). This test asserts the DFS phase also observes the wrapped query
so it does not reintroduce whole-shard statistics -- a term confined to non-visible
docs must score the same as an absent term under DFS. Previously verified by hand;
now guarded by a test. 3/3 green.
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.

1 participant