Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 337 additions & 0 deletions docs/design/auth-model.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/design/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@ druid.auth.authorizer.<authorizer-name>.name=<authorizer-name>
```

These properties provide the authenticator and authorizer names to the implementations as @JsonProperty parameters, potentially useful when multiple authenticators or authorizers of the same type are configured.

## Defining permissions

Please see [authorization model](../design/auth-model.md).
76 changes: 1 addition & 75 deletions docs/development/extensions-core/druid-basic-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,81 +457,7 @@ Each Authorizer will always have a default "admin" and "druid_system" user with

## Defining permissions

There are two action types in Druid: READ and WRITE

There are three resource types in Druid: DATASOURCE, CONFIG, and STATE.

### DATASOURCE
Resource names for this type are datasource names. Specifying a datasource permission allows the administrator to grant users access to specific datasources.

### CONFIG
There are two possible resource names for the "CONFIG" resource type, "CONFIG" and "security". Granting a user access to CONFIG resources allows them to access the following endpoints.

"CONFIG" resource name covers the following endpoints:

|Endpoint|Process Type|
|--------|---------|
|`/druid/coordinator/v1/config`|coordinator|
|`/druid/indexer/v1/worker`|overlord|
|`/druid/indexer/v1/worker/history`|overlord|
|`/druid/worker/v1/disable`|middleManager|
|`/druid/worker/v1/enable`|middleManager|

"security" resource name covers the following endpoint:

|Endpoint|Process Type|
|--------|---------|
|`/druid-ext/basic-security/authentication`|coordinator|
|`/druid-ext/basic-security/authorization`|coordinator|

### STATE
There is only one possible resource name for the "STATE" config resource type, "STATE". Granting a user access to STATE resources allows them to access the following endpoints.

"STATE" resource name covers the following endpoints:

|Endpoint|Process Type|
|--------|---------|
|`/druid/coordinator/v1`|coordinator|
|`/druid/coordinator/v1/rules`|coordinator|
|`/druid/coordinator/v1/rules/history`|coordinator|
|`/druid/coordinator/v1/servers`|coordinator|
|`/druid/coordinator/v1/tiers`|coordinator|
|`/druid/broker/v1`|broker|
|`/druid/v2/candidates`|broker|
|`/druid/indexer/v1/leader`|overlord|
|`/druid/indexer/v1/isLeader`|overlord|
|`/druid/indexer/v1/action`|overlord|
|`/druid/indexer/v1/workers`|overlord|
|`/druid/indexer/v1/scaling`|overlord|
|`/druid/worker/v1/enabled`|middleManager|
|`/druid/worker/v1/tasks`|middleManager|
|`/druid/worker/v1/task/{taskid}/shutdown`|middleManager|
|`/druid/worker/v1/task/{taskid}/log`|middleManager|
|`/druid/historical/v1`|historical|
|`/druid-internal/v1/segments/`|historical|
|`/druid-internal/v1/segments/`|peon|
|`/druid-internal/v1/segments/`|realtime|
|`/status`|all process types|

### HTTP methods

For information on what HTTP methods are supported on a particular request endpoint, please refer to the [API documentation](../../operations/api-reference.md).

GET requires READ permission, while POST and DELETE require WRITE permission.

### SQL Permissions

Queries on Druid datasources require DATASOURCE READ permissions for the specified datasource.

Queries on the [INFORMATION_SCHEMA tables](../../querying/sql.html#information-schema) will
return information about datasources that the caller has DATASOURCE READ access to. Other
datasources will be omitted.

Queries on the [system schema tables](../../querying/sql.html#system-schema) require the following permissions:
- `segments`: Segments will be filtered based on DATASOURCE READ permissions.
- `servers`: The user requires STATE READ permissions.
- `server_segments`: The user requires STATE READ permissions and segments will be filtered based on DATASOURCE READ permissions.
- `tasks`: Tasks will be filtered based on DATASOURCE READ permissions.
Please see [authorization model](../../design/auth-model.md).

## Configuration Propagation

Expand Down
2 changes: 1 addition & 1 deletion docs/querying/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -1221,5 +1221,5 @@ Druid SQL planning occurs on the Broker and is configured by

## Security

Please see [Defining SQL permissions](../development/extensions-core/druid-basic-security.html#sql-permissions) in the
Please see [Defining SQL permissions](../design/auth-model.html#sql-permissions) in the
basic security documentation for information on what permissions are needed for making SQL queries.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.server.http.security.AbstractResourceFilter;
import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.Resource;
Expand All @@ -33,6 +34,7 @@
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

@Deprecated
public class BasicSecurityResourceFilter extends AbstractResourceFilter
{
private static final String SECURITY_RESOURCE_NAME = "security";
Expand All @@ -48,25 +50,26 @@ public BasicSecurityResourceFilter(
@Override
public ContainerRequest filter(ContainerRequest request)
{
final ResourceAction resourceAction = new ResourceAction(
new Resource(SECURITY_RESOURCE_NAME, ResourceType.CONFIG),
getAction(request)
);

final Access authResult = AuthorizationUtils.authorizeResourceAction(
getReq(),
resourceAction,
getAuthorizerMapper()
);
if (getAuthVersion().equals(AuthConfig.AUTH_VERSION_1)) {
final ResourceAction resourceAction = new ResourceAction(
new Resource(SECURITY_RESOURCE_NAME, ResourceType.CONFIG),
getAction(request)
);

if (!authResult.isAllowed()) {
throw new WebApplicationException(
Response.status(Response.Status.FORBIDDEN)
.entity(StringUtils.format("Access-Check-Result: %s", authResult.toString()))
.build()
final Access authResult = AuthorizationUtils.authorizeResourceAction(
getReq(),
resourceAction,
getAuthorizerMapper()
);
}

if (!authResult.isAllowed()) {
throw new WebApplicationException(
Response.status(Response.Status.FORBIDDEN)
.entity(StringUtils.format("Access-Check-Result: %s", authResult.toString()))
.build()
);
}
}
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.security.basic.BasicSecurityResourceFilter;
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
import org.apache.druid.server.http.security.ServerServerResourceFilter;
import org.apache.druid.server.security.AuthValidator;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -65,7 +66,7 @@ public BasicAuthenticatorResource(
@Path("/loadStatus")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response getLoadStatus(
@Context HttpServletRequest req
)
Expand All @@ -83,7 +84,7 @@ public Response getLoadStatus(
@Path("/refreshAll")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response refreshAll(
@Context HttpServletRequest req
)
Expand All @@ -100,7 +101,7 @@ public Response refreshAll(
@Path("/db/{authenticatorName}/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response getAllUsers(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName
Expand All @@ -120,7 +121,7 @@ public Response getAllUsers(
@Path("/db/{authenticatorName}/users/{userName}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response getUser(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName,
Expand All @@ -143,7 +144,7 @@ public Response getUser(
@Path("/db/{authenticatorName}/users/{userName}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response createUser(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName,
Expand All @@ -166,7 +167,7 @@ public Response createUser(
@Path("/db/{authenticatorName}/users/{userName}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response deleteUser(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName,
Expand All @@ -189,7 +190,7 @@ public Response deleteUser(
@Path("/db/{authenticatorName}/users/{userName}/credentials")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response updateUserCredentials(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName,
Expand All @@ -210,7 +211,7 @@ public Response updateUserCredentials(
@Path("/db/{authenticatorName}/cachedSerializedUserMap")
@Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response getCachedSerializedUserMap(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName
Expand All @@ -227,7 +228,7 @@ public Response getCachedSerializedUserMap(
@Path("/listen/{authenticatorName}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ResourceFilters(BasicSecurityResourceFilter.class)
@ResourceFilters({ BasicSecurityResourceFilter.class, ServerServerResourceFilter.class })
public Response authenticatorUpdateListener(
@Context HttpServletRequest req,
@PathParam("authenticatorName") final String authenticatorName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ public Access authorize(AuthenticationResult authenticationResult, Resource reso
return new Access(false);
}

@Override
public Access authorizeV2(AuthenticationResult authenticationResult, Resource resource, Action action)
{
// its same implementation as with v2 permissions for roles will be changed in the metadata store
// checking logic will remain the same
return this.authorize(authenticationResult, resource, action);
}

private boolean permissionCheck(Resource resource, Action action, BasicAuthorizerPermission permission)
{
if (action != permission.getResourceAction().getAction()) {
Expand All @@ -121,7 +129,7 @@ private boolean permissionCheck(Resource resource, Action action, BasicAuthorize
}

Pattern resourceNamePattern = permission.getResourceNamePattern();
Matcher resourceNameMatcher = resourceNamePattern.matcher(resource.getName());
Matcher resourceNameMatcher = resourceNamePattern.matcher(resource.getName().toString());
return resourceNameMatcher.matches();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
import org.apache.druid.server.security.Action;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.server.security.Authorizer;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.Resource;
Expand Down Expand Up @@ -86,7 +87,9 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
private static final String GROUP_MAPPINGS = "groupMappings";
private static final String ROLES = "roles";

@Deprecated
public static final List<ResourceAction> SUPERUSER_PERMISSIONS = makeSuperUserPermissions();
public static final List<ResourceAction> SUPERUSER_PERMISSIONS_V2 = makeSuperUserPermissionsV2();

private final AuthorizerMapper authorizerMapper;
private final MetadataStorageConnector connector;
Expand Down Expand Up @@ -175,7 +178,8 @@ public void start()
initSuperUsersAndGroupMapping(authorizerName, userMap, roleMap, groupMappingMap,
dbConfig.getInitialAdminUser(),
dbConfig.getInitialAdminRole(),
dbConfig.getInitialAdminGroupMapping()
dbConfig.getInitialAdminGroupMapping(),
authorizerMapper.getAuthVersion()
);
}
}
Expand Down Expand Up @@ -1138,17 +1142,26 @@ private void initSuperUsersAndGroupMapping(
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
String initialAdminUser,
String initialAdminRole,
String initialAdminGroupMapping
String initialAdminGroupMapping,
String authVersion
)
{
if (!roleMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
createRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
setPermissionsInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, SUPERUSER_PERMISSIONS);
setPermissionsInternal(
authorizerName,
BasicAuthUtils.ADMIN_NAME,
authVersion.equals(AuthConfig.AUTH_VERSION_2) ? SUPERUSER_PERMISSIONS_V2 : SUPERUSER_PERMISSIONS
);
}

if (!roleMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
createRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
setPermissionsInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, SUPERUSER_PERMISSIONS);
setPermissionsInternal(
authorizerName,
BasicAuthUtils.INTERNAL_USER_NAME,
authVersion.equals(AuthConfig.AUTH_VERSION_2) ? SUPERUSER_PERMISSIONS_V2 : SUPERUSER_PERMISSIONS
);
}

if (!userMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
Expand All @@ -1165,7 +1178,11 @@ private void initSuperUsersAndGroupMapping(
&& !(initialAdminRole.equals(BasicAuthUtils.ADMIN_NAME) || initialAdminRole.equals(BasicAuthUtils.INTERNAL_USER_NAME))
&& !roleMap.containsKey(initialAdminRole)) {
createRoleInternal(authorizerName, initialAdminRole);
setPermissionsInternal(authorizerName, initialAdminRole, SUPERUSER_PERMISSIONS);
setPermissionsInternal(
authorizerName,
initialAdminRole,
authVersion.equals(AuthConfig.AUTH_VERSION_2) ? SUPERUSER_PERMISSIONS_V2 : SUPERUSER_PERMISSIONS
);
}

if (initialAdminUser != null
Expand All @@ -1186,6 +1203,7 @@ private void initSuperUsersAndGroupMapping(
}
}

@Deprecated
private static List<ResourceAction> makeSuperUserPermissions()
{
ResourceAction datasourceR = new ResourceAction(
Expand Down Expand Up @@ -1220,4 +1238,49 @@ private static List<ResourceAction> makeSuperUserPermissions()

return Lists.newArrayList(datasourceR, datasourceW, configR, configW, stateR, stateW);
}

private static List<ResourceAction> makeSuperUserPermissionsV2()
{
ResourceAction datasourceR = new ResourceAction(
new Resource(".*", ResourceType.DATASOURCE),
Action.READ
);

ResourceAction datasourceW = new ResourceAction(
new Resource(".*", ResourceType.DATASOURCE),
Action.WRITE
);

ResourceAction internalR = new ResourceAction(
new Resource(".*", ResourceType.INTERNAL),
Action.READ
);

ResourceAction internalW = new ResourceAction(
new Resource(".*", ResourceType.INTERNAL),
Action.WRITE
);

ResourceAction lookupR = new ResourceAction(
new Resource(".*", ResourceType.LOOKUP),
Action.READ
);

ResourceAction lookupW = new ResourceAction(
new Resource(".*", ResourceType.LOOKUP),
Action.WRITE
);

ResourceAction serverR = new ResourceAction(
new Resource(".*", ResourceType.SERVER),
Action.READ
);

ResourceAction serverW = new ResourceAction(
new Resource(".*", ResourceType.SERVER),
Action.WRITE
);

return Lists.newArrayList(datasourceR, datasourceW, internalR, internalW, lookupR, lookupW, serverR, serverW);
}
}
Loading