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
This commit is contained in:
Clint Wylie 2021-09-21 13:28:26 -07:00 committed by GitHub
parent 3c487ff5b4
commit 5de26cf6d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 579 additions and 600 deletions

View File

@ -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.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.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.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`. > 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 > These properties are no longer available. Since Druid 0.18.0, you can use `druid.server.http.maxSubqueryRows` to control the maximum

View File

@ -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. 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. * DATASOURCE – Each Druid table (i.e., `tables` in the `druid` schema in SQL) is a resource.
* CONFIG – Configuration resources exposed by the cluster components. * CONFIG – Configuration resources exposed by the cluster components.
* STATE – Cluster-wide state resources. * 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). For specific resources associated with the types, see the endpoint list below and corresponding descriptions in [API Reference](./api-reference.md).

View File

@ -72,6 +72,7 @@ druid_coordinator_kill_supervisor_on=true
druid_coordinator_kill_supervisor_period=PT10S druid_coordinator_kill_supervisor_period=PT10S
druid_coordinator_kill_supervisor_durationToRetain=PT0M druid_coordinator_kill_supervisor_durationToRetain=PT0M
druid_coordinator_period_metadataStoreManagementPeriod=PT10S druid_coordinator_period_metadataStoreManagementPeriod=PT10S
druid_sql_planner_authorizeSystemTablesDirectly=true
# Testing the legacy config from https://github.com/apache/druid/pull/10267 # Testing the legacy config from https://github.com/apache/druid/pull/10267
# Can remove this when the flag is no longer needed # Can remove this when the flag is no longer needed

View File

@ -136,3 +136,21 @@ homeDirectory: /home/druid
uidNumber: 7 uidNumber: 7
gidNumber: 7 gidNumber: 7
userPassword: helloworld 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

View File

@ -21,6 +21,7 @@ package org.apache.druid.tests.security;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.inject.Inject; 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.HttpClient;
import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.java.util.http.client.auth.BasicCredentials;
import org.apache.druid.java.util.http.client.response.StatusResponseHolder; 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.sql.avatica.DruidAvaticaJsonHandler;
import org.apache.druid.testing.IntegrationTestingConfig; import org.apache.druid.testing.IntegrationTestingConfig;
import org.apache.druid.testing.clients.CoordinatorResourceTestClient; 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.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -50,19 +56,17 @@ import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.Statement; import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.stream.Collectors;
public abstract class AbstractAuthConfigurationTest public abstract class AbstractAuthConfigurationTest
{ {
private static final Logger LOG = new Logger(AbstractAuthConfigurationTest.class); private static final Logger LOG = new Logger(AbstractAuthConfigurationTest.class);
private static final String INVALID_NAME = "invalid%2Fname";
static final TypeReference<List<Map<String, Object>>> SYS_SCHEMA_RESULTS_TYPE_REFERENCE =
new TypeReference<List<Map<String, Object>>>()
{
};
static final String SYSTEM_SCHEMA_SEGMENTS_RESULTS_RESOURCE = static final String SYSTEM_SCHEMA_SEGMENTS_RESULTS_RESOURCE =
"/results/auth_test_sys_schema_segments.json"; "/results/auth_test_sys_schema_segments.json";
@ -85,7 +89,84 @@ public abstract class AbstractAuthConfigurationTest
static final String SYS_SCHEMA_TASKS_QUERY = static final String SYS_SCHEMA_TASKS_QUERY =
"SELECT * FROM sys.tasks WHERE datasource IN ('auth_test')"; "SELECT * FROM sys.tasks WHERE datasource IN ('auth_test')";
private static final String INVALID_NAME = "invalid%2Fname"; static final TypeReference<List<Map<String, Object>>> SYS_SCHEMA_RESULTS_TYPE_REFERENCE =
new TypeReference<List<Map<String, Object>>>()
{
};
/**
* 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<ResourceAction> 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<ResourceAction> 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<ResourceAction> 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<ResourceAction> STATE_ONLY_PERMISSIONS = ImmutableList.of(
new ResourceAction(
new Resource(".*", ResourceType.STATE),
Action.READ
),
new ResourceAction(
new Resource(".*", ResourceType.SYSTEM_TABLE),
Action.READ
)
);
List<Map<String, Object>> adminSegments; List<Map<String, Object>> adminSegments;
List<Map<String, Object>> adminTasks; List<Map<String, Object>> adminTasks;
@ -107,11 +188,323 @@ public abstract class AbstractAuthConfigurationTest
HttpClient adminClient; HttpClient adminClient;
HttpClient datasourceOnlyUserClient; HttpClient datasourceOnlyUserClient;
HttpClient datasourceAndSysUserClient;
HttpClient datasourceWithStateUserClient; HttpClient datasourceWithStateUserClient;
HttpClient stateOnlyUserClient; HttpClient stateOnlyUserClient;
HttpClient internalSystemClient; 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) void checkNodeAccess(HttpClient httpClient)
{ {
HttpUtil.makeRequest(httpClient, HttpMethod.GET, config.getCoordinatorUrl() + "/status", null); HttpUtil.makeRequest(httpClient, HttpMethod.GET, config.getCoordinatorUrl() + "/status", null);
@ -302,7 +695,7 @@ public abstract class AbstractAuthConfigurationTest
testOptionsRequests(adminClient); testOptionsRequests(adminClient);
} }
void verifyAuthenticatioInvalidAuthNameFails() void verifyAuthenticationInvalidAuthNameFails()
{ {
verifyInvalidAuthNameFails(StringUtils.format( verifyInvalidAuthNameFails(StringUtils.format(
"%s/druid-ext/basic-security/authentication/listen/%s", "%s/druid-ext/basic-security/authentication/listen/%s",
@ -370,7 +763,6 @@ public abstract class AbstractAuthConfigurationTest
setupTestSpecificHttpClients(); setupTestSpecificHttpClients();
} }
abstract void setupUsers() throws Exception;
void setupCommonHttpClients() void setupCommonHttpClients()
{ {
@ -384,6 +776,11 @@ public abstract class AbstractAuthConfigurationTest
httpClient httpClient
); );
datasourceAndSysUserClient = new CredentialedHttpClient(
new BasicCredentials("datasourceAndSysUser", "helloworld"),
httpClient
);
datasourceWithStateUserClient = new CredentialedHttpClient( datasourceWithStateUserClient = new CredentialedHttpClient(
new BasicCredentials("datasourceWithStateUser", "helloworld"), new BasicCredentials("datasourceWithStateUser", "helloworld"),
httpClient httpClient
@ -400,8 +797,6 @@ public abstract class AbstractAuthConfigurationTest
); );
} }
abstract void setupTestSpecificHttpClients() throws Exception;
void setExpectedSystemSchemaObjects() throws IOException void setExpectedSystemSchemaObjects() throws IOException
{ {
// initial setup is done now, run the system schema response content tests // 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())); json = StringUtils.replace(json, "%%NON_LEADER%%", String.valueOf(NullHandling.defaultLongValue()));
return json; return json;
} }
abstract String getAuthenticatorName();
abstract String getAuthorizerName();
abstract String getExpectedAvaticaAuthError();
} }

View File

@ -19,30 +19,23 @@
package org.apache.druid.tests.security; 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.StringUtils;
import org.apache.druid.java.util.common.logger.Logger; 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.CredentialedHttpClient;
import org.apache.druid.java.util.http.client.HttpClient; 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.auth.BasicCredentials;
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate; 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.ResourceAction;
import org.apache.druid.server.security.ResourceType;
import org.apache.druid.testing.guice.DruidTestModuleFactory; import org.apache.druid.testing.guice.DruidTestModuleFactory;
import org.apache.druid.testing.utils.HttpUtil; import org.apache.druid.testing.utils.HttpUtil;
import org.apache.druid.testing.utils.ITRetryUtil; import org.apache.druid.testing.utils.ITRetryUtil;
import org.apache.druid.tests.TestNGGroup; import org.apache.druid.tests.TestNGGroup;
import org.jboss.netty.handler.codec.http.HttpMethod; 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.BeforeClass;
import org.testng.annotations.Guice; import org.testng.annotations.Guice;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Test(groups = TestNGGroup.SECURITY) @Test(groups = TestNGGroup.SECURITY)
@Guice(moduleFactory = DruidTestModuleFactory.class) @Guice(moduleFactory = DruidTestModuleFactory.class)
@ -65,313 +58,61 @@ public class ITBasicAuthConfigurationTest extends AbstractAuthConfigurationTest
() -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load" () -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load"
); );
setupHttpClients(); setupHttpClientsAndUsers();
setupUsers();
setExpectedSystemSchemaObjects(); 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 @Test
public void test_druid99User_hasNodeAccess() public void test_druid99User_hasNodeAccess()
{ {
checkNodeAccess(druid99); 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 @Override
void setupUsers() throws Exception void setupDatasourceOnlyUser() throws Exception
{ {
// 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( createUserAndRoleWithPermissions(
adminClient, adminClient,
"datasourceOnlyUser", "datasourceOnlyUser",
"helloworld", "helloworld",
"datasourceOnlyRole", "datasourceOnlyRole",
readDatasourceOnlyPermissions DATASOURCE_ONLY_PERMISSIONS
); );
}
// create a new user+role that can only read 'auth_test' + STATE read access @Override
List<ResourceAction> readDatasourceWithStatePermissions = ImmutableList.of( void setupDatasourceAndSysTableUser() throws Exception
new ResourceAction( {
new Resource("auth_test", ResourceType.DATASOURCE), createUserAndRoleWithPermissions(
Action.READ adminClient,
), "datasourceAndSysUser",
new ResourceAction( "helloworld",
new Resource(".*", ResourceType.STATE), "datasourceAndSysRole",
Action.READ DATASOURCE_SYS_PERMISSIONS
)
); );
}
@Override
void setupDatasourceAndSysAndStateUser() throws Exception
{
createUserAndRoleWithPermissions( createUserAndRoleWithPermissions(
adminClient, adminClient,
"datasourceWithStateUser", "datasourceWithStateUser",
"helloworld", "helloworld",
"datasourceWithStateRole", "datasourceWithStateRole",
readDatasourceWithStatePermissions DATASOURCE_SYS_STATE_PERMISSIONS
); );
}
// create a new user+role with only STATE read access @Override
List<ResourceAction> stateOnlyPermissions = ImmutableList.of( void setupSysTableAndStateOnlyUser() throws Exception
new ResourceAction( {
new Resource(".*", ResourceType.STATE),
Action.READ
)
);
createUserAndRoleWithPermissions( createUserAndRoleWithPermissions(
adminClient, adminClient,
"stateOnlyUser", "stateOnlyUser",
"helloworld", "helloworld",
"stateOnlyRole", "stateOnlyRole",
stateOnlyPermissions STATE_ONLY_PERMISSIONS
); );
} }
@ -379,18 +120,12 @@ public class ITBasicAuthConfigurationTest extends AbstractAuthConfigurationTest
void setupTestSpecificHttpClients() throws Exception void setupTestSpecificHttpClients() throws Exception
{ {
// create a new user+role that can read /status // create a new user+role that can read /status
List<ResourceAction> permissions = Collections.singletonList(
new ResourceAction(
new Resource(".*", ResourceType.STATE),
Action.READ
)
);
createUserAndRoleWithPermissions( createUserAndRoleWithPermissions(
adminClient, adminClient,
"druid", "druid",
"helloworld", "helloworld",
"druidrole", "druidrole",
permissions STATE_ONLY_PERMISSIONS
); );
// create 100 users // create 100 users

View File

@ -20,7 +20,6 @@
package org.apache.druid.tests.security; package org.apache.druid.tests.security;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.apache.druid.java.util.common.StringUtils; 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.HttpClient;
import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.java.util.http.client.auth.BasicCredentials;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping; 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.ResourceAction;
import org.apache.druid.server.security.ResourceType;
import org.apache.druid.testing.IntegrationTestingConfig; 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.guice.DruidTestModuleFactory;
import org.apache.druid.testing.utils.HttpUtil; import org.apache.druid.testing.utils.HttpUtil;
import org.apache.druid.testing.utils.ITRetryUtil; import org.apache.druid.testing.utils.ITRetryUtil;
import org.apache.druid.tests.TestNGGroup; import org.apache.druid.tests.TestNGGroup;
import org.jboss.netty.handler.codec.http.HttpMethod; 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.BeforeClass;
import org.testng.annotations.Guice; import org.testng.annotations.Guice;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -48,7 +42,6 @@ import org.testng.annotations.Test;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
@Test(groups = TestNGGroup.LDAP_SECURITY) @Test(groups = TestNGGroup.LDAP_SECURITY)
@Guice(moduleFactory = DruidTestModuleFactory.class) @Guice(moduleFactory = DruidTestModuleFactory.class)
@ -67,8 +60,6 @@ public class ITBasicAuthLdapConfigurationTest extends AbstractAuthConfigurationT
@Inject @Inject
ObjectMapper jsonMapper; ObjectMapper jsonMapper;
@Inject
private CoordinatorResourceTestClient coordinatorClient;
private HttpClient druidUserClient; private HttpClient druidUserClient;
private HttpClient stateOnlyNoLdapGroupUserClient; private HttpClient stateOnlyNoLdapGroupUserClient;
@ -81,177 +72,10 @@ public class ITBasicAuthLdapConfigurationTest extends AbstractAuthConfigurationT
() -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load" () -> coordinatorClient.areSegmentsLoaded("auth_test"), "auth_test segment load"
); );
setupHttpClients(); setupHttpClientsAndUsers();
setupUsers();
setExpectedSystemSchemaObjects(); 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 @Test
public void test_systemSchemaAccess_stateOnlyNoLdapGroupUser() throws Exception 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 @Test
public void test_druidUser_hasNodeAccess() public void test_druidUser_hasNodeAccess()
{ {
checkNodeAccess(druidUserClient); checkNodeAccess(druidUserClient);
} }
@Test
public void test_avaticaQuery_broker()
{
testAvaticaQuery(getBrokerAvacticaUrl());
}
@Test @Override
public void test_avaticaQuery_router() void setupDatasourceOnlyUser() throws Exception
{ {
testAvaticaQuery(getRouterAvacticaUrl()); createRoleWithPermissionsAndGroupMapping(
} "datasourceOnlyGroup",
ImmutableMap.of("datasourceOnlyRole", DATASOURCE_ONLY_PERMISSIONS)
@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 @Override
void setupUsers() throws Exception void setupDatasourceAndSysTableUser() throws Exception
{ {
// create a role that can only read 'auth_test'
List<ResourceAction> readDatasourceOnlyPermissions = Collections.singletonList(
new ResourceAction(
new Resource("auth_test", ResourceType.DATASOURCE),
Action.READ
)
);
createRoleWithPermissionsAndGroupMapping( createRoleWithPermissionsAndGroupMapping(
"datasourceOnlyGroup", "datasourceWithSysGroup",
ImmutableMap.of("datasourceOnlyRole", readDatasourceOnlyPermissions) ImmutableMap.of("datasourceWithSysRole", DATASOURCE_SYS_PERMISSIONS)
);
// create a new 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
)
); );
}
@Override
void setupDatasourceAndSysAndStateUser() throws Exception
{
createRoleWithPermissionsAndGroupMapping( createRoleWithPermissionsAndGroupMapping(
"datasourceWithStateGroup", "datasourceWithStateGroup",
ImmutableMap.of("datasourceWithStateRole", readDatasourceWithStatePermissions) ImmutableMap.of("datasourceWithStateRole", DATASOURCE_SYS_STATE_PERMISSIONS)
);
// create a new role with only STATE read access
List<ResourceAction> stateOnlyPermissions = ImmutableList.of(
new ResourceAction(
new Resource(".*", ResourceType.STATE),
Action.READ
)
); );
}
@Override
void setupSysTableAndStateOnlyUser() throws Exception
{
createRoleWithPermissionsAndGroupMapping( createRoleWithPermissionsAndGroupMapping(
"stateOnlyGroup", "stateOnlyGroup",
ImmutableMap.of("stateOnlyRole", stateOnlyPermissions) ImmutableMap.of("stateOnlyRole", STATE_ONLY_PERMISSIONS)
); );
// create a role that can read /status // create a role that can read /status
createRoleWithPermissionsAndGroupMapping( createRoleWithPermissionsAndGroupMapping(
"druidGroup", "druidGroup",
ImmutableMap.of("druidrole", stateOnlyPermissions) ImmutableMap.of("druidrole", STATE_ONLY_PERMISSIONS)
); );
assignUserToRole("stateOnlyNoLdapGroup", "stateOnlyRole"); assignUserToRole("stateOnlyNoLdapGroup", "stateOnlyRole");

View File

@ -32,6 +32,7 @@ public class ResourceType
public static final String VIEW = "VIEW"; public static final String VIEW = "VIEW";
public static final String CONFIG = "CONFIG"; public static final String CONFIG = "CONFIG";
public static final String STATE = "STATE"; public static final String STATE = "STATE";
public static final String SYSTEM_TABLE = "SYSTEM_TABLE";
private static final Set<String> KNOWN_TYPES = Sets.newConcurrentHashSet(); private static final Set<String> KNOWN_TYPES = Sets.newConcurrentHashSet();
@ -41,6 +42,7 @@ public class ResourceType
registerResourceType(VIEW); registerResourceType(VIEW);
registerResourceType(CONFIG); registerResourceType(CONFIG);
registerResourceType(STATE); registerResourceType(STATE);
registerResourceType(SYSTEM_TABLE);
} }
/** /**

View File

@ -67,6 +67,9 @@ public class PlannerConfig
@JsonProperty @JsonProperty
private boolean computeInnerJoinCostAsFilter = true; private boolean computeInnerJoinCostAsFilter = true;
@JsonProperty
private boolean authorizeSystemTablesDirectly = false;
public long getMetadataSegmentPollPeriod() public long getMetadataSegmentPollPeriod()
{ {
return metadataSegmentPollPeriod; return metadataSegmentPollPeriod;
@ -129,6 +132,11 @@ public class PlannerConfig
return computeInnerJoinCostAsFilter; return computeInnerJoinCostAsFilter;
} }
public boolean isAuthorizeSystemTablesDirectly()
{
return authorizeSystemTablesDirectly;
}
public PlannerConfig withOverrides(final Map<String, Object> context) public PlannerConfig withOverrides(final Map<String, Object> context)
{ {
if (context == null) { if (context == null) {
@ -153,15 +161,18 @@ public class PlannerConfig
CTX_KEY_USE_APPROXIMATE_TOPN, CTX_KEY_USE_APPROXIMATE_TOPN,
isUseApproximateTopN() isUseApproximateTopN()
); );
newConfig.computeInnerJoinCostAsFilter = getContextBoolean(
context,
CTX_COMPUTE_INNER_JOIN_COST_AS_FILTER,
computeInnerJoinCostAsFilter
);
newConfig.requireTimeCondition = isRequireTimeCondition(); newConfig.requireTimeCondition = isRequireTimeCondition();
newConfig.sqlTimeZone = getSqlTimeZone(); newConfig.sqlTimeZone = getSqlTimeZone();
newConfig.awaitInitializationOnStart = isAwaitInitializationOnStart(); newConfig.awaitInitializationOnStart = isAwaitInitializationOnStart();
newConfig.metadataSegmentCacheEnable = isMetadataSegmentCacheEnable(); newConfig.metadataSegmentCacheEnable = isMetadataSegmentCacheEnable();
newConfig.metadataSegmentPollPeriod = getMetadataSegmentPollPeriod(); newConfig.metadataSegmentPollPeriod = getMetadataSegmentPollPeriod();
newConfig.serializeComplexValues = shouldSerializeComplexValues(); newConfig.serializeComplexValues = shouldSerializeComplexValues();
newConfig.computeInnerJoinCostAsFilter = getContextBoolean(context, newConfig.authorizeSystemTablesDirectly = isAuthorizeSystemTablesDirectly();
CTX_COMPUTE_INNER_JOIN_COST_AS_FILTER,
computeInnerJoinCostAsFilter);
return newConfig; return newConfig;
} }

View File

@ -21,6 +21,10 @@ package org.apache.druid.sql.calcite.schema;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.apache.calcite.schema.Schema; 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. * 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"; public static final String NAME = "sys";
private final SystemSchema systemSchema; private final SystemSchema systemSchema;
private final PlannerConfig plannerConfig;
@Inject @Inject
public NamedSystemSchema(SystemSchema systemSchema) public NamedSystemSchema(PlannerConfig plannerConfig, SystemSchema systemSchema)
{ {
this.plannerConfig = plannerConfig;
this.systemSchema = systemSchema; this.systemSchema = systemSchema;
} }
@ -48,4 +54,14 @@ public class NamedSystemSchema implements NamedSchema
{ {
return systemSchema; return systemSchema;
} }
@Nullable
@Override
public String getSchemaResourceType(String resourceName)
{
if (plannerConfig.isAuthorizeSystemTablesDirectly()) {
return ResourceType.SYSTEM_TABLE;
}
return null;
}
} }

View File

@ -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 DUMMY_SQL_ID = "dummy";
public static final String LOS_ANGELES = "America/Los_Angeles"; public static final String LOS_ANGELES = "America/Los_Angeles";

View File

@ -22,6 +22,7 @@ package org.apache.druid.sql.calcite;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.Resource;
import org.apache.druid.server.security.ResourceType; 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.apache.druid.sql.calcite.util.CalciteTests;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -243,4 +244,39 @@ public class DruidPlannerResourceAnalyzeTest extends BaseCalciteQueryTest
requiredResources 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<Resource> 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
);
}
}
} }

View File

@ -19,7 +19,10 @@
package org.apache.druid.sql.calcite.schema; 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.apache.druid.sql.calcite.util.CalciteTestBase;
import org.easymock.EasyMock;
import org.easymock.Mock; import org.easymock.Mock;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -32,12 +35,15 @@ public class NamedSystemSchemaTest extends CalciteTestBase
@Mock @Mock
private SystemSchema systemSchema; private SystemSchema systemSchema;
private PlannerConfig plannerConfig;
private NamedSystemSchema target; private NamedSystemSchema target;
@Before @Before
public void setUp() public void setUp()
{ {
target = new NamedSystemSchema(systemSchema); plannerConfig = EasyMock.createMock(PlannerConfig.class);
target = new NamedSystemSchema(plannerConfig, systemSchema);
} }
@Test @Test
@ -51,4 +57,22 @@ public class NamedSystemSchemaTest extends CalciteTestBase
{ {
Assert.assertEquals(systemSchema, target.getSchema()); 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);
}
} }

View File

@ -1140,7 +1140,7 @@ public class CalciteTests
SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus();
Set<NamedSchema> namedSchemas = ImmutableSet.of( Set<NamedSchema> namedSchemas = ImmutableSet.of(
new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME),
new NamedSystemSchema(systemSchema), new NamedSystemSchema(plannerConfig, systemSchema),
new NamedLookupSchema(lookupSchema) new NamedLookupSchema(lookupSchema)
); );
DruidSchemaCatalog catalog = new DruidSchemaCatalog( DruidSchemaCatalog catalog = new DruidSchemaCatalog(
@ -1178,7 +1178,7 @@ public class CalciteTests
SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus(); SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus();
Set<NamedSchema> namedSchemas = ImmutableSet.of( Set<NamedSchema> namedSchemas = ImmutableSet.of(
new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME), new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME),
new NamedSystemSchema(systemSchema), new NamedSystemSchema(plannerConfig, systemSchema),
new NamedLookupSchema(lookupSchema), new NamedLookupSchema(lookupSchema),
new NamedViewSchema(viewSchema) new NamedViewSchema(viewSchema)
); );

View File

@ -180,6 +180,7 @@ SqlFirehose
SqlParameter SqlParameter
SslContextFactory SslContextFactory
StatsD StatsD
SYSTEM_TABLE
TCP TCP
TGT TGT
TLS TLS