apache/druid · error · ForbiddenException

Unauthorized

Error message

Unauthorized

What it means

In the gRPC query server's runNativeQuery, after the query is authorized via QueryLifecycle.authorize, any result that does not allow unrestricted access causes a ForbiddenException with Druid's default "Unauthorized" access message. The gRPC extension does not support resource-restricted (partial) authorization: if the authenticator/authorizer only grants restricted access to the requested resources, the whole query is rejected. It surfaces to the gRPC client as a runtime error with the message "Unauthorized".

Source

Thrown at extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java:184

    if (Strings.isNullOrEmpty(query.getId())) {
      query = query.withId(UUID.randomUUID().toString());
    }

    final QueryLifecycle queryLifecycle = queryLifecycleFactory.factorize();

    if (queryScheduler != null) {
      final String queryId = query.getId();
      cancelCallback.set(() -> queryScheduler.cancelQuery(queryId));
    }

    final org.apache.druid.server.QueryResponse queryResponse;
    final String currThreadName = Thread.currentThread().getName();
    Throwable caught = null;
    try {
      queryLifecycle.initialize(query);
      AuthorizationResult authorizationResult = queryLifecycle.authorize(authResult);
      if (!authorizationResult.allowAccessWithNoRestriction()) {
        throw new ForbiddenException(Access.DEFAULT_ERROR_MESSAGE);
      }
      queryResponse = queryLifecycle.execute();

      QueryToolChest queryToolChest = queryLifecycle.getToolChest();

      Sequence<Object[]> sequence = queryToolChest.resultsAsArrays(query, queryResponse.getResults());
      RowSignature rowSignature = queryToolChest.resultArraySignature(query);

      Thread.currentThread().setName(StringUtils.format("grpc-native[%s]", query.getId()));
      final ByteString results = encodeNativeResults(request, sequence, rowSignature);
      return QueryResponse.newBuilder()
                          .setQueryId(query.getId())
                          .setStatus(QueryStatus.OK)
                          .setData(results)
                          .clearErrorMessage()
                          .addAllColumns(encodeNativeColumns(rowSignature, request.getSkipColumnsList()))
                          .build();
    }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Grant the authenticated user/role READ (and any needed) permissions on all datasources the query touches via the Druid permissions UI or coordinator API.
  2. Verify the authenticator/authorizer chain in the gRPC server config matches the credentials the client sends (basic auth is the supported path).
  3. Check which resources the query references (datasource, metadata tables) and ensure each is authorized; one unauthorized resource rejects the entire query.
  4. Capture the actual identity of the failing request (logs) to confirm the intended principal is being authenticated, not an anonymous/default one.

Example fix

// before: user 'etl-bot' has no role with datasource access
// grants via coordinator API after
POST /druid/coordinator/v1/authorizer/basic/roles/datasource-read/permissions
[{"resource": {"name": "wikipedia", "type": "DATASOURCE"}, "action": "READ"}]
// after: assign role
PUT /druid/coordinator/v1/authorizer/basic/users/etl-bot/roles/datasource-read
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: confirm credentials and target datasource before calling
if (username == null || password == null) throw new IllegalStateException("credentials required");
// ensure the principal has READ on every datasource in the query

Try / catch

try {
  grpcStub.query(request);
} catch (StatusRuntimeException e) {
  if (e.getStatus().getCode() == Status.Code.PERMISSION_DENIED || "Unauthorized".equals(e.getStatus().getDescription())) {
    // fix grants / switch credentials
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the gRPC Query service with a native query whose authResult/authorizer yields AuthorizationResult that is not allowAccessWithNoRestriction() — e.g. the user lacks READ permission on the datasource(s) referenced by the query, or the authorizer returns access with restrictions (row/table filters) which gRPC cannot enforce.

Common situations: User credentials exist but the authorizer's role grants access only to other datasources; using an authorizer that returns restricted-access results (gRPC only accepts full access); stale or mis-scoped basic-auth credentials on the client; permissions changed after the client was configured.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/9eba0c71cded3edb. Report an issue: GitHub.