Selaa lähdekoodia

Add new predefined reserved roles (#71710)

This change adds two new reserved roles in Elasticsearch, viewer
and editor
Solutions will use these roles in order to provide,out of the box,
a role for full access to data and configuration with no access to
cluster management(editor) and a role for read only access to data
again with no access to cluster management (viewer).
Ioannis Kakavas 4 vuotta sitten
vanhempi
commit
5a7b1c6c35

+ 1 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -691,7 +691,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
             List<Role> roles = response.getRoles();
             assertNotNull(response);
             // 29 system roles plus the three we created
-            assertThat(roles.size(), equalTo(29 + 3));
+            assertThat(roles.size(), equalTo(31 + 3));
         }
 
         {

+ 51 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java

@@ -336,9 +336,60 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                             .indices(".enrich-*")
                             .privileges("manage", "read", "write")
                             .build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
+                .put("viewer", buildViewerRoleDescriptor())
+                .put("editor", buildEditorRoleDescriptor())
                 .immutableMap();
     }
 
+    private static RoleDescriptor buildViewerRoleDescriptor() {
+        return new RoleDescriptor(
+            "viewer",
+            new String[] {},
+            new RoleDescriptor.IndicesPrivileges[] {
+                // Stack
+                RoleDescriptor.IndicesPrivileges.builder()
+                    .indices("/~(([.]|ilm-history-).*)/")
+                    .privileges("read", "view_index_metadata").build(),
+                // Security
+                RoleDescriptor.IndicesPrivileges.builder().indices(".siem-signals-*").privileges("read", "view_index_metadata").build() },
+            new RoleDescriptor.ApplicationResourcePrivileges[] {
+                RoleDescriptor.ApplicationResourcePrivileges.builder()
+                    .application("kibana-.kibana")
+                    .resources("*")
+                    .privileges("read").build() },
+            null,
+            null,
+            MetadataUtils.DEFAULT_RESERVED_METADATA,
+            null);
+    }
+
+    private static RoleDescriptor buildEditorRoleDescriptor() {
+        return new RoleDescriptor("editor",
+            new String[] {},
+            new RoleDescriptor.IndicesPrivileges[] {
+                // Stack
+                RoleDescriptor.IndicesPrivileges.builder()
+                    .indices("/~(([.]|ilm-history-).*)/")
+                    .privileges("read", "view_index_metadata").build(),
+                // Observability
+                RoleDescriptor.IndicesPrivileges.builder()
+                    .indices("observability-annotations")
+                    .privileges("read", "view_index_metadata", "write").build(),
+                // Security
+                RoleDescriptor.IndicesPrivileges.builder()
+                    .indices(".siem-signals-*", ".lists-*", ".items-*")
+                    .privileges("read", "view_index_metadata", "write", "maintenance").build() },
+            new RoleDescriptor.ApplicationResourcePrivileges[] {
+                RoleDescriptor.ApplicationResourcePrivileges.builder()
+                    .application("kibana-.kibana")
+                    .resources("*")
+                    .privileges("all").build() },
+            null,
+            null,
+            MetadataUtils.DEFAULT_RESERVED_METADATA,
+            null);
+    }
+
     private static RoleDescriptor kibanaAdminUser(String name, Map<String, Object> metadata) {
         return new RoleDescriptor(name, null, null,
             new RoleDescriptor.ApplicationResourcePrivileges[] {

+ 119 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java

@@ -230,6 +230,8 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(ReservedRolesStore.isReserved("snapshot_user"), is(true));
         assertThat(ReservedRolesStore.isReserved("code_admin"), is(false));
         assertThat(ReservedRolesStore.isReserved("code_user"), is(false));
+        assertThat(ReservedRolesStore.isReserved("viewer"), is(true));
+        assertThat(ReservedRolesStore.isReserved("editor"), is(true));
     }
 
     public void testSnapshotUserRole() {
@@ -1655,6 +1657,123 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
     }
 
+    public void testPredefinedViewerRole() {
+        final TransportRequest request = mock(TransportRequest.class);
+        final Authentication authentication = mock(Authentication.class);
+
+        RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("viewer");
+        assertNotNull(roleDescriptor);
+        assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
+
+        Role role = Role.builder(roleDescriptor, null).build();
+        // No cluster privileges
+        assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
+        // Check index privileges
+        assertOnlyReadAllowed(role, "observability-annotations");
+        assertOnlyReadAllowed(role, "logs-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "metrics-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "synthetics-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "apm-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "traces-apm." + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "filebeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "metricbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "heardbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "kibana_sample_data_-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, ".siem-signals-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "apm-" + randomIntBetween(0, 5) + "-transaction-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "logs-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "auditbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "filebeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "packetbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "winlogbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "endgame-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, randomAlphaOfLength(5));
+
+        assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
+        assertNoAccessAllowed(role, "." + randomAlphaOfLengthBetween(6, 10));
+        assertNoAccessAllowed(role, "ilm-history-" + randomIntBetween(0, 5));
+        // Check application privileges
+        assertThat(role.application().grants(new ApplicationPrivilege("kibana-.kibana", "kibana-read", "read"), "*"), is(true));
+        assertThat(role.application().grants(new ApplicationPrivilege("kibana-.kibana", "kibana-all", "all"), "*"), is(false));
+
+        assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 20)), is(false));
+    }
+
+    public void testPredefinedEditorRole() {
+        final TransportRequest request = mock(TransportRequest.class);
+        final Authentication authentication = mock(Authentication.class);
+
+        RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("editor");
+        assertNotNull(roleDescriptor);
+        assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
+
+        Role role = Role.builder(roleDescriptor, null).build();
+
+        // No cluster privileges
+        assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
+        assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
+
+        // Check index privileges
+        assertOnlyReadAllowed(role, "logs-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "metrics-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "synthetics-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "apm-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "traces-apm." + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "filebeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "metricbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "heardbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "kibana_sample_data_-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "apm-" + randomIntBetween(0, 5) + "-transaction-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "logs-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "auditbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "filebeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "packetbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "winlogbeat-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, "endgame-" + randomIntBetween(0, 5));
+        assertOnlyReadAllowed(role, randomAlphaOfLength(5));
+
+        assertReadWriteDocsAndMaintenanceButNotDeleteIndexAllowed(role, ".siem-signals-" + randomIntBetween(0, 5));
+        assertReadWriteDocsAndMaintenanceButNotDeleteIndexAllowed(role, ".lists-" + randomIntBetween(0, 5));
+        assertReadWriteDocsAndMaintenanceButNotDeleteIndexAllowed(role, ".items-" + randomIntBetween(0, 5));
+        assertReadWriteDocsButNotDeleteIndexAllowed(role, "observability-annotations");
+
+        assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
+        assertNoAccessAllowed(role, "." + randomAlphaOfLengthBetween(6, 10));
+        assertNoAccessAllowed(role, "ilm-history-" + randomIntBetween(0, 5));
+
+        // Check application privileges
+        assertThat(role.application().grants(new ApplicationPrivilege("kibana-.kibana", "kibana-all", "all"), "*"), is(true));
+
+        assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 20)), is(false));
+    }
+
+    private void assertReadWriteDocsAndMaintenanceButNotDeleteIndexAllowed(Role role, String index) {
+        assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false));
+        assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher(UpdateAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher(BulkAction.NAME).test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher("indices:admin/refresh*").test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher("indices:admin/flush*").test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher("indices:admin/synced_flush").test(mockIndexAbstraction(index)), is(true));
+        assertThat(role.indices().allowedIndicesMatcher("indices:admin/forcemerge*").test(mockIndexAbstraction(index)), is(true));
+    }
+
     private void assertReadWriteDocsButNotDeleteIndexAllowed(Role role, String index) {
         assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false));
         assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true));