CVE-2022-41918: Other (NVD-CWE-Other)
value: OpenSearch is a community-driven, open source fork of Elasticsearch and Kibana. There is an issue with the implementation of fine-grained access control rules (document-level security, field-level security and field masking) where they are not correctly applied to the indices that back data streams potentially leading to incorrect access authorization. OpenSearch 1.3.7 and 2.4.0 contain a fix for this issue. Users are advised to update. There are no known workarounds for this issue.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 6.3
Published: 2022-11-15T23:15Z
Repository: https://github.com/opensearch-project/security
Language: YAML
Files changed: src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java, src/test/java/org/opensearch/security/DataStreamIntegrationTests.java, src/test/resources/internal_users.yml, src/test/resources/roles.yml, src/test/resources/roles_mapping.yml
Commit date: 2022-11-07 16:55:08 -0500
Commit message: Merge pull request from GHSA-wmx7-x4jp-9jgg

* Resolving backing indices of data streams when resolving for aliases

* Fixing resolution of indices for non-wild card scenarios / exact matches

* Adding tests for DLS/FLS/Field-Masking on Data Streams

Co-authored-by: Sandesh Kumar <kusandes@amazon.com>
Diff: diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
index eb42792c..cbd7cb83 100644
--- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
@@ -67,10 +67,11 @@ import org.opensearch.security.securityconf.impl.v7.TenantV7;
 import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.support.WildcardMatcher;
 import org.opensearch.security.user.User;
 
 import static org.opensearch.cluster.metadata.IndexAbstraction.Type.ALIAS;
