mirror of https://github.com/apache/druid.git
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:
parent
3c487ff5b4
commit
5de26cf6d9
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<List<Map<String, Object>>> SYS_SCHEMA_RESULTS_TYPE_REFERENCE =
|
||||
new TypeReference<List<Map<String, Object>>>()
|
||||
{
|
||||
};
|
||||
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<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>> 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();
|
||||
}
|
||||
|
|
|
@ -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<ResourceAction> 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<ResourceAction> 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<ResourceAction> 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<ResourceAction> permissions = Collections.singletonList(
|
||||
new ResourceAction(
|
||||
new Resource(".*", ResourceType.STATE),
|
||||
Action.READ
|
||||
)
|
||||
);
|
||||
createUserAndRoleWithPermissions(
|
||||
adminClient,
|
||||
"druid",
|
||||
"helloworld",
|
||||
"druidrole",
|
||||
permissions
|
||||
STATE_ONLY_PERMISSIONS
|
||||
);
|
||||
|
||||
// create 100 users
|
||||
|
|
|
@ -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<ResourceAction> 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<ResourceAction> 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<ResourceAction> 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");
|
||||
|
|
|
@ -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<String> KNOWN_TYPES = Sets.newConcurrentHashSet();
|
||||
|
||||
|
@ -41,6 +42,7 @@ public class ResourceType
|
|||
registerResourceType(VIEW);
|
||||
registerResourceType(CONFIG);
|
||||
registerResourceType(STATE);
|
||||
registerResourceType(SYSTEM_TABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<String, Object> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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<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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1140,7 +1140,7 @@ public class CalciteTests
|
|||
SchemaPlus rootSchema = CalciteSchema.createRootSchema(false, false).plus();
|
||||
Set<NamedSchema> 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<NamedSchema> namedSchemas = ImmutableSet.of(
|
||||
new NamedDruidSchema(druidSchema, CalciteTests.DRUID_SCHEMA_NAME),
|
||||
new NamedSystemSchema(systemSchema),
|
||||
new NamedSystemSchema(plannerConfig, systemSchema),
|
||||
new NamedLookupSchema(lookupSchema),
|
||||
new NamedViewSchema(viewSchema)
|
||||
);
|
||||
|
|
|
@ -180,6 +180,7 @@ SqlFirehose
|
|||
SqlParameter
|
||||
SslContextFactory
|
||||
StatsD
|
||||
SYSTEM_TABLE
|
||||
TCP
|
||||
TGT
|
||||
TLS
|
||||
|
|
Loading…
Reference in New Issue