Skip to content
Permalink
Browse files
Add support for authorizing query context params (#12396)
The query context is a way that the user gives a hint to the Druid query engine, so that they enforce a certain behavior or at least let the query engine prefer a certain plan during query planning. Today, there are 3 types of query context params as below.

Default context params. They are set via druid.query.default.context in runtime properties. Any user context params can be default params.
User context params. They are set in the user query request. See https://druid.apache.org/docs/latest/querying/query-context.html for parameters.
System context params. They are set by the Druid query engine during query processing. These params override other context params.
Today, any context params are allowed to users. This can cause 
1) a bad UX if the context param is not matured yet or 
2) even query failure or system fault in the worst case if a sensitive param is abused, ex) maxSubqueryRows.

This PR adds an ability to limit context params per user role. That means, a query will fail if you have a context param set in the query that is not allowed to you. To do that, this PR adds a new built-in resource type, QUERY_CONTEXT. The resource to authorize has a name of the context param (such as maxSubqueryRows) and the type of QUERY_CONTEXT. To allow a certain context param for a user, the user should be granted WRITE permission on the context param resource. Here is an example of the permission.

{
  "resourceAction" : {
    "resource" : {
      "name" : "maxSubqueryRows",
      "type" : "QUERY_CONTEXT"
    },
    "action" : "WRITE"
  },
  "resourceNamePattern" : "maxSubqueryRows"
}
Each role can have multiple permissions for context params. Each permission should be set for different context params.

When a query is issued with a query context X, the query will fail if the user who issued the query does not have WRITE permission on the query context X. In this case,

HTTP endpoints will return 403 response code.
JDBC will throw ForbiddenException.
Note: there is a context param called brokerService that is used only by the router. This param is used to pin your query to run it in a specific broker. Because the authorization is done not in the router, but in the broker, if you have brokerService set in your query without a proper permission, your query will fail in the broker after routing is done. Technically, this is not right because the authorization is checked after the context param takes effect. However, this should not cause any user-facing issue and thus should be OK. The query will still fail if the user doesn’t have permission for brokerService.

The context param authorization can be enabled using druid.auth.authorizeQueryContextParams. This is disabled by default to avoid any hassle when someone upgrades his cluster blindly without reading release notes.
  • Loading branch information
jihoonson committed Apr 21, 2022
1 parent 4c6ba73 commit 73ce5df22dc64a675be2ec5e6b86b1b6ad211808
Showing 48 changed files with 1,623 additions and 500 deletions.
@@ -28,6 +28,7 @@
import org.apache.druid.query.BaseQuery;
import org.apache.druid.query.DataSource;
import org.apache.druid.query.Query;
import org.apache.druid.query.QueryContext;
import org.apache.druid.query.QueryRunner;
import org.apache.druid.query.QuerySegmentWalker;
import org.apache.druid.query.filter.DimFilter;
@@ -145,6 +146,12 @@ public Map<String, Object> getContext()
return query.getContext();
}

@Override
public QueryContext getQueryContext()
{
return query.getQueryContext();
}

@Override
public <ContextType> ContextType getContextValue(String key)
{
@@ -32,8 +32,8 @@
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.druid.java.util.common.Numbers;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.query.QueryContext;
import org.apache.druid.query.aggregation.AggregatorFactory;
import org.apache.druid.query.aggregation.datasketches.quantiles.DoublesSketchAggregatorFactory;
import org.apache.druid.query.aggregation.datasketches.quantiles.DoublesSketchToQuantilePostAggregator;
@@ -50,7 +50,6 @@

import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;

public class DoublesSketchApproxQuantileSqlAggregator implements SqlAggregator
{
@@ -200,11 +199,12 @@ public Aggregation toDruidAggregation(
);
}

@Nullable
static Long getMaxStreamLengthFromQueryContext(Map<String, Object> queryContext)
static long getMaxStreamLengthFromQueryContext(QueryContext queryContext)
{
final Object val = queryContext.get(CTX_APPROX_QUANTILE_DS_MAX_STREAM_LENGTH);
return val == null ? null : Numbers.parseLong(val);
return queryContext.getAsLong(
CTX_APPROX_QUANTILE_DS_MAX_STREAM_LENGTH,
DoublesSketchAggregatorFactory.DEFAULT_MAX_STREAM_LENGTH
);
}

private static class DoublesSketchApproxQuantileSqlAggFunction extends SqlAggFunction
@@ -36,6 +36,7 @@ druid_auth_authenticator_basic_type=basic
druid_auth_authenticatorChain=["basic"]
druid_auth_authorizer_basic_type=basic
druid_auth_authorizers=["basic"]
druid_auth_authorizeQueryContextParams=true
druid_client_https_certAlias=druid
druid_client_https_keyManagerPassword=druid123
druid_client_https_keyStorePassword=druid123
@@ -46,6 +46,7 @@ druid_auth_authorizer_ldapauth_initialAdminUser=admin
druid_auth_authorizer_ldapauth_initialAdminRole=admin
druid_auth_authorizer_ldapauth_roleProvider_type=ldap
druid_auth_authorizers=["ldapauth"]
druid_auth_authorizeQueryContextParams=true
druid_client_https_certAlias=druid
druid_client_https_keyManagerPassword=druid123
druid_client_https_keyStorePassword=druid123
@@ -154,3 +154,21 @@ objectClass: groupOfUniqueNames
cn: datasourceWithSysGroup
description: datasourceWithSysGroup users
uniqueMember: uid=datasourceAndSysUser,ou=Users,dc=example,dc=org

dn: uid=datasourceAndContextParamsUser,ou=Users,dc=example,dc=org
uid: datasourceAndContextParamsUser
cn: datasourceAndContextParamsUser
sn: datasourceAndContextParamsUser
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
homeDirectory: /home/datasourceAndContextParamsUser
uidNumber: 9
gidNumber: 9
userPassword: helloworld

dn: cn=datasourceAndContextParamsGroup,ou=Groups,dc=example,dc=org
objectClass: groupOfUniqueNames
cn: datasourceAndContextParamsGroup
description: datasourceAndContextParamsGroup users
uniqueMember: uid=datasourceAndContextParamsUser,ou=Users,dc=example,dc=org
@@ -26,7 +26,6 @@
import org.apache.druid.java.util.http.client.Request;
import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
import org.apache.druid.testing.clients.AbstractQueryResourceTestClient;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;

@@ -36,7 +35,7 @@

public class HttpUtil
{
private static final Logger LOG = new Logger(AbstractQueryResourceTestClient.class);
private static final Logger LOG = new Logger(HttpUtil.class);
private static final StatusResponseHandler RESPONSE_HANDLER = StatusResponseHandler.getInstance();

static final int NUM_RETRIES = 30;

0 comments on commit 73ce5df

Please sign in to comment.