+import static org.opensearch.cluster.metadata.IndexAbstraction.Type.DATA_STREAM;
 
 public class ConfigModelV7 extends ConfigModel {
 
     protected final Logger log = LogManager.getLogger(this.getClass());
     private ConfigConstants.RolesMappingResolution rolesMappingResolution;
@@ -767,24 +768,26 @@ public class ConfigModelV7 extends ConfigModel {
         public Set<String> getResolvedIndexPattern(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs, final boolean appendUnresolved) {
             final String unresolved = getUnresolvedIndexPattern(user);
             final ImmutableSet.Builder<String> resolvedIndices = new ImmutableSet.Builder<>();
 
             final WildcardMatcher matcher = WildcardMatcher.from(unresolved);
+            boolean includeDataStreams = true;
             if (!(matcher instanceof WildcardMatcher.Exact)) {
-                final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream()
-                        .filter(e -> e.getValue().getType() == ALIAS)
+                final String[] aliasesAndDataStreamsForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream()
+                        .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM))
                         .filter(e -> matcher.test(e.getKey()))
                         .map(e -> e.getKey())
                         .toArray(String[]::new);
-                if (aliasesForPermittedPattern.length > 0) {
-                    final String[] resolvedAliases = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern);
-                    resolvedIndices.addAll(Arrays.asList(resolvedAliases));
+                if (aliasesAndDataStreamsForPermittedPattern.length > 0) {
+                    final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames(cs.state(),
+                            IndicesOptions.lenientExpandOpen(), includeDataStreams, aliasesAndDataStreamsForPermittedPattern);
+                    resolvedIndices.addAll(Arrays.asList(resolvedAliasesAndDataStreamIndices));
                 }
             }
 
             if (Strings.isNotBlank(unresolved)) {
-                final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved);
+                final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), includeDataStreams, unresolved);
                 resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern));
             }
 
             if (appendUnresolved || resolvedIndices.build().isEmpty()) {
                 resolvedIndices.add(unresolved);
diff --git a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java
index cc37a6d1..78c9ab78 100644
--- a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java
+++ b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java
@@ -20,10 +20,21 @@ import org.opensearch.security.test.helper.rest.RestHelper;
 import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse;
 
 
 public class DataStreamIntegrationTests extends SingleClusterTest {
 
+     final String bulkDocsBody =
+        "{ \"create\" : {} }" + System.lineSeparator() +
+        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"vlb44hny\", \"name\": \"Sam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator() +
+        "{ \"create\" : {} }" + System.lineSeparator() +
+        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"8a4f500d\", \"name\": \"Dam\"}, \"message\" : \"Login successful\" }" + System.lineSeparator() +
+        "{ \"create\" : {} }" + System.lineSeparator() +
+        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"l7gk7f82\", \"name\": \"Pam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator();
+
+    final String searchQuery1 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"8a4f500d\"}}}";
+    final String searchQuery2 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"l7gk7f82\"}}}";
+
     public String getIndexTemplateBody() {
         return  "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, \"template\": {\"settings\": { } } }";
     }
 
     public void createSampleDataStreams(RestHelper rh) throws Exception{
@@ -206,11 +217,11 @@ public class DataStreamIntegrationTests extends SingleClusterTest {
         response = rh.executeGetRequest("/_data_stream/my-data-stream*/_stats", encodeBasicHeader("ds3", "nagilum"));
         Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
     }
 
     @Test
-    public void testBackingIndicesOfDataStream() throws Exception {
+    public void testGetIndexOnBackingIndicesOfDataStream() throws Exception {
 
         setup();
         RestHelper rh = nonSslRestHelper();
         createSampleDataStreams(rh);
         HttpResponse response;
@@ -241,14 +252,250 @@ public class DataStreamIntegrationTests extends SingleClusterTest {
 
         response = rh.executeGetRequest(".ds-my-data-stream11-000001", encodeBasicHeader("ds2", "nagilum"));
         Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
 
         response = rh.executeGetRequest(".ds-my-data-stream22-000001", encodeBasicHeader("ds2", "nagilum"));
-        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
 
         response = rh.executeGetRequest(".ds-my-data-stream21-000001,.ds-my-data-stream22-000001", encodeBasicHeader("ds2", "nagilum"));
-        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
 
         response = rh.executeGetRequest(".ds-my-data-stream2*", encodeBasicHeader("ds2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+    }
+
+    @Test
+    public void testDocumentLevelSecurityOnDataStream() throws Exception {
+
+        setup();
+        RestHelper rh = nonSslRestHelper();
+        createSampleDataStreams(rh);
+        HttpResponse response;
+
+        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+
+        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("8a4f500d"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));
+
+        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));
+    }
+
+    @Test
+    public void testFLSOnBackingIndicesOfDataStream() throws Exception {
+
+        setup();
+        RestHelper rh = nonSslRestHelper();
+        createSampleDataStreams(rh);
+        HttpResponse response;
+
+        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+
+        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\"Login attempt failed\""));
+
+        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum"));
         Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fls3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\""));
+    }
+
+    @Test
+    public void testFieldMaskingOnDataStream() throws Exception {
+
+        setup();
+        RestHelper rh = nonSslRestHelper();
+        createSampleDataStreams(rh);
+        HttpResponse response;
+
+        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
+
+        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
+
+        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fm3", "nagilum"));
+        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
+        Assert.assertFalse(response.getBody().contains("\"name\":\"Pam\""));
+        Assert.assertTrue(response.getBody().contains("\"name\":\""));
+        Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\""));
+        Assert.assertTrue(response.getBody().contains("\"message\":\""));
     }
 }
diff --git a/src/test/resources/internal_users.yml b/src/test/resources/internal_users.yml
index 44464c9c..951b988b 100644
--- a/src/test/resources/internal_users.yml
+++ b/src/test/resources/internal_users.yml
@@ -353,10 +353,40 @@ pit-2:
   hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
   #password is: nagilum
 all-pit:
   hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
   #password is: nagilum
+ds_admin:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_dls1:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_dls2:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_dls3:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fls1:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fls2:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fls3:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fm1:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fm2:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
+ds_fm3:
+  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
+  #password is: nagilum
 hidden_test:
   hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
   opendistro_security_roles:
   - hidden_test
 sem-user:
diff --git a/src/test/resources/roles.yml b/src/test/resources/roles.yml
index 4da098e7..2bff2a97 100644
--- a/src/test/resources/roles.yml
+++ b/src/test/resources/roles.yml
@@ -1120,16 +1120,148 @@ data_stream_2:
 
 data_stream_3:
   reserved: true
   hidden: false
   description: "Migrated from v6 (all types mapped)"
-  cluster_permissions: []
+  cluster_permissions:
+    - "*"
   index_permissions:
     - index_patterns:
         - "*"
       allowed_actions:
         - "DATASTREAM_ALL"
