From 5de26cf6d9ba94bdfb34ec9fcc76a2e7aa32db79 Mon Sep 17 00:00:00 2001 From: Clint Wylie Date: Tue, 21 Sep 2021 13:28:26 -0700 Subject: [PATCH] add optional system schema authorization (#11720) * add optional system schema authorization * remove unused * adjust docs * doc fixes, missing ldap config change for integration tests * style --- docs/configuration/index.md | 1 + docs/operations/security-user-auth.md | 3 +- .../docker/environment-configs/common | 1 + .../docker/ldap-configs/bootstrap.ldif | 18 + .../AbstractAuthConfigurationTest.java | 421 +++++++++++++++++- .../ITBasicAuthConfigurationTest.java | 315 ++----------- .../ITBasicAuthLdapConfigurationTest.java | 307 +------------ .../druid/server/security/ResourceType.java | 2 + .../sql/calcite/planner/PlannerConfig.java | 17 +- .../sql/calcite/schema/NamedSystemSchema.java | 18 +- .../sql/calcite/BaseCalciteQueryTest.java | 9 + .../DruidPlannerResourceAnalyzeTest.java | 36 ++ .../calcite/schema/NamedSystemSchemaTest.java | 26 +- .../druid/sql/calcite/util/CalciteTests.java | 4 +- website/.spelling | 1 + 15 files changed, 579 insertions(+), 600 deletions(-) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index d6801f9895d..7e170f4573a 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -1763,6 +1763,7 @@ The Druid SQL server is configured through the following properties on the Broke |`druid.sql.planner.sqlTimeZone`|Sets the default time zone for the server, which will affect how time functions and timestamp literals behave. Should be a time zone name like "America/Los_Angeles" or offset like "-08:00".|UTC| |`druid.sql.planner.metadataSegmentCacheEnable`|Whether to keep a cache of published segments in broker. If true, broker polls coordinator in background to get segments from metadata store and maintains a local cache. If false, coordinator's REST API will be invoked when broker needs published segments info.|false| |`druid.sql.planner.metadataSegmentPollPeriod`|How often to poll coordinator for published segments list if `druid.sql.planner.metadataSegmentCacheEnable` is set to true. Poll period is in milliseconds. |60000| +|`druid.sql.planner.authorizeSystemTablesDirectly`|If true, Druid authorizes queries against any of the system schema tables (`sys` in SQL) as `SYSTEM_TABLE` resources which require `READ` access, in addition to permissions based content filtering.|false| > Previous versions of Druid had properties named `druid.sql.planner.maxQueryCount` and `druid.sql.planner.maxSemiJoinRowsInMemory`. > These properties are no longer available. Since Druid 0.18.0, you can use `druid.server.http.maxSubqueryRows` to control the maximum diff --git a/docs/operations/security-user-auth.md b/docs/operations/security-user-auth.md index d128bb78305..54f4317a708 100644 --- a/docs/operations/security-user-auth.md +++ b/docs/operations/security-user-auth.md @@ -29,11 +29,12 @@ This document describes the Druid security model that extensions use to enable u At the center of the Druid user authentication and authorization model are _resources_ and _actions_. A resource is something that authenticated users are trying to access or modify. An action is something that users are trying to do. -There are three resource types: +Druid uses the following resource types: * DATASOURCE – Each Druid table (i.e., `tables` in the `druid` schema in SQL) is a resource. * CONFIG – Configuration resources exposed by the cluster components. * STATE – Cluster-wide state resources. +* SYSTEM_TABLE – if `druid.sql.planner.authorizeSystemTablesDirectly` is enabled, then Druid authorizes system tables, the `sys` schema in SQL, using this resource type. For specific resources associated with the types, see the endpoint list below and corresponding descriptions in [API Reference](./api-reference.md). diff --git a/integration-tests/docker/environment-configs/common b/integration-tests/docker/environment-configs/common index 2ded3965bb5..49f83d415c0 100644 --- a/integration-tests/docker/environment-configs/common +++ b/integration-tests/docker/environment-configs/common @@ -72,6 +72,7 @@ druid_coordinator_kill_supervisor_on=true druid_coordinator_kill_supervisor_period=PT10S druid_coordinator_kill_supervisor_durationToRetain=PT0M druid_coordinator_period_metadataStoreManagementPeriod=PT10S +druid_sql_planner_authorizeSystemTablesDirectly=true # Testing the legacy config from https://github.com/apache/druid/pull/10267 # Can remove this when the flag is no longer needed diff --git a/integration-tests/docker/ldap-configs/bootstrap.ldif b/integration-tests/docker/ldap-configs/bootstrap.ldif index 05591c0df51..9614a782e01 100644 --- a/integration-tests/docker/ldap-configs/bootstrap.ldif +++ b/integration-tests/docker/ldap-configs/bootstrap.ldif @@ -136,3 +136,21 @@ homeDirectory: /home/druid uidNumber: 7 gidNumber: 7 userPassword: helloworld + +dn: uid=datasourceAndSysUser,ou=Users,dc=example,dc=org +uid: datasourceAndSysUser +cn: datasourceAndSysUser +sn: datasourceAndSysUser +objectClass: top +objectClass: posixAccount +objectClass: inetOrgPerson +homeDirectory: /home/datasourceAndSysUser +uidNumber: 8 +gidNumber: 8 +userPassword: helloworld + +dn: cn=datasourceWithSysGroup,ou=Groups,dc=example,dc=org +objectClass: groupOfUniqueNames +cn: datasourceWithSysGroup +description: datasourceWithSysGroup users +uniqueMember: uid=datasourceAndSysUser,ou=Users,dc=example,dc=org diff --git a/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java b/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java index ddbb2ef8444..1e0d0b89bce 100644 --- a/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java @@ -21,6 +21,7 @@ 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; @@ -34,6 +35,10 @@ import org.apache.druid.java.util.http.client.CredentialedHttpClient; import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.java.util.http.client.response.StatusResponseHolder; +import org.apache.druid.server.security.Action; +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.avatica.DruidAvaticaJsonHandler; import org.apache.druid.testing.IntegrationTestingConfig; import org.apache.druid.testing.clients.CoordinatorResourceTestClient; @@ -43,6 +48,7 @@ import org.apache.druid.tests.indexer.AbstractIndexerTest; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.testng.Assert; +import org.testng.annotations.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -50,19 +56,17 @@ import java.sql.Connection; 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; public abstract class AbstractAuthConfigurationTest { private static final Logger LOG = new Logger(AbstractAuthConfigurationTest.class); - - static final TypeReference>> SYS_SCHEMA_RESULTS_TYPE_REFERENCE = - new TypeReference>>() - { - }; + private static final String INVALID_NAME = "invalid%2Fname"; static final String SYSTEM_SCHEMA_SEGMENTS_RESULTS_RESOURCE = "/results/auth_test_sys_schema_segments.json"; @@ -85,7 +89,84 @@ public abstract class AbstractAuthConfigurationTest static final String SYS_SCHEMA_TASKS_QUERY = "SELECT * FROM sys.tasks WHERE datasource IN ('auth_test')"; - private static final String INVALID_NAME = "invalid%2Fname"; + static final TypeReference>> SYS_SCHEMA_RESULTS_TYPE_REFERENCE = + new TypeReference>>() + { + }; + + /** + * create a ResourceAction set of permissions that can only read a 'auth_test' datasource, for Authorizer + * implementations which use ResourceAction pattern matching + */ + static final List DATASOURCE_ONLY_PERMISSIONS = Collections.singletonList( + new ResourceAction( + new Resource("auth_test", ResourceType.DATASOURCE), + Action.READ + ) + ); + + /** + * create a ResourceAction set of permissions that can only read 'auth_test' + partial SYSTEM_TABLE, for Authorizer + * implementations which use ResourceAction pattern matching + */ + static final List DATASOURCE_SYS_PERMISSIONS = ImmutableList.of( + new ResourceAction( + new Resource("auth_test", ResourceType.DATASOURCE), + Action.READ + ), + new ResourceAction( + new Resource("segments", ResourceType.SYSTEM_TABLE), + Action.READ + ), + // test missing state permission but having servers permission + new ResourceAction( + new Resource("servers", ResourceType.SYSTEM_TABLE), + Action.READ + ), + // test missing state permission but having server_segments permission + new ResourceAction( + new Resource("server_segments", ResourceType.SYSTEM_TABLE), + Action.READ + ), + new ResourceAction( + new Resource("tasks", ResourceType.SYSTEM_TABLE), + Action.READ + ) + ); + + /** + * create a ResourceAction set of permissions that can only read 'auth_test' + STATE + SYSTEM_TABLE read access, for + * Authorizer implementations which use ResourceAction pattern matching + */ + static final List DATASOURCE_SYS_STATE_PERMISSIONS = ImmutableList.of( + new ResourceAction( + new Resource("auth_test", ResourceType.DATASOURCE), + Action.READ + ), + new ResourceAction( + new Resource(".*", ResourceType.SYSTEM_TABLE), + Action.READ + ), + new ResourceAction( + new Resource(".*", ResourceType.STATE), + Action.READ + ) + ); + + /** + * create a ResourceAction set of permissions with only STATE and SYSTEM_TABLE read access, for Authorizer + * implementations which use ResourceAction pattern matching + */ + static final List STATE_ONLY_PERMISSIONS = ImmutableList.of( + new ResourceAction( + new Resource(".*", ResourceType.STATE), + Action.READ + ), + new ResourceAction( + new Resource(".*", ResourceType.SYSTEM_TABLE), + Action.READ + ) + ); List> adminSegments; List> adminTasks; @@ -107,11 +188,323 @@ public abstract class AbstractAuthConfigurationTest HttpClient adminClient; HttpClient datasourceOnlyUserClient; + HttpClient datasourceAndSysUserClient; HttpClient datasourceWithStateUserClient; HttpClient stateOnlyUserClient; HttpClient internalSystemClient; + abstract void setupDatasourceOnlyUser() throws Exception; + abstract void setupDatasourceAndSysTableUser() throws Exception; + abstract void setupDatasourceAndSysAndStateUser() throws Exception; + abstract void setupSysTableAndStateOnlyUser() throws Exception; + + abstract void setupTestSpecificHttpClients() throws Exception; + + abstract String getAuthenticatorName(); + + abstract String getAuthorizerName(); + + abstract String getExpectedAvaticaAuthError(); + + @Test + public void test_systemSchemaAccess_admin() throws Exception + { + // check that admin access works on all nodes + checkNodeAccess(adminClient); + + // as admin + LOG.info("Checking sys.segments query as admin..."); + verifySystemSchemaQuery( + adminClient, + SYS_SCHEMA_SEGMENTS_QUERY, + adminSegments + ); + + LOG.info("Checking sys.servers query as admin..."); + verifySystemSchemaServerQuery( + adminClient, + SYS_SCHEMA_SERVERS_QUERY, + getServersWithoutCurrentSize(adminServers) + ); + + LOG.info("Checking sys.server_segments query as admin..."); + verifySystemSchemaQuery( + adminClient, + SYS_SCHEMA_SERVER_SEGMENTS_QUERY, + adminServerSegments + ); + + LOG.info("Checking sys.tasks query as admin..."); + verifySystemSchemaQuery( + adminClient, + SYS_SCHEMA_TASKS_QUERY, + adminTasks + ); + } + + @Test + public void test_systemSchemaAccess_datasourceOnlyUser() throws Exception + { + // check that we can access a datasource-permission restricted resource on the broker + HttpUtil.makeRequest( + datasourceOnlyUserClient, + HttpMethod.GET, + config.getBrokerUrl() + "/druid/v2/datasources/auth_test", + null + ); + + // as user that can only read auth_test + LOG.info("Checking sys.segments query as datasourceOnlyUser..."); + verifySystemSchemaQueryFailure( + datasourceOnlyUserClient, + SYS_SCHEMA_SEGMENTS_QUERY, + HttpResponseStatus.FORBIDDEN, + "{\"Access-Check-Result\":\"Allowed:false, Message:\"}" + ); + + LOG.info("Checking sys.servers query as datasourceOnlyUser..."); + verifySystemSchemaQueryFailure( + datasourceOnlyUserClient, + SYS_SCHEMA_SERVERS_QUERY, + HttpResponseStatus.FORBIDDEN, + "{\"Access-Check-Result\":\"Allowed:false, Message:\"}" + ); + + LOG.info("Checking sys.server_segments query as datasourceOnlyUser..."); + verifySystemSchemaQueryFailure( + datasourceOnlyUserClient, + SYS_SCHEMA_SERVER_SEGMENTS_QUERY, + HttpResponseStatus.FORBIDDEN, + "{\"Access-Check-Result\":\"Allowed:false, Message:\"}" + ); + + LOG.info("Checking sys.tasks query as datasourceOnlyUser..."); + verifySystemSchemaQueryFailure( + datasourceOnlyUserClient, + SYS_SCHEMA_TASKS_QUERY, + HttpResponseStatus.FORBIDDEN, + "{\"Access-Check-Result\":\"Allowed:false, Message:\"}" + ); + } + + @Test + public void test_systemSchemaAccess_datasourceAndSysUser() throws Exception + { + // check that we can access a datasource-permission restricted resource on the broker + HttpUtil.makeRequest( + datasourceAndSysUserClient, + HttpMethod.GET, + config.getBrokerUrl() + "/druid/v2/datasources/auth_test", + null + ); + + // as user that can only read auth_test + LOG.info("Checking sys.segments query as datasourceAndSysUser..."); + verifySystemSchemaQuery( + datasourceAndSysUserClient, + SYS_SCHEMA_SEGMENTS_QUERY, + adminSegments.stream() + .filter((segmentEntry) -> "auth_test".equals(segmentEntry.get("datasource"))) + .collect(Collectors.toList()) + ); + + LOG.info("Checking sys.servers query as datasourceAndSysUser..."); + verifySystemSchemaQueryFailure( + datasourceAndSysUserClient, + 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 datasourceAndSysUser..."); + verifySystemSchemaQueryFailure( + datasourceAndSysUserClient, + 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 datasourceAndSysUser..."); + verifySystemSchemaQuery( + datasourceAndSysUserClient, + SYS_SCHEMA_TASKS_QUERY, + adminTasks.stream() + .filter((taskEntry) -> "auth_test".equals(taskEntry.get("datasource"))) + .collect(Collectors.toList()) + ); + } + + @Test + public void test_systemSchemaAccess_datasourceAndSysWithStateUser() throws Exception + { + // check that we can access a state-permission restricted resource on the broker + HttpUtil.makeRequest( + datasourceWithStateUserClient, + HttpMethod.GET, + config.getBrokerUrl() + "/status", + null + ); + + // 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) -> "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) -> ((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) -> "auth_test".equals(taskEntry.get("datasource"))) + .collect(Collectors.toList()) + ); + } + + @Test + public void test_systemSchemaAccess_stateOnlyUser() throws Exception + { + HttpUtil.makeRequest(stateOnlyUserClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null); + + // 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 test_unsecuredPathWithoutCredentials_allowed() + { + // check that we are allowed to access unsecured path without credentials. + checkUnsecuredCoordinatorLoadQueuePath(httpClient); + } + + @Test + public void test_admin_loadStatus() throws Exception + { + checkLoadStatus(adminClient); + } + + @Test + public void test_admin_hasNodeAccess() + { + checkNodeAccess(adminClient); + } + + @Test + public void test_internalSystemUser_hasNodeAccess() + { + checkNodeAccess(internalSystemClient); + } + + @Test + public void test_avaticaQuery_broker() + { + testAvaticaQuery(getBrokerAvacticaUrl()); + } + + @Test + public void test_avaticaQuery_router() + { + testAvaticaQuery(getRouterAvacticaUrl()); + } + + @Test + public void test_avaticaQueryAuthFailure_broker() throws Exception + { + testAvaticaAuthFailure(getBrokerAvacticaUrl()); + } + + @Test + public void test_avaticaQueryAuthFailure_router() throws Exception + { + testAvaticaAuthFailure(getRouterAvacticaUrl()); + } + + @Test + public void test_admin_optionsRequest() + { + verifyAdminOptionsRequest(); + } + + @Test + public void test_authentication_invalidAuthName_fails() + { + verifyAuthenticationInvalidAuthNameFails(); + } + + @Test + public void test_authorization_invalidAuthName_fails() + { + verifyAuthorizationInvalidAuthNameFails(); + } + + @Test + public void test_groupMappings_invalidAuthName_fails() + { + verifyGroupMappingsInvalidAuthNameFails(); + } + + @Test + public void testMaliciousUser() + { + verifyMaliciousUser(); + } + + void setupHttpClientsAndUsers() throws Exception + { + setupHttpClients(); + setupDatasourceOnlyUser(); + setupDatasourceAndSysTableUser(); + setupDatasourceAndSysAndStateUser(); + setupSysTableAndStateOnlyUser(); + } + void checkNodeAccess(HttpClient httpClient) { HttpUtil.makeRequest(httpClient, HttpMethod.GET, config.getCoordinatorUrl() + "/status", null); @@ -302,7 +695,7 @@ public abstract class AbstractAuthConfigurationTest testOptionsRequests(adminClient); } - void verifyAuthenticatioInvalidAuthNameFails() + void verifyAuthenticationInvalidAuthNameFails() { verifyInvalidAuthNameFails(StringUtils.format( "%s/druid-ext/basic-security/authentication/listen/%s", @@ -370,7 +763,6 @@ public abstract class AbstractAuthConfigurationTest setupTestSpecificHttpClients(); } - abstract void setupUsers() throws Exception; void setupCommonHttpClients() { @@ -384,6 +776,11 @@ public abstract class AbstractAuthConfigurationTest httpClient ); + datasourceAndSysUserClient = new CredentialedHttpClient( + new BasicCredentials("datasourceAndSysUser", "helloworld"), + httpClient + ); + datasourceWithStateUserClient = new CredentialedHttpClient( new BasicCredentials("datasourceWithStateUser", "helloworld"), httpClient @@ -400,8 +797,6 @@ public abstract class AbstractAuthConfigurationTest ); } - abstract void setupTestSpecificHttpClients() throws Exception; - void setExpectedSystemSchemaObjects() throws IOException { // initial setup is done now, run the system schema response content tests @@ -462,10 +857,4 @@ public abstract class AbstractAuthConfigurationTest json = StringUtils.replace(json, "%%NON_LEADER%%", String.valueOf(NullHandling.defaultLongValue())); return json; } - - abstract String getAuthenticatorName(); - - abstract String getAuthorizerName(); - - abstract String getExpectedAvaticaAuthError(); } 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 37cf34992da..8b82d3b323f 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 @@ -19,30 +19,23 @@ package org.apache.druid.tests.security; -import com.google.common.collect.ImmutableList; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.java.util.http.client.CredentialedHttpClient; import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate; -import org.apache.druid.server.security.Action; -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.testing.guice.DruidTestModuleFactory; import org.apache.druid.testing.utils.HttpUtil; import org.apache.druid.testing.utils.ITRetryUtil; import org.apache.druid.tests.TestNGGroup; import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.testng.annotations.BeforeClass; import org.testng.annotations.Guice; import org.testng.annotations.Test; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; @Test(groups = TestNGGroup.SECURITY) @Guice(moduleFactory = DruidTestModuleFactory.class) @@ -65,313 +58,61 @@ public class ITBasicAuthConfigurationTest extends AbstractAuthConfigurationTest () -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load" ); - setupHttpClients(); - setupUsers(); + setupHttpClientsAndUsers(); setExpectedSystemSchemaObjects(); } - @Test - public void test_systemSchemaAccess_admin() throws Exception - { - // check that admin access works on all nodes - checkNodeAccess(adminClient); - - // as admin - LOG.info("Checking sys.segments query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_SEGMENTS_QUERY, - adminSegments - ); - - LOG.info("Checking sys.servers query as admin..."); - verifySystemSchemaServerQuery( - adminClient, - SYS_SCHEMA_SERVERS_QUERY, - getServersWithoutCurrentSize(adminServers) - ); - - LOG.info("Checking sys.server_segments query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_SERVER_SEGMENTS_QUERY, - adminServerSegments - ); - - LOG.info("Checking sys.tasks query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_TASKS_QUERY, - adminTasks - ); - } - - @Test - public void test_systemSchemaAccess_datasourceOnlyUser() throws Exception - { - // check that we can access a datasource-permission restricted resource on the broker - HttpUtil.makeRequest( - datasourceOnlyUserClient, - HttpMethod.GET, - config.getBrokerUrl() + "/druid/v2/datasources/auth_test", - null - ); - - // 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) -> "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) -> "auth_test".equals(taskEntry.get("datasource"))) - .collect(Collectors.toList()) - ); - } - - @Test - public void test_systemSchemaAccess_datasourceWithStateUser() throws Exception - { - // check that we can access a state-permission restricted resource on the broker - HttpUtil.makeRequest( - datasourceWithStateUserClient, - HttpMethod.GET, - config.getBrokerUrl() + "/status", - null - ); - - // 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) -> "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) -> ((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) -> "auth_test".equals(taskEntry.get("datasource"))) - .collect(Collectors.toList()) - ); - } - - @Test - public void test_systemSchemaAccess_stateOnlyUser() throws Exception - { - HttpUtil.makeRequest(stateOnlyUserClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null); - - // 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 test_unsecuredPathWithoutCredentials_allowed() - { - // check that we are allowed to access unsecured path without credentials. - checkUnsecuredCoordinatorLoadQueuePath(httpClient); - } - - @Test - public void test_admin_hasNodeAccess() - { - checkNodeAccess(adminClient); - } - - @Test - public void test_admin_loadStatus() throws Exception - { - checkLoadStatus(adminClient); - } - - @Test - public void test_internalSystemUser_hasNodeAccess() - { - checkNodeAccess(internalSystemClient); - } - - @Test public void test_druid99User_hasNodeAccess() { checkNodeAccess(druid99); } - @Test - public void test_avaticaQuery_broker() - { - testAvaticaQuery(getBrokerAvacticaUrl()); - } - - @Test - public void test_avaticaQuery_router() - { - testAvaticaQuery(getRouterAvacticaUrl()); - } - - @Test - public void test_avaticaQueryAuthFailure_broker() throws Exception - { - testAvaticaAuthFailure(getBrokerAvacticaUrl()); - } - - @Test - public void test_avaticaQueryAuthFailure_router() throws Exception - { - testAvaticaAuthFailure(getRouterAvacticaUrl()); - } - - @Test - public void test_admin_optionsRequest() - { - verifyAdminOptionsRequest(); - } - - @Test - public void test_authentication_invalidAuthName_fails() - { - verifyAuthenticatioInvalidAuthNameFails(); - } - - @Test - public void test_authorization_invalidAuthName_fails() - { - verifyAuthorizationInvalidAuthNameFails(); - } - - @Test - public void test_groupMappings_invalidAuthName_fails() - { - verifyGroupMappingsInvalidAuthNameFails(); - } - - @Test - public void testMaliciousUser() - { - verifyMaliciousUser(); - } - @Override - void setupUsers() throws Exception + void setupDatasourceOnlyUser() throws Exception { - // create a new user+role that can only read 'auth_test' - List readDatasourceOnlyPermissions = Collections.singletonList( - new ResourceAction( - new Resource("auth_test", ResourceType.DATASOURCE), - Action.READ - ) - ); createUserAndRoleWithPermissions( adminClient, "datasourceOnlyUser", "helloworld", "datasourceOnlyRole", - readDatasourceOnlyPermissions + DATASOURCE_ONLY_PERMISSIONS ); + } - // create a new user+role that can only read 'auth_test' + STATE read access - List readDatasourceWithStatePermissions = ImmutableList.of( - new ResourceAction( - new Resource("auth_test", ResourceType.DATASOURCE), - Action.READ - ), - new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ) + @Override + void setupDatasourceAndSysTableUser() throws Exception + { + createUserAndRoleWithPermissions( + adminClient, + "datasourceAndSysUser", + "helloworld", + "datasourceAndSysRole", + DATASOURCE_SYS_PERMISSIONS ); + } + + @Override + void setupDatasourceAndSysAndStateUser() throws Exception + { createUserAndRoleWithPermissions( adminClient, "datasourceWithStateUser", "helloworld", "datasourceWithStateRole", - readDatasourceWithStatePermissions + DATASOURCE_SYS_STATE_PERMISSIONS ); + } - // create a new user+role with only STATE read access - List stateOnlyPermissions = ImmutableList.of( - new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ) - ); + @Override + void setupSysTableAndStateOnlyUser() throws Exception + { createUserAndRoleWithPermissions( adminClient, "stateOnlyUser", "helloworld", "stateOnlyRole", - stateOnlyPermissions + STATE_ONLY_PERMISSIONS ); } @@ -379,18 +120,12 @@ public class ITBasicAuthConfigurationTest extends AbstractAuthConfigurationTest void setupTestSpecificHttpClients() throws Exception { // create a new user+role that can read /status - List permissions = Collections.singletonList( - new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ) - ); createUserAndRoleWithPermissions( adminClient, "druid", "helloworld", "druidrole", - permissions + STATE_ONLY_PERMISSIONS ); // create 100 users diff --git a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthLdapConfigurationTest.java b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthLdapConfigurationTest.java index 219e6647d78..edfec4e3866 100644 --- a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthLdapConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthLdapConfigurationTest.java @@ -20,7 +20,6 @@ package org.apache.druid.tests.security; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import org.apache.druid.java.util.common.StringUtils; @@ -29,18 +28,13 @@ import org.apache.druid.java.util.http.client.CredentialedHttpClient; import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping; -import org.apache.druid.server.security.Action; -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.testing.IntegrationTestingConfig; -import org.apache.druid.testing.clients.CoordinatorResourceTestClient; import org.apache.druid.testing.guice.DruidTestModuleFactory; import org.apache.druid.testing.utils.HttpUtil; import org.apache.druid.testing.utils.ITRetryUtil; import org.apache.druid.tests.TestNGGroup; import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.testng.annotations.BeforeClass; import org.testng.annotations.Guice; import org.testng.annotations.Test; @@ -48,7 +42,6 @@ import org.testng.annotations.Test; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @Test(groups = TestNGGroup.LDAP_SECURITY) @Guice(moduleFactory = DruidTestModuleFactory.class) @@ -67,8 +60,6 @@ public class ITBasicAuthLdapConfigurationTest extends AbstractAuthConfigurationT @Inject ObjectMapper jsonMapper; - @Inject - private CoordinatorResourceTestClient coordinatorClient; private HttpClient druidUserClient; private HttpClient stateOnlyNoLdapGroupUserClient; @@ -81,177 +72,10 @@ public class ITBasicAuthLdapConfigurationTest extends AbstractAuthConfigurationT () -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load" ); - setupHttpClients(); - setupUsers(); + setupHttpClientsAndUsers(); setExpectedSystemSchemaObjects(); } - @Test - public void test_systemSchemaAccess_admin() throws Exception - { - // check that admin access works on all nodes - checkNodeAccess(adminClient); - - // as admin - LOG.info("Checking sys.segments query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_SEGMENTS_QUERY, - adminSegments - ); - - LOG.info("Checking sys.servers query as admin..."); - verifySystemSchemaServerQuery( - adminClient, - SYS_SCHEMA_SERVERS_QUERY, - getServersWithoutCurrentSize(adminServers) - ); - - LOG.info("Checking sys.server_segments query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_SERVER_SEGMENTS_QUERY, - adminServerSegments - ); - - LOG.info("Checking sys.tasks query as admin..."); - verifySystemSchemaQuery( - adminClient, - SYS_SCHEMA_TASKS_QUERY, - adminTasks - ); - } - - @Test - public void test_systemSchemaAccess_datasourceOnlyUser() throws Exception - { - // check that we can access a datasource-permission restricted resource on the broker - HttpUtil.makeRequest( - datasourceOnlyUserClient, - HttpMethod.GET, - config.getBrokerUrl() + "/druid/v2/datasources/auth_test", - null - ); - - // 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) -> "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) -> "auth_test".equals(taskEntry.get("datasource"))) - .collect(Collectors.toList()) - ); - } - - @Test - public void test_systemSchemaAccess_datasourceWithStateUser() throws Exception - { - // check that we can access a state-permission restricted resource on the broker - HttpUtil.makeRequest( - datasourceWithStateUserClient, - HttpMethod.GET, - config.getBrokerUrl() + "/status", - null - ); - - // 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) -> "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) -> ((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) -> "auth_test".equals(taskEntry.get("datasource"))) - .collect(Collectors.toList()) - ); - } - - @Test - public void test_systemSchemaAccess_stateOnlyUser() throws Exception - { - HttpUtil.makeRequest(stateOnlyUserClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null); - - // 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 test_systemSchemaAccess_stateOnlyNoLdapGroupUser() throws Exception { @@ -287,141 +111,52 @@ public class ITBasicAuthLdapConfigurationTest extends AbstractAuthConfigurationT ); } - @Test - public void test_unsecuredPathWithoutCredentials_allowed() - { - // check that we are allowed to access unsecured path without credentials. - checkUnsecuredCoordinatorLoadQueuePath(httpClient); - } - - @Test - public void test_admin_loadStatus() throws Exception - { - checkLoadStatus(adminClient); - } - - @Test - public void test_admin_hasNodeAccess() - { - checkNodeAccess(adminClient); - } - - @Test - public void test_internalSystemUser_hasNodeAccess() - { - checkNodeAccess(internalSystemClient); - } - @Test public void test_druidUser_hasNodeAccess() { checkNodeAccess(druidUserClient); } - @Test - public void test_avaticaQuery_broker() - { - testAvaticaQuery(getBrokerAvacticaUrl()); - } - @Test - public void test_avaticaQuery_router() + @Override + void setupDatasourceOnlyUser() throws Exception { - testAvaticaQuery(getRouterAvacticaUrl()); - } - - @Test - public void test_avaticaQueryAuthFailure_broker() throws Exception - { - testAvaticaAuthFailure(getBrokerAvacticaUrl()); - } - - @Test - public void test_avaticaQueryAuthFailure_router() throws Exception - { - testAvaticaAuthFailure(getRouterAvacticaUrl()); - } - - @Test - public void test_admin_optionsRequest() - { - verifyAdminOptionsRequest(); - } - - @Test - public void test_authentication_invalidAuthName_fails() - { - verifyAuthenticatioInvalidAuthNameFails(); - } - - @Test - public void test_authorization_invalidAuthName_fails() - { - verifyAuthorizationInvalidAuthNameFails(); - } - - @Test - public void test_groupMappings_invalidAuthName_fails() - { - verifyGroupMappingsInvalidAuthNameFails(); - } - - @Test - public void testMaliciousUser() - { - verifyMaliciousUser(); + createRoleWithPermissionsAndGroupMapping( + "datasourceOnlyGroup", + ImmutableMap.of("datasourceOnlyRole", DATASOURCE_ONLY_PERMISSIONS) + ); } @Override - void setupUsers() throws Exception + void setupDatasourceAndSysTableUser() throws Exception { - // create a role that can only read 'auth_test' - List readDatasourceOnlyPermissions = Collections.singletonList( - new ResourceAction( - new Resource("auth_test", ResourceType.DATASOURCE), - Action.READ - ) - ); - createRoleWithPermissionsAndGroupMapping( - "datasourceOnlyGroup", - ImmutableMap.of("datasourceOnlyRole", readDatasourceOnlyPermissions) - ); - - // create a new role that can only read 'auth_test' + STATE read access - List readDatasourceWithStatePermissions = ImmutableList.of( - new ResourceAction( - new Resource("auth_test", ResourceType.DATASOURCE), - Action.READ - ), - new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ) + "datasourceWithSysGroup", + ImmutableMap.of("datasourceWithSysRole", DATASOURCE_SYS_PERMISSIONS) ); + } + @Override + void setupDatasourceAndSysAndStateUser() throws Exception + { createRoleWithPermissionsAndGroupMapping( "datasourceWithStateGroup", - ImmutableMap.of("datasourceWithStateRole", readDatasourceWithStatePermissions) - ); - - // create a new role with only STATE read access - List stateOnlyPermissions = ImmutableList.of( - new ResourceAction( - new Resource(".*", ResourceType.STATE), - Action.READ - ) + ImmutableMap.of("datasourceWithStateRole", DATASOURCE_SYS_STATE_PERMISSIONS) ); + } + @Override + void setupSysTableAndStateOnlyUser() throws Exception + { createRoleWithPermissionsAndGroupMapping( "stateOnlyGroup", - ImmutableMap.of("stateOnlyRole", stateOnlyPermissions) + ImmutableMap.of("stateOnlyRole", STATE_ONLY_PERMISSIONS) ); // create a role that can read /status createRoleWithPermissionsAndGroupMapping( "druidGroup", - ImmutableMap.of("druidrole", stateOnlyPermissions) + ImmutableMap.of("druidrole", STATE_ONLY_PERMISSIONS) ); assignUserToRole("stateOnlyNoLdapGroup", "stateOnlyRole"); diff --git a/server/src/main/java/org/apache/druid/server/security/ResourceType.java b/server/src/main/java/org/apache/druid/server/security/ResourceType.java index 0e02572b615..1b02f1263b2 100644 --- a/server/src/main/java/org/apache/druid/server/security/ResourceType.java +++ b/server/src/main/java/org/apache/druid/server/security/ResourceType.java @@ -32,6 +32,7 @@ public class ResourceType public static final String VIEW = "VIEW"; public static final String CONFIG = "CONFIG"; public static final String STATE = "STATE"; + public static final String SYSTEM_TABLE = "SYSTEM_TABLE"; private static final Set KNOWN_TYPES = Sets.newConcurrentHashSet(); @@ -41,6 +42,7 @@ public class ResourceType registerResourceType(VIEW); registerResourceType(CONFIG); registerResourceType(STATE); + registerResourceType(SYSTEM_TABLE); } /** diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java index ee7dee103a0..43dfa5a36c2 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java @@ -67,6 +67,9 @@ public class PlannerConfig @JsonProperty private boolean computeInnerJoinCostAsFilter = true; + @JsonProperty + private boolean authorizeSystemTablesDirectly = false; + public long getMetadataSegmentPollPeriod() { return metadataSegmentPollPeriod; @@ -129,6 +132,11 @@ public class PlannerConfig return computeInnerJoinCostAsFilter; } + public boolean isAuthorizeSystemTablesDirectly() + { + return authorizeSystemTablesDirectly; + } + public PlannerConfig withOverrides(final Map context) { if (context == null) { @@ -153,15 +161,18 @@ public class PlannerConfig CTX_KEY_USE_APPROXIMATE_TOPN, isUseApproximateTopN() ); + newConfig.computeInnerJoinCostAsFilter = getContextBoolean( + context, + CTX_COMPUTE_INNER_JOIN_COST_AS_FILTER, + computeInnerJoinCostAsFilter + ); newConfig.requireTimeCondition = isRequireTimeCondition(); newConfig.sqlTimeZone = getSqlTimeZone(); newConfig.awaitInitializationOnStart = isAwaitInitializationOnStart(); newConfig.metadataSegmentCacheEnable = isMetadataSegmentCacheEnable(); newConfig.metadataSegmentPollPeriod = getMetadataSegmentPollPeriod(); newConfig.serializeComplexValues = shouldSerializeComplexValues(); - newConfig.computeInnerJoinCostAsFilter = getContextBoolean(context, - CTX_COMPUTE_INNER_JOIN_COST_AS_FILTER, - computeInnerJoinCostAsFilter); + newConfig.authorizeSystemTablesDirectly = isAuthorizeSystemTablesDirectly(); return newConfig; } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java index 3d42f47e067..65bd3adc504 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/schema/NamedSystemSchema.java @@ -21,6 +21,10 @@ package org.apache.druid.sql.calcite.schema; import com.google.inject.Inject; import org.apache.calcite.schema.Schema; +import org.apache.druid.server.security.ResourceType; +import org.apache.druid.sql.calcite.planner.PlannerConfig; + +import javax.annotation.Nullable; /** * The schema for Druid system tables to be accessible via SQL. @@ -30,10 +34,12 @@ public class NamedSystemSchema implements NamedSchema public static final String NAME = "sys"; private final SystemSchema systemSchema; + private final PlannerConfig plannerConfig; @Inject - public NamedSystemSchema(SystemSchema systemSchema) + public NamedSystemSchema(PlannerConfig plannerConfig, SystemSchema systemSchema) { + this.plannerConfig = plannerConfig; this.systemSchema = systemSchema; } @@ -48,4 +54,14 @@ public class NamedSystemSchema implements NamedSchema { return systemSchema; } + + @Nullable + @Override + public String getSchemaResourceType(String resourceName) + { + if (plannerConfig.isAuthorizeSystemTablesDirectly()) { + return ResourceType.SYSTEM_TABLE; + } + return null; + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java index 45f0eb1fb16..954c9fee828 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java @@ -175,6 +175,15 @@ public class BaseCalciteQueryTest extends CalciteTestBase } }; + public static final PlannerConfig PLANNER_CONFIG_AUTHORIZE_SYS_TABLES = new PlannerConfig() + { + @Override + public boolean isAuthorizeSystemTablesDirectly() + { + return true; + } + }; + public static final String DUMMY_SQL_ID = "dummy"; public static final String LOS_ANGELES = "America/Los_Angeles"; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java index 96a540f2001..402fffc4faf 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java @@ -22,6 +22,7 @@ package org.apache.druid.sql.calcite; import com.google.common.collect.ImmutableSet; import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.ResourceType; +import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.CalciteTests; import org.junit.Assert; import org.junit.Test; @@ -243,4 +244,39 @@ public class DruidPlannerResourceAnalyzeTest extends BaseCalciteQueryTest requiredResources ); } + + @Test + public void testSysTables() + { + testSysTable("SELECT * FROM sys.segments", null, PLANNER_CONFIG_DEFAULT); + testSysTable("SELECT * FROM sys.servers", null, PLANNER_CONFIG_DEFAULT); + testSysTable("SELECT * FROM sys.server_segments", null, PLANNER_CONFIG_DEFAULT); + testSysTable("SELECT * FROM sys.tasks", null, PLANNER_CONFIG_DEFAULT); + testSysTable("SELECT * FROM sys.supervisors", null, PLANNER_CONFIG_DEFAULT); + + testSysTable("SELECT * FROM sys.segments", "segments", PLANNER_CONFIG_AUTHORIZE_SYS_TABLES); + testSysTable("SELECT * FROM sys.servers", "servers", PLANNER_CONFIG_AUTHORIZE_SYS_TABLES); + testSysTable("SELECT * FROM sys.server_segments", "server_segments", PLANNER_CONFIG_AUTHORIZE_SYS_TABLES); + testSysTable("SELECT * FROM sys.tasks", "tasks", PLANNER_CONFIG_AUTHORIZE_SYS_TABLES); + testSysTable("SELECT * FROM sys.supervisors", "supervisors", PLANNER_CONFIG_AUTHORIZE_SYS_TABLES); + } + + private void testSysTable(String sql, String name, PlannerConfig plannerConfig) + { + Set requiredResources = analyzeResources( + plannerConfig, + sql, + CalciteTests.REGULAR_USER_AUTH_RESULT + ); + if (name == null) { + Assert.assertEquals(0, requiredResources.size()); + } else { + Assert.assertEquals( + ImmutableSet.of( + new Resource(name, ResourceType.SYSTEM_TABLE) + ), + requiredResources + ); + } + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java index b4fa99f6f47..17e83a78994 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java @@ -19,7 +19,10 @@ package org.apache.druid.sql.calcite.schema; +import org.apache.druid.server.security.ResourceType; +import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.CalciteTestBase; +import org.easymock.EasyMock; import org.easymock.Mock; import org.junit.Assert; import org.junit.Before; @@ -32,12 +35,15 @@ public class NamedSystemSchemaTest extends CalciteTestBase @Mock private SystemSchema systemSchema; + private PlannerConfig plannerConfig; + private NamedSystemSchema target; @Before public void setUp() { - target = new NamedSystemSchema(systemSchema); + plannerConfig = EasyMock.createMock(PlannerConfig.class); + target = new NamedSystemSchema(plannerConfig, systemSchema); } @Test @@ -51,4 +57,22 @@ public class NamedSystemSchemaTest extends CalciteTestBase { Assert.assertEquals(systemSchema, target.getSchema()); } + + @Test + public void testResourceTypeAuthDisabled() + { + EasyMock.expect(plannerConfig.isAuthorizeSystemTablesDirectly()).andReturn(false).once(); + EasyMock.replay(plannerConfig); + Assert.assertNull(target.getSchemaResourceType("servers")); + EasyMock.verify(plannerConfig); + } + + @Test + public void testResourceTypeAuthEnabled() + { + EasyMock.expect(plannerConfig.isAuthorizeSystemTablesDirectly()).andReturn(true).once(); + EasyMock.replay(plannerConfig); + Assert.assertEquals(ResourceType.SYSTEM_TABLE, target.getSchemaResourceType("servers")); + EasyMock.verify(plannerConfig); + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java index a5e493d7f85..ca70e1dfe4a 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java @@ -1140,7 +1140,7 @@ public class CalciteTests SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); Set namedSchemas = ImmutableSet.of( new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), - new NamedSystemSchema(systemSchema), + new NamedSystemSchema(plannerConfig, systemSchema), new NamedLookupSchema(lookupSchema) ); DruidSchemaCatalog catalog = new DruidSchemaCatalog( @@ -1178,7 +1178,7 @@ public class CalciteTests SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); Set namedSchemas = ImmutableSet.of( new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), - new NamedSystemSchema(systemSchema), + new NamedSystemSchema(plannerConfig, systemSchema), new NamedLookupSchema(lookupSchema), new NamedViewSchema(viewSchema) ); diff --git a/website/.spelling b/website/.spelling index 017402cc0dc..d6e22f749fe 100644 --- a/website/.spelling +++ b/website/.spelling @@ -180,6 +180,7 @@ SqlFirehose SqlParameter SslContextFactory StatsD +SYSTEM_TABLE TCP TGT TLS