trinodb / trino-gateway

Home Page:https://trinodb.github.io/trino-gateway/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Inconsistent behavior for parsing roles/members from ldap memberOf

andythsu opened this issue · comments

Description:

Scenario 1:

The /userinfo request made from the frontend is coming from this line

@POST
@RolesAllowed("USER")
@Path("userinfo")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response restUserinfo(@Context SecurityContext securityContext)
{
LbPrincipal principal = (LbPrincipal) securityContext.getUserPrincipal();
List<String> roles = List.of(principal.getMemberOf().map(String::toLowerCase).orElse("").split("_"));

and the /findQueryHistory request made from the frontend is coming from this line

@POST
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/findQueryHistory")
public Response findQueryHistory(QueryHistoryRequest query, @Context SecurityContext securityContext)
{
LbPrincipal principal = (LbPrincipal) securityContext.getUserPrincipal();
String[] roles = principal.getMemberOf().orElse("").split("_");

The way that these two lines retrieve user memberOf suggests that memberOf should return something in the form of ADMIN_USER_API. The roles array will store user's roles where each element in the array will be user's different role.

Scenario 2:

the way that principal.getMemberOf() should actually be used is below:

@Override
public boolean authorize(LbPrincipal principal,
String role,
@Nullable ContainerRequestContext ctx)
{
switch (role) {
case "ADMIN":
log.info("User '%s' with memberOf(%s) was identified as ADMIN(%s)",
principal.getName(), principal.getMemberOf(), configuration.getAdmin());
return principal.getMemberOf()
.filter(m -> m.matches(configuration.getAdmin()))
.isPresent();
case "USER":
log.info("User '%s' with memberOf(%s) identified as USER(%s)",
principal.getName(), principal.getMemberOf(), configuration.getUser());
return principal.getMemberOf()
.filter(m -> m.matches(configuration.getUser()))
.isPresent();
case "API":
log.info("User '%s' with memberOf(%s) identified as API(%s)",
principal.getName(), principal.getMemberOf(), configuration.getApi());
return principal.getMemberOf()
.filter(m -> m.matches(configuration.getApi()))
.isPresent();
default:
log.warn("User '%s' with role %s has no regex match based on ldap search",
principal.getName(), role);
return false;
}

This is suggesting that given an operation, and the privilege required to perform the operation, check if user's memberOf regex matches.

For example, if my principal.getMemberOf() returns

memberOf: CN=123, ...
memberOf: CN=456, ...
memberOf: CN=789, ...

as long as I set my configuration to

authorization:
  admin: .*123.*
  user: .*456.*
  api: .*999.*

I'm privileged to perform any admin and user operation in this case because my regex matches

Problem:

Scenario 1 and Scenario 2 have conflicting format requirement for principal.getMemberOf(). Satisfying one will break another.

Solution:

either I misunderstand the flow here, or I can send a PR to fix this issue

Could you check is #310 fixed this issue?

For now it works. Will let it sit for a few more days.

issue is fixed with #310