+        - "indices:data/write/index"
+        - "indices:data/write/bulk*"
+
+data_stream_admin:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions:
+    - "*"
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream*"
+      allowed_actions:
+        - "*"
+
+data_stream_dls_1:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream11"
+      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
+      allowed_actions:
+        - "read"
+
+data_stream_dls_2:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream2*"
+      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
+      allowed_actions:
+        - "read"
+
+data_stream_dls_3:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream*"
+      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
+      allowed_actions:
+        - "read"
+
+data_stream_fls_1:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream11"
+      fls:
+        - "user.id"
+        - "message"
+      allowed_actions:
+        - "read"
+
+data_stream_fls_2:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream2*"
+      fls:
+        - "user.id"
+        - "user.name"
+      allowed_actions:
+        - "read"
+
+data_stream_fls_3:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream*"
+      fls:
+        - "~message"
+      allowed_actions:
+        - "read"
+
+data_stream_fm_1:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream11"
+      masked_fields:
+        - "message"
+      allowed_actions:
+        - "read"
+
+data_stream_fm_2:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream2*"
+      masked_fields:
+        - "message"
+      allowed_actions:
+        - "read"
+
+data_stream_fm_3:
+  reserved: true
+  hidden: false
+  description: "Migrated from v6 (all types mapped)"
+  cluster_permissions: []
+  index_permissions:
+    - index_patterns:
+        - "my-data-stream*"
+      masked_fields:
+        - "user.name"
+        - "message"
+      allowed_actions:
+        - "read"
 
 point_in_time_1:
   reserved: true
   hidden: false
   description: "Migrated from v6 (all types mapped)"
diff --git a/src/test/resources/roles_mapping.yml b/src/test/resources/roles_mapping.yml
index bc32c5b4..8c65fab3 100644
--- a/src/test/resources/roles_mapping.yml
+++ b/src/test/resources/roles_mapping.yml
@@ -426,10 +426,60 @@ point_in_time_2:
 point_in_time_all:
   reserved: false
   hidden: false
   users:
     - "all-pit"
+data_stream_admin:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_admin"
+data_stream_dls_1:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_dls1"
+data_stream_dls_2:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_dls2"
+data_stream_dls_3:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_dls3"
+data_stream_fls_1:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fls1"
+data_stream_fls_2:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fls2"
+data_stream_fls_3:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fls3"
+data_stream_fm_1:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fm1"
+data_stream_fm_2:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fm2"
+data_stream_fm_3:
+  reserved: false
+  hidden: false
+  users:
+    - "ds_fm3"
 sem-role:
   reserved: false
   hidden: false
   users:
     - "sem-user"

Vulnerable code:                 final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream()
                        .filter(e -> e.getValue().getType() == ALIAS)
                if (aliasesForPermittedPattern.length > 0) {
                    final String[] resolvedAliases = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern);
                    resolvedIndices.addAll(Arrays.asList(resolvedAliases));
                final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved);
    public void testBackingIndicesOfDataStream() throws Exception {
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());
  cluster_permissions: []
