CVE-2022-41918 (published 2022-11-15T23:15Z)

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.

CVSS v3 base score: 6.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
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.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/opensearch-project/security
commit_details: repo_url: https://github.com/opensearch-project/security
author: Dave Lago
author_date: 2022-11-07 16:55:08-05:00
msg: 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>
repository: repo_url: https://github.com/opensearch-project/security
repo_name: opensearch-project/security
description: 🔐 Secure your cluster with TLS, numerous authentication backends, data masking, audit logging as well as role-based access control on indices, documents, and fields
repo_language: Java
owner: opensearch-project
file_changes: filename: ConfigModelV7.java
change_type: ModificationType.MODIFY
diff: @@ -69,6 +69,7 @@ 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 {
 
@@ -769,20 +770,22 @@ public class ConfigModelV7 extends ConfigModel {
             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));
             }
 

programming_language: Java
filename: DataStreamIntegrationTests.java
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,17 @@ 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\": { } } }";
     }
@@ -208,7 +219,7 @@ public class DataStreamIntegrationTests extends SingleClusterTest {
     }
 
     @Test
-    public void testBackingIndicesOfDataStream() throws Exception {
+    public void testGetIndexOnBackingIndicesOfDataStream() throws Exception {
 
         setup();
         RestHelper rh = nonSslRestHelper();
@@ -243,12 +254,248 @@ public class DataStreamIntegrationTests extends SingleClusterTest {
         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\":\""));
     }
 }

programming_language: Java
filename: internal_users.yml
change_type: ModificationType.MODIFY
diff: @@ -355,6 +355,36 @@ pit-2:
 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:

programming_language: CoffeeScript
filename: roles.yml
change_type: ModificationType.MODIFY
diff: @@ -1122,12 +1122,144 @@ 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

programming_language: CoffeeScript
filename: roles_mapping.yml
change_type: ModificationType.MODIFY
diff: @@ -428,6 +428,56 @@ point_in_time_all:
   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

programming_language: CoffeeScript