You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2019/05/02 14:18:10 UTC

[incubator-druid] branch master updated: Adjust required permissions for system schema (#7579)

This is an automated email from the ASF dual-hosted git repository.

fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git


The following commit(s) were added to refs/heads/master by this push:
     new a013350  Adjust required permissions for system schema (#7579)
a013350 is described below

commit a013350018d96cbb40944a0b5e060d510eeabdbc
Author: Jonathan Wei <jo...@users.noreply.github.com>
AuthorDate: Thu May 2 07:18:02 2019 -0700

    Adjust required permissions for system schema (#7579)
    
    * Adjust required permissions for system schema
    
    * PR comments, fix current_size handling
    
    * Checkstyle
    
    * Set curr_size instead of current_size
    
    * Adjust information schema docs
    
    * Fix merge conflict
    
    * Update tests
---
 .../extensions-core/druid-basic-security.md        |  14 +
 docs/content/querying/sql.md                       |   4 +
 integration-tests/docker/sample-data.sql           |   2 +
 .../security/ITBasicAuthConfigurationTest.java     | 511 +++++++++++++++++++--
 .../results/auth_test_sys_schema_segments.json     |  18 +
 .../auth_test_sys_schema_server_segments.json      |   6 +
 .../results/auth_test_sys_schema_servers.json      |  12 +
 .../results/auth_test_sys_schema_tasks.json        |  17 +
 .../firehose/EventReceiverFirehoseFactory.java     |   5 +-
 .../server/http/security/StateResourceFilter.java  |   3 +-
 .../org/apache/druid/server/security/Resource.java |   2 +
 .../druid/sql/calcite/schema/SystemSchema.java     |  60 ++-
 12 files changed, 593 insertions(+), 61 deletions(-)

diff --git a/docs/content/development/extensions-core/druid-basic-security.md b/docs/content/development/extensions-core/druid-basic-security.md
index adba32b..28eff1f 100644
--- a/docs/content/development/extensions-core/druid-basic-security.md
+++ b/docs/content/development/extensions-core/druid-basic-security.md
@@ -310,6 +310,20 @@ For information on what HTTP methods are supported on a particular request endpo
 
 GET requires READ permission, while POST and DELETE require WRITE permission.
 
+### SQL Permissions
+
+Queries on Druid datasources require DATASOURCE READ permissions for the specified datasource.
+
+Queries on the [INFORMATION_SCHEMA tables](../../querying/sql.html#information-schema) will
+return information about datasources that the caller has DATASOURCE READ access to. Other
+datasources will be omitted.
+
+Queries on the [system schema tables](../../querying/sql.html#system-schema) require the following permissions:
+- `segments`: Segments will be filtered based on DATASOURCE READ permissions.
+- `servers`: The user requires STATE READ permissions.
+- `server_segments`: The user requires STATE READ permissions and segments will be filtered based on DATASOURCE READ permissions.
+- `tasks`: Tasks will be filtered based on DATASOURCE READ permissions.
+
 ## Configuration Propagation
 
 To prevent excessive load on the Coordinator, the Authenticator and Authorizer user/role database state is cached on each Druid process.
diff --git a/docs/content/querying/sql.md b/docs/content/querying/sql.md
index 1fc1243..4871594 100644
--- a/docs/content/querying/sql.md
+++ b/docs/content/querying/sql.md
@@ -739,3 +739,7 @@ Broker will emit the following metrics for SQL.
 |`sqlQuery/time`|Milliseconds taken to complete a SQL.|id, nativeQueryIds, dataSource, remoteAddress, success.|< 1s|
 |`sqlQuery/bytes`|number of bytes returned in SQL response.|id, nativeQueryIds, dataSource, remoteAddress, success.| |
 
+
+## Authorization Permissions
+
+Please see [Defining SQL permissions](../../development/extensions-core/druid-basic-security.html#sql-permissions) for information on what permissions are needed for making SQL queries in a secured cluster.
\ No newline at end of file
diff --git a/integration-tests/docker/sample-data.sql b/integration-tests/docker/sample-data.sql
index 18ab48a..69bf6ea 100644
--- a/integration-tests/docker/sample-data.sql
+++ b/integration-tests/docker/sample-data.sql
@@ -18,3 +18,5 @@ INSERT INTO druid_segments (id,dataSource,created_date,start,end,partitioned,ver
 INSERT INTO druid_segments (id,dataSource,created_date,start,end,partitioned,version,used,payload) VALUES ('twitterstream_2013-01-03T00:00:00.000Z_2013-01-04T00:00:00.000Z_2013-01-04T04:09:13.590Z_v9','twitterstream','2013-05-13T00:03:48.807Z','2013-01-03T00:00:00.000Z','2013-01-04T00:00:00.000Z',0,'2013-01-04T04:09:13.590Z_v9',1,'{\"dataSource\":\"twitterstream\",\"interval\":\"2013-01-03T00:00:00.000Z/2013-01-04T00:00:00.000Z\",\"version\":\"2013-01-04T04:09:13.590Z_v9\",\"loadSpec\":{ [...]
 INSERT INTO druid_segments (id,dataSource,created_date,start,end,partitioned,version,used,payload) VALUES ('wikipedia_editstream_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9','wikipedia_editstream','2013-03-15T20:49:52.348Z','2012-12-29T00:00:00.000Z','2013-01-10T08:00:00.000Z',0,'2013-01-10T08:13:47.830Z_v9',1,'{\"dataSource\":\"wikipedia_editstream\",\"interval\":\"2012-12-29T00:00:00.000Z/2013-01-10T08:00:00.000Z\",\"version\":\"2013-01-10T08:13:47.830 [...]
 INSERT INTO druid_segments (id, dataSource, created_date, start, end, partitioned, version, used, payload) VALUES ('wikipedia_2013-08-01T00:00:00.000Z_2013-08-02T00:00:00.000Z_2013-08-08T21:22:48.989Z', 'wikipedia', '2013-08-08T21:26:23.799Z', '2013-08-01T00:00:00.000Z', '2013-08-02T00:00:00.000Z', '0', '2013-08-08T21:22:48.989Z', '1', '{\"dataSource\":\"wikipedia\",\"interval\":\"2013-08-01T00:00:00.000Z/2013-08-02T00:00:00.000Z\",\"version\":\"2013-08-08T21:22:48.989Z\",\"loadSpec\":{\ [...]
+INSERT INTO druid_tasks (id, created_date, datasource, payload, status_payload, active) VALUES ('index_auth_test_2030-04-30T01:13:31.893Z', '2030-04-30T01:13:31.893Z', 'auth_test', '{\"id\":\"index_auth_test_2030-04-30T01:13:31.893Z\",\"created_date\":\"2030-04-30T01:13:31.893Z\",\"datasource\":\"auth_test\",\"active\":0}', '{\"id\":\"index_auth_test_2030-04-30T01:13:31.893Z\",\"status\":\"SUCCESS\",\"duration\":1}', 0);
+INSERT INTO druid_segments (id,dataSource,created_date,start,end,partitioned,version,used,payload) VALUES ('auth_test_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9','auth_test','2013-03-15T20:49:52.348Z','2012-12-29T00:00:00.000Z','2013-01-10T08:00:00.000Z',0,'2013-01-10T08:13:47.830Z_v9',1,'{\"dataSource\":\"auth_test\",\"interval\":\"2012-12-29T00:00:00.000Z/2013-01-10T08:00:00.000Z\",\"version\":\"2013-01-10T08:13:47.830Z_v9\",\"loadSpec\":{\"type\":\"s [...]
diff --git a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java
index 7272e5e..dfa3791 100644
--- a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java
+++ b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java
@@ -21,6 +21,9 @@ package org.apache.druid.tests.security;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 import org.apache.calcite.avatica.AvaticaSqlException;
 import org.apache.druid.guice.annotations.Client;
@@ -40,10 +43,14 @@ import org.apache.druid.server.security.ResourceAction;
 import org.apache.druid.server.security.ResourceType;
 import org.apache.druid.sql.avatica.DruidAvaticaHandler;
 import org.apache.druid.testing.IntegrationTestingConfig;
+import org.apache.druid.testing.clients.CoordinatorResourceTestClient;
 import org.apache.druid.testing.guice.DruidTestModuleFactory;
+import org.apache.druid.testing.utils.RetryUtil;
+import org.apache.druid.testing.utils.TestQueryHelper;
 import org.jboss.netty.handler.codec.http.HttpMethod;
 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
@@ -55,9 +62,11 @@ import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.Statement;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.stream.Collectors;
 
 @Guice(moduleFactory = DruidTestModuleFactory.class)
 public class ITBasicAuthConfigurationTest
@@ -69,6 +78,32 @@ public class ITBasicAuthConfigurationTest
       {
       };
 
+  private static final TypeReference SYS_SCHEMA_RESULTS_TYPE_REFERENCE =
+      new TypeReference<List<Map<String, Object>>>()
+      {
+      };
+
+  private static final String SYSTEM_SCHEMA_SEGMENTS_RESULTS_RESOURCE =
+      "/results/auth_test_sys_schema_segments.json";
+  private static final String SYSTEM_SCHEMA_SERVER_SEGMENTS_RESULTS_RESOURCE =
+      "/results/auth_test_sys_schema_server_segments.json";
+  private static final String SYSTEM_SCHEMA_SERVERS_RESULTS_RESOURCE =
+      "/results/auth_test_sys_schema_servers.json";
+  private static final String SYSTEM_SCHEMA_TASKS_RESULTS_RESOURCE =
+      "/results/auth_test_sys_schema_tasks.json";
+
+  private static final String SYS_SCHEMA_SEGMENTS_QUERY =
+      "SELECT * FROM sys.segments WHERE datasource IN ('auth_test')";
+
+  private static final String SYS_SCHEMA_SERVERS_QUERY =
+      "SELECT * FROM sys.servers";
+
+  private static final String SYS_SCHEMA_SERVER_SEGMENTS_QUERY =
+      "SELECT * FROM sys.server_segments WHERE segment_id LIKE 'auth_test%'";
+
+  private static final String SYS_SCHEMA_TASKS_QUERY =
+      "SELECT * FROM sys.tasks WHERE datasource IN ('auth_test')";
+
   @Inject
   IntegrationTestingConfig config;
 
@@ -81,83 +116,306 @@ public class ITBasicAuthConfigurationTest
 
   StatusResponseHandler responseHandler = new StatusResponseHandler(StandardCharsets.UTF_8);
 
+  @Inject
+  private CoordinatorResourceTestClient coordinatorClient;
+
+  @BeforeMethod
+  public void before() throws Exception
+  {
+    // ensure that auth_test segments are loaded completely, we use them for testing system schema tables
+    RetryUtil.retryUntilTrue(
+        () -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load"
+    );
+  }
+
   @Test
-  public void testAuthConfiguration() throws Exception
+  public void testSystemSchemaAccess() throws Exception
   {
     HttpClient adminClient = new CredentialedHttpClient(
         new BasicCredentials("admin", "priest"),
         httpClient
     );
 
-    HttpClient internalSystemClient = new CredentialedHttpClient(
-        new BasicCredentials("druid_system", "warlock"),
+    // check that admin access works on all nodes
+    checkNodeAccess(adminClient);
+
+    // create a new user+role that can only read 'auth_test'
+    List<ResourceAction> readDatasourceOnlyPermissions = Collections.singletonList(
+        new ResourceAction(
+            new Resource("auth_test", ResourceType.DATASOURCE),
+            Action.READ
+        )
+    );
+    createUserAndRoleWithPermissions(
+        adminClient,
+        "datasourceOnlyUser",
+        "helloworld",
+        "datasourceOnlyRole",
+        readDatasourceOnlyPermissions
+    );
+    HttpClient datasourceOnlyUserClient = new CredentialedHttpClient(
+        new BasicCredentials("datasourceOnlyUser", "helloworld"),
         httpClient
     );
 
-    HttpClient newUserClient = new CredentialedHttpClient(
-        new BasicCredentials("druid", "helloworld"),
+    // create a new user+role that can only read 'auth_test' + STATE read access
+    List<ResourceAction> readDatasourceWithStatePermissions = ImmutableList.of(
+        new ResourceAction(
+            new Resource("auth_test", ResourceType.DATASOURCE),
+            Action.READ
+        ),
+        new ResourceAction(
+            new Resource(".*", ResourceType.STATE),
+            Action.READ
+        )
+    );
+    createUserAndRoleWithPermissions(
+        adminClient,
+        "datasourceWithStateUser",
+        "helloworld",
+        "datasourceWithStateRole",
+        readDatasourceWithStatePermissions
+    );
+    HttpClient datasourceWithStateUserClient = new CredentialedHttpClient(
+        new BasicCredentials("datasourceWithStateUser", "helloworld"),
         httpClient
     );
 
-    final HttpClient unsecuredClient = httpClient;
+    // create a new user+role with only STATE read access
+    List<ResourceAction> stateOnlyPermissions = ImmutableList.of(
+        new ResourceAction(
+            new Resource(".*", ResourceType.STATE),
+            Action.READ
+        )
+    );
+    createUserAndRoleWithPermissions(
+        adminClient,
+        "stateOnlyUser",
+        "helloworld",
+        "stateOnlyRole",
+        stateOnlyPermissions
+    );
+    HttpClient stateOnlyUserClient = new CredentialedHttpClient(
+        new BasicCredentials("stateOnlyUser", "helloworld"),
+        httpClient
+    );
 
-    // check that we are allowed to access unsecured path without credentials.
-    checkUnsecuredCoordinatorLoadQueuePath(unsecuredClient);
+    // check that we can access a datasource-permission restricted resource on the broker
+    makeRequest(
+        datasourceOnlyUserClient,
+        HttpMethod.GET,
+        config.getBrokerUrl() + "/druid/v2/datasources/auth_test",
+        null
+    );
 
-    // check that admin works
-    checkNodeAccess(adminClient);
+    // check that we can access a state-permission restricted resource on the broker
+    makeRequest(datasourceWithStateUserClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null);
+    makeRequest(stateOnlyUserClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null);
 
-    // check that internal user works
-    checkNodeAccess(internalSystemClient);
+    // initial setup is done now, run the system schema response content tests
+    final List<Map<String, Object>> adminSegments = jsonMapper.readValue(
+        TestQueryHelper.class.getResourceAsStream(SYSTEM_SCHEMA_SEGMENTS_RESULTS_RESOURCE),
+        SYS_SCHEMA_RESULTS_TYPE_REFERENCE
+    );
 
-    // create a new user that can read /status
-    makeRequest(
-        adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid",
-        null
+    final List<Map<String, Object>> adminServerSegments = jsonMapper.readValue(
+        TestQueryHelper.class.getResourceAsStream(SYSTEM_SCHEMA_SERVER_SEGMENTS_RESULTS_RESOURCE),
+        SYS_SCHEMA_RESULTS_TYPE_REFERENCE
     );
 
-    makeRequest(
+    final List<Map<String, Object>> adminServers = getServersWithoutCurrentSize(
+        jsonMapper.readValue(
+            TestQueryHelper.class.getResourceAsStream(SYSTEM_SCHEMA_SERVERS_RESULTS_RESOURCE),
+            SYS_SCHEMA_RESULTS_TYPE_REFERENCE
+        )
+    );
+
+    final List<Map<String, Object>> adminTasks = jsonMapper.readValue(
+        TestQueryHelper.class.getResourceAsStream(SYSTEM_SCHEMA_TASKS_RESULTS_RESOURCE),
+        SYS_SCHEMA_RESULTS_TYPE_REFERENCE
+    );
+
+    // as admin
+    LOG.info("Checking sys.segments query as admin...");
+    verifySystemSchemaQuery(
         adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid/credentials",
-        jsonMapper.writeValueAsBytes(new BasicAuthenticatorCredentialUpdate("helloworld", 5000))
+        SYS_SCHEMA_SEGMENTS_QUERY,
+        adminSegments
     );
 
-    makeRequest(
+    LOG.info("Checking sys.servers query as admin...");
+    verifySystemSchemaServerQuery(
         adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid",
-        null
+        SYS_SCHEMA_SERVERS_QUERY,
+        getServersWithoutCurrentSize(adminServers)
     );
 
-    makeRequest(
+    LOG.info("Checking sys.server_segments query as admin...");
+    verifySystemSchemaQuery(
         adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/roles/druidrole",
-        null
+        SYS_SCHEMA_SERVER_SEGMENTS_QUERY,
+        adminServerSegments
     );
 
-    makeRequest(
+    LOG.info("Checking sys.tasks query as admin...");
+    verifySystemSchemaQuery(
         adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid/roles/druidrole",
-        null
+        SYS_SCHEMA_TASKS_QUERY,
+        adminTasks
+    );
+
+    // as user that can only read auth_test
+    LOG.info("Checking sys.segments query as datasourceOnlyUser...");
+    verifySystemSchemaQuery(
+        datasourceOnlyUserClient,
+        SYS_SCHEMA_SEGMENTS_QUERY,
+        adminSegments.stream()
+                     .filter((segmentEntry) -> {
+                       return "auth_test".equals(segmentEntry.get("datasource"));
+                     })
+                     .collect(Collectors.toList())
+    );
+
+    LOG.info("Checking sys.servers query as datasourceOnlyUser...");
+    verifySystemSchemaQueryFailure(
+        datasourceOnlyUserClient,
+        SYS_SCHEMA_SERVERS_QUERY,
+        HttpResponseStatus.FORBIDDEN,
+        "{\"Access-Check-Result\":\"Insufficient permission to view servers : Allowed:false, Message:\"}"
+    );
+
+    LOG.info("Checking sys.server_segments query as datasourceOnlyUser...");
+    verifySystemSchemaQueryFailure(
+        datasourceOnlyUserClient,
+        SYS_SCHEMA_SERVER_SEGMENTS_QUERY,
+        HttpResponseStatus.FORBIDDEN,
+        "{\"Access-Check-Result\":\"Insufficient permission to view servers : Allowed:false, Message:\"}"
+    );
+
+    LOG.info("Checking sys.tasks query as datasourceOnlyUser...");
+    verifySystemSchemaQuery(
+        datasourceOnlyUserClient,
+        SYS_SCHEMA_TASKS_QUERY,
+        adminTasks.stream()
+                     .filter((taskEntry) -> {
+                       return "auth_test".equals(taskEntry.get("datasource"));
+                     })
+                     .collect(Collectors.toList())
+    );
+
+    // as user that can read auth_test and STATE
+    LOG.info("Checking sys.segments query as datasourceWithStateUser...");
+    verifySystemSchemaQuery(
+        datasourceWithStateUserClient,
+        SYS_SCHEMA_SEGMENTS_QUERY,
+        adminSegments.stream()
+                     .filter((segmentEntry) -> {
+                       return "auth_test".equals(segmentEntry.get("datasource"));
+                     })
+                     .collect(Collectors.toList())
+    );
+
+    LOG.info("Checking sys.servers query as datasourceWithStateUser...");
+    verifySystemSchemaServerQuery(
+        datasourceWithStateUserClient,
+        SYS_SCHEMA_SERVERS_QUERY,
+        adminServers
+    );
+
+    LOG.info("Checking sys.server_segments query as datasourceWithStateUser...");
+    verifySystemSchemaQuery(
+        datasourceWithStateUserClient,
+        SYS_SCHEMA_SERVER_SEGMENTS_QUERY,
+        adminServerSegments.stream()
+                           .filter((serverSegmentEntry) -> {
+                             return ((String) serverSegmentEntry.get("segment_id")).contains("auth_test");
+                           })
+                           .collect(Collectors.toList())
+    );
+
+    LOG.info("Checking sys.tasks query as datasourceWithStateUser...");
+    verifySystemSchemaQuery(
+        datasourceWithStateUserClient,
+        SYS_SCHEMA_TASKS_QUERY,
+        adminTasks.stream()
+                     .filter((taskEntry) -> {
+                       return "auth_test".equals(taskEntry.get("datasource"));
+                     })
+                     .collect(Collectors.toList())
+    );
+
+    // as user that can only read STATE
+    LOG.info("Checking sys.segments query as stateOnlyUser...");
+    verifySystemSchemaQuery(
+        stateOnlyUserClient,
+        SYS_SCHEMA_SEGMENTS_QUERY,
+        Collections.emptyList()
+    );
+
+    LOG.info("Checking sys.servers query as stateOnlyUser...");
+    verifySystemSchemaServerQuery(
+        stateOnlyUserClient,
+        SYS_SCHEMA_SERVERS_QUERY,
+        adminServers
+    );
+
+    LOG.info("Checking sys.server_segments query as stateOnlyUser...");
+    verifySystemSchemaQuery(
+        stateOnlyUserClient,
+        SYS_SCHEMA_SERVER_SEGMENTS_QUERY,
+        Collections.emptyList()
     );
 
+    LOG.info("Checking sys.tasks query as stateOnlyUser...");
+    verifySystemSchemaQuery(
+        stateOnlyUserClient,
+        SYS_SCHEMA_TASKS_QUERY,
+        Collections.emptyList()
+    );
+  }
+
+  @Test
+  public void testAuthConfiguration() throws Exception
+  {
+    HttpClient adminClient = new CredentialedHttpClient(
+        new BasicCredentials("admin", "priest"),
+        httpClient
+    );
+
+    HttpClient internalSystemClient = new CredentialedHttpClient(
+        new BasicCredentials("druid_system", "warlock"),
+        httpClient
+    );
+
+    HttpClient newUserClient = new CredentialedHttpClient(
+        new BasicCredentials("druid", "helloworld"),
+        httpClient
+    );
+
+    final HttpClient unsecuredClient = httpClient;
+
+    // check that we are allowed to access unsecured path without credentials.
+    checkUnsecuredCoordinatorLoadQueuePath(unsecuredClient);
+
+    // check that admin works
+    checkNodeAccess(adminClient);
+
+    // check that internal user works
+    checkNodeAccess(internalSystemClient);
+
+    // create a new user+role that can read /status
     List<ResourceAction> permissions = Collections.singletonList(
         new ResourceAction(
             new Resource(".*", ResourceType.STATE),
             Action.READ
         )
     );
-    byte[] permissionsBytes = jsonMapper.writeValueAsBytes(permissions);
-    makeRequest(
+    createUserAndRoleWithPermissions(
         adminClient,
-        HttpMethod.POST,
-        config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/roles/druidrole/permissions",
-        permissionsBytes
+        "druid",
+        "helloworld",
+        "druidrole",
+        permissions
     );
 
     // check that the new user works
@@ -166,7 +424,6 @@ public class ITBasicAuthConfigurationTest
     // check loadStatus
     checkLoadStatus(adminClient);
 
-
     // create 100 users
     for (int i = 0; i < 100; i++) {
       makeRequest(
@@ -334,6 +591,23 @@ public class ITBasicAuthConfigurationTest
 
   private StatusResponseHolder makeRequest(HttpClient httpClient, HttpMethod method, String url, byte[] content)
   {
+    return makeRequestWithExpectedStatus(
+        httpClient,
+        method,
+        url,
+        content,
+        HttpResponseStatus.OK
+    );
+  }
+
+  private StatusResponseHolder makeRequestWithExpectedStatus(
+      HttpClient httpClient,
+      HttpMethod method,
+      String url,
+      byte[] content,
+      HttpResponseStatus expectedStatus
+  )
+  {
     try {
       Request request = new Request(method, new URL(url));
       if (content != null) {
@@ -349,7 +623,7 @@ public class ITBasicAuthConfigurationTest
             responseHandler
         ).get();
 
-        if (!response.getStatus().equals(HttpResponseStatus.OK)) {
+        if (!response.getStatus().equals(expectedStatus)) {
           String errMsg = StringUtils.format(
               "Error while making request to url[%s] status[%s] content[%s]",
               url,
@@ -357,7 +631,7 @@ public class ITBasicAuthConfigurationTest
               response.getContent()
           );
           // it can take time for the auth config to propagate, so we retry
-          if (retryCount > 4) {
+          if (retryCount > 10) {
             throw new ISE(errMsg);
           } else {
             LOG.error(errMsg);
@@ -375,4 +649,157 @@ public class ITBasicAuthConfigurationTest
       throw new RuntimeException(e);
     }
   }
+
+  private void createUserAndRoleWithPermissions(
+      HttpClient adminClient,
+      String user,
+      String password,
+      String role,
+      List<ResourceAction> permissions
+  ) throws Exception
+  {
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authentication/db/basic/users/%s",
+            config.getCoordinatorUrl(),
+            user
+        ),
+        null
+    );
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authentication/db/basic/users/%s/credentials",
+            config.getCoordinatorUrl(),
+            user
+        ),
+        jsonMapper.writeValueAsBytes(new BasicAuthenticatorCredentialUpdate(password, 5000))
+    );
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authorization/db/basic/users/%s",
+            config.getCoordinatorUrl(),
+            user
+        ),
+        null
+    );
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authorization/db/basic/roles/%s",
+            config.getCoordinatorUrl(),
+            role
+        ),
+        null
+    );
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authorization/db/basic/users/%s/roles/%s",
+            config.getCoordinatorUrl(),
+            user,
+            role
+        ),
+        null
+    );
+    byte[] permissionsBytes = jsonMapper.writeValueAsBytes(permissions);
+    makeRequest(
+        adminClient,
+        HttpMethod.POST,
+        StringUtils.format(
+            "%s/druid-ext/basic-security/authorization/db/basic/roles/%s/permissions",
+            config.getCoordinatorUrl(),
+            role
+        ),
+        permissionsBytes
+    );
+  }
+
+  private StatusResponseHolder makeSQLQueryRequest(
+      HttpClient httpClient,
+      String query,
+      HttpResponseStatus expectedStatus
+  ) throws Exception
+  {
+    Map<String, Object> queryMap = ImmutableMap.of(
+        "query", query
+    );
+    return makeRequestWithExpectedStatus(
+        httpClient,
+        HttpMethod.POST,
+        config.getBrokerUrl() + "/druid/v2/sql",
+        jsonMapper.writeValueAsBytes(queryMap),
+        expectedStatus
+    );
+  }
+
+  private void verifySystemSchemaQueryBase(
+      HttpClient client,
+      String query,
+      List<Map<String, Object>> expectedResults,
+      boolean isServerQuery
+  ) throws Exception
+  {
+    StatusResponseHolder responseHolder = makeSQLQueryRequest(client, query, HttpResponseStatus.OK);
+    String content = responseHolder.getContent();
+    List<Map<String, Object>> responseMap = jsonMapper.readValue(content, SYS_SCHEMA_RESULTS_TYPE_REFERENCE);
+    if (isServerQuery) {
+      responseMap = getServersWithoutCurrentSize(responseMap);
+    }
+    Assert.assertEquals(responseMap, expectedResults);
+  }
+
+  private void verifySystemSchemaQuery(
+      HttpClient client,
+      String query,
+      List<Map<String, Object>> expectedResults
+  ) throws Exception
+  {
+    verifySystemSchemaQueryBase(client, query, expectedResults, false);
+  }
+
+  private void verifySystemSchemaServerQuery(
+      HttpClient client,
+      String query,
+      List<Map<String, Object>> expectedResults
+  ) throws Exception
+  {
+    verifySystemSchemaQueryBase(client, query, expectedResults, true);
+  }
+
+  private void verifySystemSchemaQueryFailure(
+      HttpClient client,
+      String query,
+      HttpResponseStatus expectedErrorStatus,
+      String expectedErrorMessage
+  ) throws Exception
+  {
+    StatusResponseHolder responseHolder = makeSQLQueryRequest(client, query, expectedErrorStatus);
+    Assert.assertEquals(responseHolder.getStatus(), expectedErrorStatus);
+    Assert.assertEquals(responseHolder.getContent(), expectedErrorMessage);
+  }
+
+  /**
+   * curr_size on historicals changes because cluster state is not isolated across different
+   * integration tests, zero it out for consistent test results
+   */
+  private static List<Map<String, Object>> getServersWithoutCurrentSize(List<Map<String, Object>> servers)
+  {
+    return Lists.transform(
+        servers,
+        (server) -> {
+          Map<String, Object> newServer = new HashMap<>();
+          newServer.putAll(server);
+          newServer.put("curr_size", 0);
+          return newServer;
+        }
+    );
+  }
 }
diff --git a/integration-tests/src/test/resources/results/auth_test_sys_schema_segments.json b/integration-tests/src/test/resources/results/auth_test_sys_schema_segments.json
new file mode 100644
index 0000000..f2046de
--- /dev/null
+++ b/integration-tests/src/test/resources/results/auth_test_sys_schema_segments.json
@@ -0,0 +1,18 @@
+[
+  {
+    "segment_id": "auth_test_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9",
+    "datasource": "auth_test",
+    "start": "2012-12-29T00:00:00.000Z",
+    "end": "2013-01-10T08:00:00.000Z",
+    "size": 446027801,
+    "version": "2013-01-10T08:13:47.830Z_v9",
+    "partition_num": 0,
+    "num_replicas": 1,
+    "num_rows": 4462111,
+    "is_published": 1,
+    "is_available": 1,
+    "is_realtime": 0,
+    "is_overshadowed": 0,
+    "payload": "{\"dataSegment\":{\"dataSource\":\"auth_test\",\"interval\":\"2012-12-29T00:00:00.000Z/2013-01-10T08:00:00.000Z\",\"version\":\"2013-01-10T08:13:47.830Z_v9\",\"loadSpec\":{\"load spec is pruned, because it's not needed on Brokers, but eats a lot of heap space\":\"\"},\"dimensions\":\"anonymous,area_code,city,continent_code,country_name,dma_code,geo,language,namespace,network,newpage,page,postal_code,region_lookup,robot,unpatrolled,user\",\"metrics\":\"added,count,deleted, [...]
+  }
+]
diff --git a/integration-tests/src/test/resources/results/auth_test_sys_schema_server_segments.json b/integration-tests/src/test/resources/results/auth_test_sys_schema_server_segments.json
new file mode 100644
index 0000000..f644018
--- /dev/null
+++ b/integration-tests/src/test/resources/results/auth_test_sys_schema_server_segments.json
@@ -0,0 +1,6 @@
+[
+  {
+    "server": "172.172.172.6:8283",
+    "segment_id": "auth_test_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9"
+  }
+]
\ No newline at end of file
diff --git a/integration-tests/src/test/resources/results/auth_test_sys_schema_servers.json b/integration-tests/src/test/resources/results/auth_test_sys_schema_servers.json
new file mode 100644
index 0000000..bf7c681
--- /dev/null
+++ b/integration-tests/src/test/resources/results/auth_test_sys_schema_servers.json
@@ -0,0 +1,12 @@
+[
+  {
+    "server": "172.172.172.6:8283",
+    "host": "172.172.172.6",
+    "plaintext_port": 8083,
+    "tls_port": 8283,
+    "server_type": "historical",
+    "tier": "_default_tier",
+    "curr_size": 2208932412,
+    "max_size": 5000000000
+  }
+]
diff --git a/integration-tests/src/test/resources/results/auth_test_sys_schema_tasks.json b/integration-tests/src/test/resources/results/auth_test_sys_schema_tasks.json
new file mode 100644
index 0000000..d27d766
--- /dev/null
+++ b/integration-tests/src/test/resources/results/auth_test_sys_schema_tasks.json
@@ -0,0 +1,17 @@
+[
+  {
+    "task_id": "index_auth_test_2030-04-30T01:13:31.893Z",
+    "type": null,
+    "datasource": "auth_test",
+    "created_time": "2030-04-30T01:13:31.893Z",
+    "queue_insertion_time": "1970-01-01T00:00:00.000Z",
+    "status": "SUCCESS",
+    "runner_status": "NONE",
+    "duration": 1,
+    "location": null,
+    "host": null,
+    "plaintext_port": -1,
+    "tls_port": -1,
+    "error_msg": null
+  }
+]
diff --git a/server/src/main/java/org/apache/druid/segment/realtime/firehose/EventReceiverFirehoseFactory.java b/server/src/main/java/org/apache/druid/segment/realtime/firehose/EventReceiverFirehoseFactory.java
index b8ed0ad..d8c1ef9 100644
--- a/server/src/main/java/org/apache/druid/segment/realtime/firehose/EventReceiverFirehoseFactory.java
+++ b/server/src/main/java/org/apache/druid/segment/realtime/firehose/EventReceiverFirehoseFactory.java
@@ -49,7 +49,6 @@ import org.apache.druid.server.security.AuthorizationUtils;
 import org.apache.druid.server.security.AuthorizerMapper;
 import org.apache.druid.server.security.Resource;
 import org.apache.druid.server.security.ResourceAction;
-import org.apache.druid.server.security.ResourceType;
 import org.apache.druid.utils.Runnables;
 import org.joda.time.DateTime;
 
@@ -349,7 +348,7 @@ public class EventReceiverFirehoseFactory implements FirehoseFactory<InputRowPar
       Access accessResult = AuthorizationUtils.authorizeResourceAction(
           req,
           new ResourceAction(
-              new Resource("STATE", ResourceType.STATE),
+              Resource.STATE_RESOURCE,
               Action.WRITE
           ),
           authorizerMapper
@@ -538,7 +537,7 @@ public class EventReceiverFirehoseFactory implements FirehoseFactory<InputRowPar
       Access accessResult = AuthorizationUtils.authorizeResourceAction(
           req,
           new ResourceAction(
-              new Resource("STATE", ResourceType.STATE),
+              Resource.STATE_RESOURCE,
               Action.WRITE
           ),
           authorizerMapper
diff --git a/server/src/main/java/org/apache/druid/server/http/security/StateResourceFilter.java b/server/src/main/java/org/apache/druid/server/http/security/StateResourceFilter.java
index b3231dc..275ea35 100644
--- a/server/src/main/java/org/apache/druid/server/http/security/StateResourceFilter.java
+++ b/server/src/main/java/org/apache/druid/server/http/security/StateResourceFilter.java
@@ -27,7 +27,6 @@ import org.apache.druid.server.security.AuthorizerMapper;
 import org.apache.druid.server.security.ForbiddenException;
 import org.apache.druid.server.security.Resource;
 import org.apache.druid.server.security.ResourceAction;
-import org.apache.druid.server.security.ResourceType;
 
 /**
  * Use this ResourceFilter at end points where Druid Cluster State is read or written
@@ -58,7 +57,7 @@ public class StateResourceFilter extends AbstractResourceFilter
   public ContainerRequest filter(ContainerRequest request)
   {
     final ResourceAction resourceAction = new ResourceAction(
-        new Resource("STATE", ResourceType.STATE),
+        Resource.STATE_RESOURCE,
         getAction(request)
     );
 
diff --git a/server/src/main/java/org/apache/druid/server/security/Resource.java b/server/src/main/java/org/apache/druid/server/security/Resource.java
index 02b6539..6770bda 100644
--- a/server/src/main/java/org/apache/druid/server/security/Resource.java
+++ b/server/src/main/java/org/apache/druid/server/security/Resource.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class Resource
 {
+  public static final Resource STATE_RESOURCE = new Resource("STATE", ResourceType.STATE);
+
   private final String name;
   private final ResourceType type;
 
diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/SystemSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/SystemSchema.java
index 18f4c31..e5cfa91 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/SystemSchema.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/SystemSchema.java
@@ -64,7 +64,6 @@ import org.apache.druid.server.security.AuthorizerMapper;
 import org.apache.druid.server.security.ForbiddenException;
 import org.apache.druid.server.security.Resource;
 import org.apache.druid.server.security.ResourceAction;
-import org.apache.druid.server.security.ResourceType;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
 import org.apache.druid.sql.calcite.table.RowSignature;
 import org.apache.druid.timeline.DataSegment;
@@ -93,6 +92,17 @@ public class SystemSchema extends AbstractSchema
   private static final String SERVER_SEGMENTS_TABLE = "server_segments";
   private static final String TASKS_TABLE = "tasks";
 
+  private static final Function<SegmentWithOvershadowedStatus, Iterable<ResourceAction>>
+      SEGMENT_WITH_OVERSHADOWED_STATUS_RA_GENERATOR = segment ->
+      Collections.singletonList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(
+          segment.getDataSegment().getDataSource())
+      );
+
+  private static final Function<DataSegment, Iterable<ResourceAction>> SEGMENT_RA_GENERATOR =
+      segment -> Collections.singletonList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(
+          segment.getDataSource())
+      );
+
   /**
    * Booleans constants represented as long type,
    * where 1 = true and 0 = false to make it easy to count number of segments
@@ -338,13 +348,10 @@ public class SystemSchema extends AbstractSchema
       final AuthenticationResult authenticationResult =
           (AuthenticationResult) root.get(PlannerContext.DATA_CTX_AUTHENTICATION_RESULT);
 
-      Function<SegmentWithOvershadowedStatus, Iterable<ResourceAction>> raGenerator = segment -> Collections.singletonList(
-          AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(segment.getDataSegment().getDataSource()));
-
       final Iterable<SegmentWithOvershadowedStatus> authorizedSegments = AuthorizationUtils.filterAuthorizedResources(
           authenticationResult,
           () -> it,
-          raGenerator,
+          SEGMENT_WITH_OVERSHADOWED_STATUS_RA_GENERATOR,
           authorizerMapper
       );
       return authorizedSegments.iterator();
@@ -448,14 +455,9 @@ public class SystemSchema extends AbstractSchema
       final List<ImmutableDruidServer> druidServers = serverView.getDruidServers();
       final AuthenticationResult authenticationResult =
           (AuthenticationResult) root.get(PlannerContext.DATA_CTX_AUTHENTICATION_RESULT);
-      final Access access = AuthorizationUtils.authorizeAllResourceActions(
-          authenticationResult,
-          Collections.singletonList(new ResourceAction(new Resource("STATE", ResourceType.STATE), Action.READ)),
-          authorizerMapper
-      );
-      if (!access.isAllowed()) {
-        throw new ForbiddenException("Insufficient permission to view servers :" + access);
-      }
+
+      checkStateReadAccessForServers(authenticationResult, authorizerMapper);
+
       final FluentIterable<Object[]> results = FluentIterable
           .from(druidServers)
           .transform(val -> new Object[]{
@@ -501,11 +503,23 @@ public class SystemSchema extends AbstractSchema
     @Override
     public Enumerable<Object[]> scan(DataContext root)
     {
+      final AuthenticationResult authenticationResult =
+          (AuthenticationResult) root.get(PlannerContext.DATA_CTX_AUTHENTICATION_RESULT);
+
+      checkStateReadAccessForServers(authenticationResult, authorizerMapper);
+
       final List<Object[]> rows = new ArrayList<>();
       final List<ImmutableDruidServer> druidServers = serverView.getDruidServers();
       final int serverSegmentsTableSize = SERVER_SEGMENTS_SIGNATURE.getRowOrder().size();
       for (ImmutableDruidServer druidServer : druidServers) {
-        for (DataSegment segment : druidServer.getLazyAllSegments()) {
+        final Iterable<DataSegment> authorizedServerSegments = AuthorizationUtils.filterAuthorizedResources(
+            authenticationResult,
+            druidServer.getLazyAllSegments(),
+            SEGMENT_RA_GENERATOR,
+            authorizerMapper
+        );
+
+        for (DataSegment segment : authorizedServerSegments) {
           Object[] row = new Object[serverSegmentsTableSize];
           row[0] = druidServer.getHost();
           row[1] = segment.getId();
@@ -759,4 +773,22 @@ public class SystemSchema extends AbstractSchema
 
     return object.toString();
   }
+
+  /**
+   * Checks if an authenticated user has the STATE READ permissions needed to view server information.
+   */
+  private static void checkStateReadAccessForServers(
+      AuthenticationResult authenticationResult,
+      AuthorizerMapper authorizerMapper
+  )
+  {
+    final Access stateAccess = AuthorizationUtils.authorizeAllResourceActions(
+        authenticationResult,
+        Collections.singletonList(new ResourceAction(Resource.STATE_RESOURCE, Action.READ)),
+        authorizerMapper
+    );
+    if (!stateAccess.isAllowed()) {
+      throw new ForbiddenException("Insufficient permission to view servers : " + stateAccess);
+    }
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org