From c8b60c25d3781662abbe4b02c175108819b3a9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slobodan=20Adamovi=C4=87?= Date: Fri, 28 Mar 2025 14:36:14 +0100 Subject: [PATCH] [Failure Store] Test FLS/DLS access to failure store (#125792) More test coverage for FLS/DLS over failure store indices. --- .../FailureStoreSecurityRestIT.java | 111 ++++++++++++++++-- 1 file changed, 102 insertions(+), 9 deletions(-) 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 bfdea27eee98c..f54ec41e37eb0 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 @@ -17,6 +17,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.Maps; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.support.XContentMapValues; @@ -1388,8 +1389,7 @@ public void testDlsFls() throws Exception { Map.of(dataIndexName, Set.of("@timestamp", "age")) ); - // FLS sort of applies to failure store - // TODO this will change with FLS handling + // FLS applies to failure store assertSearchResponseContainsExpectedIndicesAndFields( performRequest(user, new Search("test1::failures").toSearchRequest()), Map.of(failureIndexName, Set.of("@timestamp")) @@ -1409,12 +1409,45 @@ public void testDlsFls() throws Exception { { "names": ["test*"], "privileges": ["read_failure_store"], + "field_security": { + "grant": ["error.type", "error.message"] + } + } + ] + }""", "test1"), role); + + // FLS applies to regular data stream + assertSearchResponseContainsExpectedIndicesAndFields( + performRequest(user, new Search(randomFrom("test1", "test1::data")).toSearchRequest()), + Map.of(dataIndexName, Set.of("@timestamp", "age")) + ); + + // FLS applies to failure store + assertSearchResponseContainsExpectedIndicesAndFields( + performRequest(user, new Search("test1::failures").toSearchRequest()), + Map.of(failureIndexName, Set.of("error.type", "error.message")) + ); + + upsertRole(Strings.format(""" + { + "cluster": ["all"], + "indices": [ + { + "names": ["%s"], + "privileges": ["read"], "field_security": { "grant": ["@timestamp", "age"] } + }, + { + "names": ["test*"], + "privileges": ["read_failure_store"], + "field_security": { + "grant": ["error.type", "error.message"] + } } ] - }""", randomFrom("test*", "test1")), role); + }""", "test*"), role); // FLS applies to regular data stream assertSearchResponseContainsExpectedIndicesAndFields( @@ -1422,11 +1455,10 @@ public void testDlsFls() throws Exception { Map.of(dataIndexName, Set.of("@timestamp", "age")) ); - // FLS sort of applies to failure store - // TODO this will change with FLS handling + // FLS applies to failure store assertSearchResponseContainsExpectedIndicesAndFields( performRequest(user, new Search("test1::failures").toSearchRequest()), - Map.of(failureIndexName, Set.of("@timestamp")) + Map.of(failureIndexName, Set.of("@timestamp", "error.type", "error.message")) ); upsertRole(""" @@ -1452,10 +1484,56 @@ public void testDlsFls() throws Exception { performRequest(user, new Search(randomFrom("test1", "test1::data")).toSearchRequest()), Map.of(dataIndexName, Set.of("@timestamp", "age", "name", "email")) ); - assertSearchResponseContainsExpectedIndicesAndFields( performRequest(user, new Search("test1::failures").toSearchRequest()), - Map.of(failureIndexName, Set.of("@timestamp", "document", "error")) + Map.of( + failureIndexName, + Set.of( + "@timestamp", + "document.id", + "document.index", + "document.source.@timestamp", + "document.source.age", + "document.source.email", + "document.source.name", + "error.message", + "error.stack_trace", + "error.type" + ) + ) + ); + + // check that direct read access to backing indices is working + upsertRole(Strings.format(""" + { + "cluster": ["all"], + "indices": [ + { + "names": ["%s"], + "privileges": ["read"], + "field_security": { + "grant": ["@timestamp", "age"] + } + }, + { + "names": ["%s"], + "privileges": ["read"], + "field_security": { + "grant": ["@timestamp", "document.source.name"] + } + } + ] + }""", dataIndexName, failureIndexName), role); + + // FLS applies to backing data index + assertSearchResponseContainsExpectedIndicesAndFields( + performRequest(user, new Search(randomFrom(dataIndexName, ".ds-*")).toSearchRequest()), + Map.of(dataIndexName, Set.of("@timestamp", "age")) + ); + // and backing failure index + assertSearchResponseContainsExpectedIndicesAndFields( + performRequest(user, new Search(randomFrom(failureIndexName, ".fs-*")).toSearchRequest()), + Map.of(failureIndexName, Set.of("@timestamp", "document.source.name")) ); // DLS @@ -1507,6 +1585,21 @@ public void testDlsFls() throws Exception { }""", role); // DLS does not apply because there is a section without DLS expectSearch(user, new Search(randomFrom("test1", "test1::data")), dataIndexDocId); + + // DLS is applicable to backing failure store when granted read directly + upsertRole(Strings.format(""" + { + "cluster": ["all"], + "indices": [ + { + "names": ["%s"], + "privileges": ["read"], + "query":{"term":{"document.source.name":{"value":"jack"}}} + } + ] + }""", failureIndexName), role); + expectSearch(user, new Search(randomFrom(".fs-*", failureIndexName))); + } private static void expectThrows(ThrowingRunnable runnable, int statusCode) { @@ -1797,7 +1890,7 @@ protected void assertSearchResponseContainsExpectedIndicesAndFields( assertThat(searchResult.keySet(), equalTo(expectedIndicesAndFields.keySet())); for (String index : expectedIndicesAndFields.keySet()) { Set expectedFields = expectedIndicesAndFields.get(index); - assertThat(searchResult.get(index).keySet(), equalTo(expectedFields)); + assertThat(Maps.flatten(searchResult.get(index), false, true).keySet(), equalTo(expectedFields)); } } finally { response.decRef();