mirror of https://github.com/apache/druid.git
Single auth check for authorized resource filtering (#4818)
* Single auth check for authorized resource filtering * PR comment * PR comments
This commit is contained in:
parent
00d39ce7a5
commit
3a4a483bb0
|
@ -365,24 +365,22 @@ public class OverlordResource
|
|||
// A bit roundabout, but works as a way of figuring out what tasks haven't been handed
|
||||
// off to the runner yet:
|
||||
final List<Task> allActiveTasks = taskStorageQueryAdapter.getActiveTasks();
|
||||
final List<Task> activeTasks;
|
||||
Function<Task, ResourceAction> raGenerator = new Function<Task, ResourceAction>()
|
||||
{
|
||||
@Override
|
||||
public ResourceAction apply(Task input)
|
||||
{
|
||||
return new ResourceAction(
|
||||
new Resource(input.getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
);
|
||||
}
|
||||
Function<Task, Iterable<ResourceAction>> raGenerator = task -> {
|
||||
return Lists.newArrayList(
|
||||
new ResourceAction(
|
||||
new Resource(task.getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
activeTasks = AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
allActiveTasks,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
final List<Task> activeTasks = Lists.newArrayList(
|
||||
AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
allActiveTasks,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
)
|
||||
);
|
||||
|
||||
final Set<String> runnersKnownTasks = Sets.newHashSet(
|
||||
|
@ -464,34 +462,32 @@ public class OverlordResource
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getCompleteTasks(@Context final HttpServletRequest req)
|
||||
{
|
||||
final List<TaskStatus> recentlyFinishedTasks;
|
||||
Function<TaskStatus, ResourceAction> raGenerator = new Function<TaskStatus, ResourceAction>()
|
||||
{
|
||||
@Override
|
||||
public ResourceAction apply(TaskStatus input)
|
||||
{
|
||||
final String taskId = input.getId();
|
||||
final Optional<Task> optionalTask = taskStorageQueryAdapter.getTask(taskId);
|
||||
if (!optionalTask.isPresent()) {
|
||||
throw new WebApplicationException(
|
||||
Response.serverError().entity(
|
||||
StringUtils.format("No task information found for task with id: [%s]", taskId)
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
return new ResourceAction(
|
||||
new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
Function<TaskStatus, Iterable<ResourceAction>> raGenerator = taskStatus -> {
|
||||
final String taskId = taskStatus.getId();
|
||||
final Optional<Task> optionalTask = taskStorageQueryAdapter.getTask(taskId);
|
||||
if (!optionalTask.isPresent()) {
|
||||
throw new WebApplicationException(
|
||||
Response.serverError().entity(
|
||||
StringUtils.format("No task information found for task with id: [%s]", taskId)
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
return Lists.newArrayList(
|
||||
new ResourceAction(
|
||||
new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
recentlyFinishedTasks = AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
taskStorageQueryAdapter.getRecentlyFinishedTaskStatuses(),
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
final List<TaskStatus> recentlyFinishedTasks = Lists.newArrayList(
|
||||
AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
taskStorageQueryAdapter.getRecentlyFinishedTaskStatuses(),
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
)
|
||||
);
|
||||
|
||||
final List<TaskResponseObject> completeTasks = Lists.transform(
|
||||
|
@ -648,33 +644,32 @@ public class OverlordResource
|
|||
HttpServletRequest req
|
||||
)
|
||||
{
|
||||
Function<TaskRunnerWorkItem, ResourceAction> raGenerator = new Function<TaskRunnerWorkItem, ResourceAction>()
|
||||
{
|
||||
@Override
|
||||
public ResourceAction apply(TaskRunnerWorkItem input)
|
||||
{
|
||||
final String taskId = input.getTaskId();
|
||||
final Optional<Task> optionalTask = taskStorageQueryAdapter.getTask(taskId);
|
||||
if (!optionalTask.isPresent()) {
|
||||
throw new WebApplicationException(
|
||||
Response.serverError().entity(
|
||||
StringUtils.format("No task information found for task with id: [%s]", taskId)
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
return new ResourceAction(
|
||||
new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
Function<TaskRunnerWorkItem, Iterable<ResourceAction>> raGenerator = taskRunnerWorkItem -> {
|
||||
final String taskId = taskRunnerWorkItem.getTaskId();
|
||||
final Optional<Task> optionalTask = taskStorageQueryAdapter.getTask(taskId);
|
||||
if (!optionalTask.isPresent()) {
|
||||
throw new WebApplicationException(
|
||||
Response.serverError().entity(
|
||||
StringUtils.format("No task information found for task with id: [%s]", taskId)
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
return Lists.newArrayList(
|
||||
new ResourceAction(
|
||||
new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE),
|
||||
Action.READ
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
collectionToFilter,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
return Lists.newArrayList(
|
||||
AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
collectionToFilter,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import com.google.common.base.Preconditions;
|
|||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
|
@ -38,6 +37,7 @@ import io.druid.server.security.AuthConfig;
|
|||
import io.druid.server.security.AuthorizerMapper;
|
||||
import io.druid.server.security.AuthorizationUtils;
|
||||
import io.druid.server.security.ForbiddenException;
|
||||
import io.druid.server.security.ResourceAction;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -49,6 +49,7 @@ import javax.ws.rs.Produces;
|
|||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -119,36 +120,12 @@ public class SupervisorResource
|
|||
@Override
|
||||
public Response apply(final SupervisorManager manager)
|
||||
{
|
||||
final Set<String> supervisorIds;
|
||||
supervisorIds = Sets.newHashSet();
|
||||
for (String supervisorId : manager.getSupervisorIds()) {
|
||||
Optional<SupervisorSpec> supervisorSpecOptional = manager.getSupervisorSpec(supervisorId);
|
||||
if (supervisorSpecOptional.isPresent()) {
|
||||
Access accessResult = AuthorizationUtils.authorizeAllResourceActions(
|
||||
req,
|
||||
Iterables.transform(
|
||||
supervisorSpecOptional.get().getDataSources(),
|
||||
AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR
|
||||
),
|
||||
authorizerMapper
|
||||
);
|
||||
|
||||
if (accessResult.isAllowed()) {
|
||||
supervisorIds.add(supervisorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no supervisorIds, go ahead and authorize the request.
|
||||
if (manager.getSupervisorIds().size() == 0) {
|
||||
AuthorizationUtils.authorizeAllResourceActions(
|
||||
req,
|
||||
Lists.newArrayList(),
|
||||
authorizerMapper
|
||||
);
|
||||
}
|
||||
|
||||
return Response.ok(supervisorIds).build();
|
||||
Set<String> authorizedSupervisorIds = filterAuthorizedSupervisorIds(
|
||||
req,
|
||||
manager,
|
||||
manager.getSupervisorIds()
|
||||
);
|
||||
return Response.ok(authorizedSupervisorIds).build();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -239,27 +216,22 @@ public class SupervisorResource
|
|||
@Override
|
||||
public Response apply(final SupervisorManager manager)
|
||||
{
|
||||
final Map<String, List<VersionedSupervisorSpec>> supervisorHistory;
|
||||
supervisorHistory = Maps.filterKeys(
|
||||
manager.getSupervisorHistory(),
|
||||
final Map<String, List<VersionedSupervisorSpec>> supervisorHistory = manager.getSupervisorHistory();
|
||||
|
||||
final Set<String> authorizedSupervisorIds = filterAuthorizedSupervisorIds(
|
||||
req,
|
||||
manager,
|
||||
supervisorHistory.keySet()
|
||||
);
|
||||
|
||||
final Map<String, List<VersionedSupervisorSpec>> authorizedSupervisorHistory = Maps.filterKeys(
|
||||
supervisorHistory,
|
||||
new Predicate<String>()
|
||||
{
|
||||
@Override
|
||||
public boolean apply(String id)
|
||||
{
|
||||
Optional<SupervisorSpec> supervisorSpecOptional = manager.getSupervisorSpec(id);
|
||||
if (!supervisorSpecOptional.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
Access accessResult = AuthorizationUtils.authorizeAllResourceActions(
|
||||
req,
|
||||
Iterables.transform(
|
||||
supervisorSpecOptional.get().getDataSources(),
|
||||
AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR
|
||||
),
|
||||
authorizerMapper
|
||||
);
|
||||
return accessResult.isAllowed();
|
||||
return authorizedSupervisorIds.contains(id);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -337,4 +309,32 @@ public class SupervisorResource
|
|||
return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> filterAuthorizedSupervisorIds(
|
||||
final HttpServletRequest req,
|
||||
SupervisorManager manager,
|
||||
Collection<String> supervisorIds
|
||||
)
|
||||
{
|
||||
Function<String, Iterable<ResourceAction>> raGenerator = supervisorId -> {
|
||||
Optional<SupervisorSpec> supervisorSpecOptional = manager.getSupervisorSpec(supervisorId);
|
||||
if (supervisorSpecOptional.isPresent()) {
|
||||
return Iterables.transform(
|
||||
supervisorSpecOptional.get().getDataSources(),
|
||||
AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return Sets.newHashSet(
|
||||
AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
supervisorIds,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package io.druid.server;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -43,6 +44,7 @@ import io.druid.server.http.security.DatasourceResourceFilter;
|
|||
import io.druid.server.security.AuthConfig;
|
||||
import io.druid.server.security.AuthorizerMapper;
|
||||
import io.druid.server.security.AuthorizationUtils;
|
||||
import io.druid.server.security.ResourceAction;
|
||||
import io.druid.timeline.DataSegment;
|
||||
import io.druid.timeline.TimelineLookup;
|
||||
import io.druid.timeline.TimelineObjectHolder;
|
||||
|
@ -119,10 +121,14 @@ public class ClientInfoResource
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Iterable<String> getDataSources(@Context final HttpServletRequest request)
|
||||
{
|
||||
Function<String, Iterable<ResourceAction>> raGenerator = datasourceName -> {
|
||||
return Lists.newArrayList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(datasourceName));
|
||||
};
|
||||
|
||||
return AuthorizationUtils.filterAuthorizedResources(
|
||||
request,
|
||||
getSegmentsForDatasources().keySet(),
|
||||
AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.google.common.base.Function;
|
|||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
import com.sun.jersey.spi.container.ResourceFilters;
|
||||
|
@ -33,6 +34,7 @@ import io.druid.server.http.security.DatasourceResourceFilter;
|
|||
import io.druid.server.security.AuthConfig;
|
||||
import io.druid.server.security.AuthorizerMapper;
|
||||
import io.druid.server.security.AuthorizationUtils;
|
||||
import io.druid.server.security.ResourceAction;
|
||||
import io.druid.timeline.DataSegment;
|
||||
import org.joda.time.Interval;
|
||||
|
||||
|
@ -102,14 +104,20 @@ public class MetadataResource
|
|||
);
|
||||
}
|
||||
|
||||
List<String> datasourceNamesList = AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
dataSourceNamesPreAuth,
|
||||
AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR,
|
||||
authorizerMapper
|
||||
);
|
||||
final Set<String> dataSourceNamesPostAuth = Sets.newTreeSet();
|
||||
Function<String, Iterable<ResourceAction>> raGenerator = datasourceName -> {
|
||||
return Lists.newArrayList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(datasourceName));
|
||||
};
|
||||
|
||||
final Set<String> dataSourceNamesPostAuth = Sets.newTreeSet(datasourceNamesList);
|
||||
Iterables.addAll(
|
||||
dataSourceNamesPostAuth,
|
||||
AuthorizationUtils.filterAuthorizedResources(
|
||||
req,
|
||||
dataSourceNamesPreAuth,
|
||||
raGenerator,
|
||||
authorizerMapper
|
||||
)
|
||||
);
|
||||
|
||||
// Cannot do both includeDisabled and full, let includeDisabled take priority
|
||||
// Always use dataSourceNamesPostAuth to determine the set of returned dataSources
|
||||
|
|
|
@ -20,15 +20,13 @@
|
|||
package io.druid.server.security;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.druid.java.util.common.ISE;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -153,25 +151,41 @@ public class AuthorizationUtils
|
|||
}
|
||||
|
||||
/**
|
||||
* Filter a list of resource-actions using the request's authorization fields, returning a new list of
|
||||
* resource-actions that were authorized.
|
||||
* Filter a collection of resources by applying the resourceActionGenerator to each resource, return an iterable
|
||||
* containing the filtered resources.
|
||||
*
|
||||
* The resourceActionGenerator returns an Iterable<ResourceAction> for each resource.
|
||||
*
|
||||
* If every resource-action in the iterable is authorized, the resource will be added to the filtered resources.
|
||||
*
|
||||
* If there is an authorization failure for one of the resource-actions, the resource will not be
|
||||
* added to the returned filtered resources..
|
||||
*
|
||||
* If the resourceActionGenerator returns null for a resource, that resource will not be added to the filtered
|
||||
* resources.
|
||||
*
|
||||
* This function will set the DRUID_AUTHORIZATION_CHECKED attribute in the request.
|
||||
*
|
||||
* If this attribute is already set when this function is called, an exception is thrown.
|
||||
*
|
||||
* @param request HTTP request to be authorized
|
||||
* @param resources List of resources to be processed into resource-actions
|
||||
* @param resourceActionGenerator Function that creates a resource-action from a resource
|
||||
* @return A list containing the resource-actions from the resourceParser that were successfully authorized.
|
||||
* @param resources resources to be processed into resource-actions
|
||||
* @param resourceActionGenerator Function that creates an iterable of resource-actions from a resource
|
||||
* @param authorizerMapper authorizer mapper
|
||||
* @return Iterable containing resources that were authorized
|
||||
*
|
||||
*/
|
||||
public static <ResType> List<ResType> filterAuthorizedResources(
|
||||
public static <ResType> Iterable<ResType> filterAuthorizedResources(
|
||||
final HttpServletRequest request,
|
||||
final Collection<ResType> resources,
|
||||
final Function<? super ResType, ResourceAction> resourceActionGenerator,
|
||||
final Iterable<ResType> resources,
|
||||
final Function<? super ResType, Iterable<ResourceAction>> resourceActionGenerator,
|
||||
final AuthorizerMapper authorizerMapper
|
||||
)
|
||||
{
|
||||
if (request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED) != null) {
|
||||
throw new ISE("Request already had authorization check.");
|
||||
}
|
||||
|
||||
final AuthenticationResult authenticationResult = (AuthenticationResult) request.getAttribute(
|
||||
AuthConfig.DRUID_AUTHENTICATION_RESULT
|
||||
);
|
||||
|
@ -185,28 +199,39 @@ public class AuthorizationUtils
|
|||
}
|
||||
|
||||
final Map<ResourceAction, Access> resultCache = Maps.newHashMap();
|
||||
List<ResType> filteredResources = new ArrayList<>();
|
||||
for (ResType resource : resources) {
|
||||
final ResourceAction resourceAction = resourceActionGenerator.apply(resource);
|
||||
Access access = resultCache.computeIfAbsent(
|
||||
resourceAction,
|
||||
ra -> authorizer.authorize(
|
||||
authenticationResult,
|
||||
ra.getResource(),
|
||||
ra.getAction()
|
||||
)
|
||||
);
|
||||
if (access.isAllowed()) {
|
||||
filteredResources.add(resource);
|
||||
}
|
||||
}
|
||||
final Iterable<ResType> filteredResources = Iterables.filter(
|
||||
resources,
|
||||
resource -> {
|
||||
final Iterable<ResourceAction> resourceActions = resourceActionGenerator.apply(resource);
|
||||
if (resourceActions == null) {
|
||||
return false;
|
||||
}
|
||||
for (ResourceAction resourceAction : resourceActions) {
|
||||
Access access = resultCache.computeIfAbsent(
|
||||
resourceAction,
|
||||
ra -> authorizer.authorize(
|
||||
authenticationResult,
|
||||
ra.getResource(),
|
||||
ra.getAction()
|
||||
)
|
||||
);
|
||||
if (!access.isAllowed()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
// We're filtering, so having access to none of the objects isn't an authorization failure (in terms of whether
|
||||
// to send an error response or not.)
|
||||
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
|
||||
|
||||
return filteredResources;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Function for the common pattern of generating a resource-action for reading from a datasource, using the
|
||||
* datasource name.
|
||||
|
|
Loading…
Reference in New Issue