Fixed code: import static org.opensearch.cluster.metadata.IndexAbstraction.Type.DATA_STREAM;
            boolean includeDataStreams = true;
                final String[] aliasesAndDataStreamsForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream()
                        .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM))
                if (aliasesAndDataStreamsForPermittedPattern.length > 0) {
                    final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames(cs.state(),
                            IndicesOptions.lenientExpandOpen(), includeDataStreams, aliasesAndDataStreamsForPermittedPattern);
                    resolvedIndices.addAll(Arrays.asList(resolvedAliasesAndDataStreamIndices));
                final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), includeDataStreams, unresolved);
     final String bulkDocsBody =
        "{ \"create\" : {} }" + System.lineSeparator() +
        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"vlb44hny\", \"name\": \"Sam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator() +
        "{ \"create\" : {} }" + System.lineSeparator() +
        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"8a4f500d\", \"name\": \"Dam\"}, \"message\" : \"Login successful\" }" + System.lineSeparator() +
        "{ \"create\" : {} }" + System.lineSeparator() +
        "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"l7gk7f82\", \"name\": \"Pam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator();

    final String searchQuery1 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"8a4f500d\"}}}";
    final String searchQuery2 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"l7gk7f82\"}}}";

    public void testGetIndexOnBackingIndicesOfDataStream() throws Exception {
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
    }

    @Test
    public void testDocumentLevelSecurityOnDataStream() throws Exception {

        setup();
        RestHelper rh = nonSslRestHelper();
        createSampleDataStreams(rh);
        HttpResponse response;

        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));

        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("8a4f500d"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));

        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertFalse(response.getBody().contains("l7gk7f82"));
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}"));
    }

    @Test
    public void testFLSOnBackingIndicesOfDataStream() throws Exception {

        setup();
        RestHelper rh = nonSslRestHelper();
        createSampleDataStreams(rh);
        HttpResponse response;

        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));

        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\""));

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\""));

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\"Login attempt failed\""));

        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum"));

        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fls3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\""));
    }

    @Test
    public void testFieldMaskingOnDataStream() throws Exception {

        setup();
        RestHelper rh = nonSslRestHelper();
        createSampleDataStreams(rh);
        HttpResponse response;

        rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));
        rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum"));

        response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode());

        response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}"));
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));

        response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fm3", "nagilum"));
        Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
        Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\""));
        Assert.assertFalse(response.getBody().contains("\"name\":\"Pam\""));
        Assert.assertTrue(response.getBody().contains("\"name\":\""));
        Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\""));
        Assert.assertTrue(response.getBody().contains("\"message\":\""));
ds_admin:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_dls1:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_dls2:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_dls3:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fls1:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fls2:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fls3:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fm1:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fm2:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
ds_fm3:
  hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m
  #password is: nagilum
  cluster_permissions:
    - "*"
        - "indices:data/write/index"
        - "indices:data/write/bulk*"

data_stream_admin:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions:
    - "*"
  index_permissions:
    - index_patterns:
        - "my-data-stream*"
      allowed_actions:
        - "*"

data_stream_dls_1:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream11"
      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
      allowed_actions:
        - "read"

data_stream_dls_2:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream2*"
      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
      allowed_actions:
        - "read"

data_stream_dls_3:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream*"
      dls: "{\n  \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}"
      allowed_actions:
        - "read"

data_stream_fls_1:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream11"
      fls:
        - "user.id"
        - "message"
      allowed_actions:
        - "read"

data_stream_fls_2:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream2*"
      fls:
        - "user.id"
        - "user.name"
      allowed_actions:
        - "read"

data_stream_fls_3:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream*"
      fls:
        - "~message"
      allowed_actions:
        - "read"

data_stream_fm_1:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream11"
      masked_fields:
        - "message"
      allowed_actions:
        - "read"

data_stream_fm_2:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream2*"
      masked_fields:
        - "message"
      allowed_actions:
        - "read"

data_stream_fm_3:
  reserved: true
  hidden: false
  description: "Migrated from v6 (all types mapped)"
  cluster_permissions: []
  index_permissions:
    - index_patterns:
        - "my-data-stream*"
      masked_fields:
        - "user.name"
        - "message"
      allowed_actions:
        - "read"
data_stream_admin:
  reserved: false
  hidden: false
  users:
    - "ds_admin"
data_stream_dls_1:
  reserved: false
  hidden: false
  users:
    - "ds_dls1"
data_stream_dls_2:
  reserved: false
  hidden: false
  users:
    - "ds_dls2"
data_stream_dls_3:
  reserved: false
  hidden: false
  users:
    - "ds_dls3"
data_stream_fls_1:
  reserved: false
  hidden: false
  users:
    - "ds_fls1"
data_stream_fls_2:
  reserved: false
  hidden: false
  users:
    - "ds_fls2"
data_stream_fls_3:
  reserved: false
  hidden: false
  users:
    - "ds_fls3"
data_stream_fm_1:
  reserved: false
  hidden: false
  users:
    - "ds_fm1"
data_stream_fm_2:
  reserved: false
  hidden: false
  users:
    - "ds_fm2"
data_stream_fm_3:
  reserved: false
  hidden: false
  users:
    - "ds_fm3"