Single auth check for authorized resource filtering (#4818)

* Single auth check for authorized resource filtering

* PR comment

* PR comments
This commit is contained in:
Jonathan Wei 2017-09-19 09:16:08 -07:00 committed by Nishant Bangarwa
parent 00d39ce7a5
commit 3a4a483bb0
5 changed files with 178 additions and 144 deletions

View File

@ -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
)
);
}

View File

@ -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
)
);
}
}

View File

@ -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
);
}

View File

@ -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

View File

@ -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.