diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexComponentSelectorPredicate.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexComponentSelectorPredicate.java index 0083de155032b..c24f0f5ec057c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexComponentSelectorPredicate.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexComponentSelectorPredicate.java @@ -34,6 +34,10 @@ public record IndexComponentSelectorPredicate(Set names, Predicate resolve(Set name) { final Set allSelectorAccessPrivileges = new HashSet<>(); final Set dataSelectorAccessPrivileges = new HashSet<>(); final Set failuresSelectorAccessPrivileges = new HashSet<>(); + final Set dataAndFailuresSelectorAccessPrivileges = new HashSet<>(); boolean containsAllAccessPrivilege = name.stream().anyMatch(n -> getNamedOrNull(n) == ALL); for (String part : name) { @@ -383,6 +388,8 @@ private static Set resolve(Set name) { dataSelectorAccessPrivileges.add(indexPrivilege); } else if (indexPrivilege.selectorPredicate == IndexComponentSelectorPredicate.FAILURES) { failuresSelectorAccessPrivileges.add(indexPrivilege); + } else if (indexPrivilege.selectorPredicate == IndexComponentSelectorPredicate.DATA_AND_FAILURES) { + dataAndFailuresSelectorAccessPrivileges.add(indexPrivilege); } else { String errorMessage = "unexpected selector [" + indexPrivilege.selectorPredicate + "]"; assert false : errorMessage; @@ -406,6 +413,7 @@ private static Set resolve(Set name) { allSelectorAccessPrivileges, dataSelectorAccessPrivileges, failuresSelectorAccessPrivileges, + dataAndFailuresSelectorAccessPrivileges, actions ); assertNamesMatch(name, combined); @@ -416,24 +424,33 @@ private static Set combineIndexPrivileges( Set allSelectorAccessPrivileges, Set dataSelectorAccessPrivileges, Set failuresSelectorAccessPrivileges, + Set dataAndFailuresSelectorAccessPrivileges, Set actions ) { assert false == allSelectorAccessPrivileges.isEmpty() || false == dataSelectorAccessPrivileges.isEmpty() || false == failuresSelectorAccessPrivileges.isEmpty() + || false == dataAndFailuresSelectorAccessPrivileges.isEmpty() || false == actions.isEmpty() : "at least one of the privilege sets or actions must be non-empty"; if (false == allSelectorAccessPrivileges.isEmpty()) { - assert failuresSelectorAccessPrivileges.isEmpty() && dataSelectorAccessPrivileges.isEmpty() - : "data and failure access must be empty when all access is present"; + assert failuresSelectorAccessPrivileges.isEmpty() + && dataSelectorAccessPrivileges.isEmpty() + && dataAndFailuresSelectorAccessPrivileges.isEmpty() : "data and failure access must be empty when all access is present"; return Set.of(union(allSelectorAccessPrivileges, actions, IndexComponentSelectorPredicate.ALL)); } // linked hash set to preserve order across selectors - final Set combined = new LinkedHashSet<>(); + final Set combined = Sets.newLinkedHashSetWithExpectedSize( + dataAndFailuresSelectorAccessPrivileges.size() + failuresSelectorAccessPrivileges.size() + dataSelectorAccessPrivileges.size() + + actions.size() + ); if (false == dataSelectorAccessPrivileges.isEmpty() || false == actions.isEmpty()) { combined.add(union(dataSelectorAccessPrivileges, actions, IndexComponentSelectorPredicate.DATA)); } + if (false == dataAndFailuresSelectorAccessPrivileges.isEmpty()) { + combined.add(union(dataAndFailuresSelectorAccessPrivileges, Set.of(), IndexComponentSelectorPredicate.DATA_AND_FAILURES)); + } if (false == failuresSelectorAccessPrivileges.isEmpty()) { combined.add(union(failuresSelectorAccessPrivileges, Set.of(), IndexComponentSelectorPredicate.FAILURES)); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilegeTests.java index 029f086d47ced..fe693eeb98562 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilegeTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.common.util.iterable.Iterables; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.rollup.action.GetRollupIndexCapsAction; +import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.transform.action.GetCheckpointAction; import java.util.Collection; @@ -240,6 +241,70 @@ public void testResolveBySelectorAccess() { List actualPredicates = actual.stream().map(IndexPrivilege::getSelectorPredicate).toList(); assertThat(actualPredicates, containsInAnyOrder(IndexComponentSelectorPredicate.ALL)); } + { + Set actual = IndexPrivilege.resolveBySelectorAccess( + Set.of("manage", "all", "read", "indices:data/read/search", "view_index_metadata") + ); + assertThat( + actual, + containsInAnyOrder( + resolvePrivilegeAndAssertSingleton(Set.of("manage", "all", "read", "indices:data/read/search", "view_index_metadata")) + ) + ); + List actualPredicates = actual.stream().map(IndexPrivilege::getSelectorPredicate).toList(); + assertThat(actualPredicates, containsInAnyOrder(IndexComponentSelectorPredicate.ALL)); + } + { + Set actual = IndexPrivilege.resolveBySelectorAccess( + Set.of("manage", "read", "indices:data/read/search", "read_failure_store") + ); + assertThat( + actual, + containsInAnyOrder( + IndexPrivilege.MANAGE, + IndexPrivilege.READ_FAILURE_STORE, + resolvePrivilegeAndAssertSingleton(Set.of("read", "indices:data/read/search")) + ) + ); + List actualPredicates = actual.stream().map(IndexPrivilege::getSelectorPredicate).toList(); + assertThat( + actualPredicates, + containsInAnyOrder( + IndexComponentSelectorPredicate.DATA, + IndexComponentSelectorPredicate.FAILURES, + IndexComponentSelectorPredicate.DATA_AND_FAILURES + ) + ); + } + { + Set actual = IndexPrivilege.resolveBySelectorAccess(Set.of("manage", "read", "indices:data/read/search")); + assertThat( + actual, + containsInAnyOrder(IndexPrivilege.MANAGE, resolvePrivilegeAndAssertSingleton(Set.of("read", "indices:data/read/search"))) + ); + List actualPredicates = actual.stream().map(IndexPrivilege::getSelectorPredicate).toList(); + assertThat( + actualPredicates, + containsInAnyOrder(IndexComponentSelectorPredicate.DATA, IndexComponentSelectorPredicate.DATA_AND_FAILURES) + ); + } + { + Set actual = IndexPrivilege.resolveBySelectorAccess( + Set.of("manage", "read", "manage_data_stream_lifecycle", "indices:admin/*") + ); + assertThat( + actual, + containsInAnyOrder( + resolvePrivilegeAndAssertSingleton(Set.of("manage_data_stream_lifecycle", "manage")), + resolvePrivilegeAndAssertSingleton(Set.of("read", "indices:admin/*")) + ) + ); + List actualPredicates = actual.stream().map(IndexPrivilege::getSelectorPredicate).toList(); + assertThat( + actualPredicates, + containsInAnyOrder(IndexComponentSelectorPredicate.DATA, IndexComponentSelectorPredicate.DATA_AND_FAILURES) + ); + } } public void testPrivilegesForRollupFieldCapsAction() { @@ -300,7 +365,11 @@ public void testCrossClusterReplicationPrivileges() { assertThat( Operations.subsetOf( crossClusterReplication.automaton, - resolvePrivilegeAndAssertSingleton(Set.of("manage", "read", "monitor")).automaton + IndexPrivilege.resolveBySelectorAccess(Set.of("manage", "read", "monitor")) + .stream() + .map(p -> p.automaton) + .reduce((a1, a2) -> Automatons.unionAndMinimize(List.of(a1, a2))) + .get() ), is(true) ); diff --git a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java index b95956e2bfe19..2e5c021aa6e3b 100644 --- a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java +++ b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java @@ -93,6 +93,7 @@ protected Settings restAdminSettings() { private static final String WRITE_ACCESS = "write_access"; private static final String MANAGE_ACCESS = "manage_access"; private static final String MANAGE_FAILURE_STORE_ACCESS = "manage_failure_store_access"; + private static final String MANAGE_DATA_STREAM_LIFECYCLE = "manage_data_stream_lifecycle"; private static final SecureString PASSWORD = new SecureString("admin-password"); @Before @@ -126,14 +127,10 @@ public void testGetUserPrivileges() throws IOException { "global": [], "indices": [{ "names": ["*"], - "privileges": ["read"], + "privileges": ["read", "read_failure_store"], "allow_restricted_indices": false - }, - { - "names": ["*"], - "privileges": ["read_failure_store"], - "allow_restricted_indices": false - }], + } + ], "applications": [], "run_as": [] }"""); @@ -210,14 +207,57 @@ public void testGetUserPrivileges() throws IOException { "indices": [ { "names": ["*"], - "privileges": ["read", "write"], + "privileges": ["manage_failure_store", "read", "read_failure_store", "write"], + "allow_restricted_indices": false + } + ], + "applications": [], + "run_as": [] + }"""); + + upsertRole(""" + { + "cluster": ["all"], + "indices": [ + { + "names": ["*", "idx"], + "privileges": ["read", "manage"], "allow_restricted_indices": false }, { - "names": ["*"], - "privileges": ["manage_failure_store", "read_failure_store"], + "names": ["idx", "*"], + "privileges": ["manage_data_stream_lifecycle"], "allow_restricted_indices": false - }], + }, + { + "names": ["*", "idx"], + "privileges": ["write"], + "allow_restricted_indices": true + }, + { + "names": ["idx", "*"], + "privileges": ["manage"], + "allow_restricted_indices": true + } + ] + } + """, "role"); + expectUserPrivilegesResponse(""" + { + "cluster": ["all"], + "global": [], + "indices": [ + { + "names": ["*", "idx"], + "privileges": ["manage", "manage_data_stream_lifecycle", "read"], + "allow_restricted_indices": false + }, + { + "names": ["*", "idx"], + "privileges": ["manage", "write"], + "allow_restricted_indices": true + } + ], "applications": [], "run_as": [] }"""); @@ -1772,7 +1812,7 @@ public void testFailureStoreAccess() throws Exception { } } - public void testWriteOperations() throws IOException { + public void testWriteAndManageOperations() throws IOException { setupDataStream(); Tuple backingIndices = getSingleDataAndFailureIndices("test1"); String dataIndexName = backingIndices.v1(); @@ -1808,12 +1848,45 @@ public void testWriteOperations() throws IOException { } """); - // user with manage access to data stream does NOT get direct access to failure index - expectThrows(() -> deleteIndex(MANAGE_ACCESS, failureIndexName), 403); + createUser(MANAGE_DATA_STREAM_LIFECYCLE, PASSWORD, MANAGE_DATA_STREAM_LIFECYCLE); + upsertRole(Strings.format(""" + { + "cluster": ["all"], + "indices": [{"names": ["test*"], "privileges": ["manage_data_stream_lifecycle"]}] + }"""), MANAGE_DATA_STREAM_LIFECYCLE); + createAndStoreApiKey(MANAGE_DATA_STREAM_LIFECYCLE, randomBoolean() ? null : """ + { + "role": { + "cluster": ["all"], + "indices": [{"names": ["test*"], "privileges": ["manage_data_stream_lifecycle"]}] + } + } + """); + + // explain lifecycle API with and without failures selector is granted by manage + assertOK(performRequest(MANAGE_ACCESS, new Request("GET", "test1/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_ACCESS, new Request("GET", "test1::failures/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_ACCESS, new Request("GET", failureIndexName + "/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_ACCESS, new Request("GET", dataIndexName + "/_lifecycle/explain"))); + + assertOK(performRequest(MANAGE_DATA_STREAM_LIFECYCLE, new Request("GET", "test1/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_DATA_STREAM_LIFECYCLE, new Request("GET", "test1::failures/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_DATA_STREAM_LIFECYCLE, new Request("GET", failureIndexName + "/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_DATA_STREAM_LIFECYCLE, new Request("GET", dataIndexName + "/_lifecycle/explain"))); + + // explain lifecycle API is granted by manage_failure_store only for failures selector + expectThrows(() -> performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", "test1/_lifecycle/explain")), 403); + assertOK(performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", "test1::failures/_lifecycle/explain"))); + assertOK(performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", failureIndexName + "/_lifecycle/explain"))); + expectThrows(() -> performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", dataIndexName + "/_lifecycle/explain")), 403); + + // user with manage access to data stream can delete failure index because manage grants access to both data and failures + expectThrows(() -> deleteIndex(MANAGE_ACCESS, failureIndexName), 400); expectThrows(() -> deleteIndex(MANAGE_ACCESS, dataIndexName), 400); - // manage_failure_store user COULD delete failure index (not valid because it's a write index, but allow security-wise) - expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403); + + // manage_failure_store user COULD delete failure index (not valid because it's a write index, but allowed security-wise) expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, failureIndexName), 400); + expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403); expectThrows(() -> deleteDataStream(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403); expectThrows(() -> deleteDataStream(MANAGE_FAILURE_STORE_ACCESS, "test1"), 403); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index 245ea1e8e6dec..abd329d294920 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -42,6 +42,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.CachedSupplier; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.core.Tuple; import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.transport.TransportActionProxy; @@ -99,6 +100,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -825,7 +827,7 @@ static GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole return new GetUserPrivilegesResponse( cluster, conditionalCluster, - indices, + combineIndices(indices), application, runAs, remoteIndices, @@ -833,6 +835,48 @@ static GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole ); } + /** + * Due to selector-processing during role building + * (see {@link org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege#resolveBySelectorAccess(Set)}), + * it is possible that multiple index groups with the same indices exist with different privilege sets. To provide a cleaner response, + * this method combines them into one group. + */ + private static Set combineIndices(Set indices) { + final Map>, GetUserPrivilegesResponse.Indices> combinedIndices = new LinkedHashMap<>(); + for (GetUserPrivilegesResponse.Indices index : indices) { + final GetUserPrivilegesResponse.Indices existing = combinedIndices.get( + new Tuple<>(index.allowRestrictedIndices(), index.getIndices()) + ); + if (existing == null) { + combinedIndices.put(new Tuple<>(index.allowRestrictedIndices(), index.getIndices()), index); + } else { + Set combinedPrivileges = new HashSet<>(existing.getPrivileges()); + combinedPrivileges.addAll(index.getPrivileges()); + boolean flsDlsMatch = Objects.equals(existing.getFieldSecurity(), index.getFieldSecurity()) + && Objects.equals(existing.getQueries(), index.getQueries()); + assert existing.getIndices().equals(index.getIndices()) + && existing.allowRestrictedIndices() == index.allowRestrictedIndices() + // due to collation & selector resolution code, fls and dls definitions are always the same if the indices match + && flsDlsMatch; + if (false == flsDlsMatch) { + // if the above invariant is violated, due to a bug, bail and return original indices (these will still be correct) + return indices; + } + combinedIndices.put( + new Tuple<>(index.allowRestrictedIndices(), index.getIndices()), + new GetUserPrivilegesResponse.Indices( + index.getIndices(), + combinedPrivileges, + index.getFieldSecurity(), + index.getQueries(), + index.allowRestrictedIndices() + ) + ); + } + } + return new LinkedHashSet<>(combinedIndices.values()); + } + private static GetUserPrivilegesResponse.Indices toIndices(final IndicesPermission.Group group) { final Set queries = group.getQuery() == null ? Collections.emptySet() : group.getQuery(); final Set fieldSecurity = getFieldGrantExcludeGroups(group); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java index 1bc711d108a17..091eaf3023476 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java @@ -1414,6 +1414,82 @@ public void testBuildUserPrivilegeResponse() { } } + public void testBuildUserPrivilegeResponseCombinesIndexPrivileges() { + final BytesArray query = new BytesArray(""" + {"term":{"public":true}}"""); + final Role role = Role.builder(RESTRICTED_INDICES, "test", "role") + .add(IndexPrivilegeTests.resolvePrivilegeAndAssertSingleton(Sets.newHashSet("read", "write")), "index-1") + .add(IndexPrivilege.ALL, "index-2") + .add( + new FieldPermissions(new FieldPermissionsDefinition(new String[] { "public.*" }, new String[0])), + Collections.singleton(query), + IndexPrivilege.MANAGE, + true, + "index-1", + "index-2" + ) + .add( + new FieldPermissions(new FieldPermissionsDefinition(new String[] { "public.*" }, new String[0])), + Collections.singleton(query), + IndexPrivilegeTests.resolvePrivilegeAndAssertSingleton(Sets.newHashSet("read", "write")), + true, + "index-2", + "index-1" + ) + .add( + new FieldPermissions(new FieldPermissionsDefinition(new String[] { "public.*" }, new String[0])), + Collections.singleton(query), + IndexPrivilegeTests.resolvePrivilegeAndAssertSingleton(Sets.newHashSet("read_failure_store", "manage_failure_store")), + true, + "index-2", + "index-1" + ) + .add( + FieldPermissions.DEFAULT, + null, + IndexPrivilegeTests.resolvePrivilegeAndAssertSingleton(Sets.newHashSet("read_failure_store")), + false, + "index-2", + "index-1" + ) + .build(); + + final GetUserPrivilegesResponse response = RBACEngine.buildUserPrivilegesResponseObject(role); + + final GetUserPrivilegesResponse.Indices index1 = findIndexPrivilege(response.getIndexPrivileges(), Set.of("index-1"), false); + assertThat(index1.getIndices(), containsInAnyOrder("index-1")); + assertThat(index1.getPrivileges(), containsInAnyOrder("read", "write")); + assertThat(index1.getFieldSecurity(), emptyIterable()); + assertThat(index1.getQueries(), emptyIterable()); + + final GetUserPrivilegesResponse.Indices index2 = findIndexPrivilege(response.getIndexPrivileges(), Set.of("index-2"), false); + assertThat(index2.getIndices(), containsInAnyOrder("index-2")); + assertThat(index2.getPrivileges(), containsInAnyOrder("all")); + assertThat(index2.getFieldSecurity(), emptyIterable()); + assertThat(index2.getQueries(), emptyIterable()); + + Set actualIndexPrivileges = response.getIndexPrivileges(); + assertThat(actualIndexPrivileges, iterableWithSize(4)); + final GetUserPrivilegesResponse.Indices index1And2 = findIndexPrivilege(actualIndexPrivileges, Set.of("index-1", "index-2"), true); + assertThat(index1And2.getIndices(), containsInAnyOrder("index-1", "index-2")); + assertThat(index1And2.getPrivileges(), containsInAnyOrder("read", "write", "read_failure_store", "manage_failure_store", "manage")); + assertThat( + index1And2.getFieldSecurity(), + containsInAnyOrder(new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[] { "public.*" }, new String[0])) + ); + assertThat(index1And2.getQueries(), containsInAnyOrder(query)); + + final GetUserPrivilegesResponse.Indices index1And2NotRestricted = findIndexPrivilege( + actualIndexPrivileges, + Set.of("index-1", "index-2"), + false + ); + assertThat(index1And2NotRestricted.getIndices(), containsInAnyOrder("index-1", "index-2")); + assertThat(index1And2NotRestricted.getPrivileges(), containsInAnyOrder("read_failure_store")); + assertThat(index1And2NotRestricted.getFieldSecurity(), emptyIterable()); + assertThat(index1And2NotRestricted.getQueries(), emptyIterable()); + } + public void testBackingIndicesAreIncludedForAuthorizedDataStreams() { final String dataStreamName = "my_data_stream"; User user = new User(randomAlphaOfLengthBetween(4, 12)); @@ -2016,6 +2092,21 @@ private static RequestInfo createRequestInfo(TransportRequest request, String ac ); } + private GetUserPrivilegesResponse.Indices findIndexPrivilege( + Set indices, + Set indexNames, + boolean allowRestrictedIndices + ) { + return indices.stream() + .filter( + i -> i.allowRestrictedIndices() == allowRestrictedIndices + && i.getIndices().containsAll(indexNames) + && indexNames.containsAll(i.getIndices()) + ) + .findFirst() + .get(); + } + private GetUserPrivilegesResponse.Indices findIndexPrivilege(Set indices, String name) { return indices.stream().filter(i -> i.getIndices().contains(name)).findFirst().get(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 50e488196803b..8fbf7a9e7b62b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -2009,11 +2009,24 @@ public void testBuildRoleWithFailureStorePrivilegeCollatesToKeepDlsFlsFromAnothe ); } - public void testBuildRoleNeverSplitsWithoutFailureStoreRelatedPrivileges() { + public void testBuildRoleDoesNotSplitIfAllPrivilegesHaveTheSameSelector() { String indexPattern = randomAlphanumericOfLength(10); - List nonFailurePrivileges = IndexPrivilege.names() + IndexComponentSelectorPredicate predicate = (DataStream.isFailureStoreFeatureFlagEnabled()) + ? randomFrom( + IndexComponentSelectorPredicate.ALL, + IndexComponentSelectorPredicate.DATA, + IndexComponentSelectorPredicate.FAILURES, + IndexComponentSelectorPredicate.DATA_AND_FAILURES + ) + : randomFrom( + IndexComponentSelectorPredicate.ALL, + IndexComponentSelectorPredicate.DATA, + IndexComponentSelectorPredicate.DATA_AND_FAILURES + ); + + List privilegesWithSelector = IndexPrivilege.names() .stream() - .filter(p -> IndexPrivilege.getNamedOrNull(p).getSelectorPredicate() != IndexComponentSelectorPredicate.FAILURES) + .filter(p -> IndexPrivilege.getNamedOrNull(p).getSelectorPredicate() == predicate) .toList(); Set usedPrivileges = new HashSet<>(); @@ -2024,7 +2037,7 @@ public void testBuildRoleNeverSplitsWithoutFailureStoreRelatedPrivileges() { // TODO this is due to an unrelated bug in index collation logic List privileges = randomValueOtherThanMany( p -> p.get(0).equals("none"), - () -> randomNonEmptySubsetOf(nonFailurePrivileges) + () -> randomNonEmptySubsetOf(privilegesWithSelector) ); usedPrivileges.addAll(privileges); indicesPrivileges[i] = builder.indices(indexPattern).privileges(privileges).build();