Skip to content

Commit

Permalink
[PLAT-14225] Implement v2 Group Mapping APIs
Browse files Browse the repository at this point in the history
Summary:
Add the following v2 Group Mapping CRUD APIs :
```
GET /customers/:cUUID/auth/group_mappings

Response body:
[
    {
        "group_identifier": "test-group1",
        "uuid": "b0968020-5626-4ae5-b516-d4d77e5302c8",
        "type": "OIDC",
        "role_resource_definitions": [
            {
                "role_uuid": "f3671ea7-6579-4fc0-ba33-f8da45ff19da"
            },
            {
                "role_uuid": "9ce94a1f-3ed7-4a2f-8a6a-bda354d2dd1b",
                "resource_group": {
                    "resource_definition_set": [
                        {
                            "resource_type": "UNIVERSE",
                            "allow_all": true,
                            "resource_uuid_set": []
                        },
                        {
                            "resource_type": "OTHER",
                            "allow_all": false,
                            "resource_uuid_set": [
                                "f33e3c9b-75ab-4c30-80ad-cba85646ea39"
                            ]
                        }
                    ]
                }
            }
        ]
    }
]

PUT v2/customers/:cUUID/auth/group_mappings
Request body same as above.
DELETE /customers/:custUUID/auth/group_mappings/:groupUUID
```

The diff also has migrations and other rbac changes required to add role bindings for Groups.
Please refer to the design doc for complete context - https://docs.google.com/document/d/1qGYu6swY8xWFzqhNWZ6uKHHtKSO9847lm2I0sDB5okU

Test Plan:
Manually tested all changes.
UTs pending.

Reviewers: #yba-api-review, sneelakantan, svarshney, skurapati

Reviewed By: #yba-api-review, sneelakantan

Subscribers: sneelakantan, sanketh, yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D36269
  • Loading branch information
asharma-yb committed Jul 17, 2024
1 parent 7786cf7 commit 95fb188
Show file tree
Hide file tree
Showing 31 changed files with 1,411 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Yugabyte, Inc.

package api.v2.controllers;

import api.v2.handlers.AuthenticationHandler;
import api.v2.models.GroupMappingSpec;
import com.google.inject.Inject;
import java.util.List;
import java.util.UUID;
import play.mvc.Http;

public class AuthenticationApiControllerImp extends AuthenticationApiControllerImpInterface {

@Inject AuthenticationHandler authHandler;

@Override
public List<GroupMappingSpec> listMappings(Http.Request request, UUID cUUID) throws Exception {
return authHandler.listMappings(cUUID);
}

@Override
public void updateGroupMappings(
Http.Request request, UUID cUUID, List<GroupMappingSpec> groupMappingSpec) throws Exception {
authHandler.updateGroupMappings(request, cUUID, groupMappingSpec);
}

@Override
public void deleteGroupMappings(Http.Request request, UUID cUUID, UUID gUUID) throws Exception {
authHandler.deleteGroupMappings(request, cUUID, gUUID);
}
}
181 changes: 181 additions & 0 deletions managed/src/main/java/api/v2/handlers/AuthenticationHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (c) Yugabyte, Inc.

package api.v2.handlers;

import static play.mvc.Http.Status.BAD_REQUEST;
import static play.mvc.Http.Status.NOT_FOUND;

import api.v2.mappers.RoleResourceDefinitionMapper;
import api.v2.models.GroupMappingSpec;
import api.v2.models.GroupMappingSpec.TypeEnum;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.yugabyte.yw.common.PlatformServiceException;
import com.yugabyte.yw.common.config.GlobalConfKeys;
import com.yugabyte.yw.common.config.RuntimeConfGetter;
import com.yugabyte.yw.common.rbac.RoleBindingUtil;
import com.yugabyte.yw.common.rbac.RoleResourceDefinition;
import com.yugabyte.yw.controllers.TokenAuthenticator;
import com.yugabyte.yw.models.GroupMappingInfo;
import com.yugabyte.yw.models.GroupMappingInfo.GroupType;
import com.yugabyte.yw.models.rbac.Role;
import com.yugabyte.yw.models.rbac.RoleBinding;
import com.yugabyte.yw.models.rbac.RoleBinding.RoleBindingType;
import io.ebean.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import play.mvc.Http;

@Slf4j
@Singleton
public class AuthenticationHandler {

@Inject RuntimeConfGetter confGetter;
@Inject TokenAuthenticator tokenAuthenticator;
@Inject RoleBindingUtil roleBindingUtil;

public List<GroupMappingSpec> listMappings(UUID cUUID) throws Exception {

checkRuntimeConfig();

List<GroupMappingInfo> groupInfoList =
GroupMappingInfo.find.query().where().eq("customer_uuid", cUUID).findList();
List<GroupMappingSpec> specList = new ArrayList<GroupMappingSpec>();
for (GroupMappingInfo info : groupInfoList) {
GroupMappingSpec spec =
new GroupMappingSpec()
.groupIdentifier(info.getIdentifier())
.type(TypeEnum.valueOf(info.getType().toString()))
.uuid(info.getGroupUUID());

List<RoleResourceDefinition> roleResourceDefinitions = new ArrayList<>();
if (confGetter.getGlobalConf(GlobalConfKeys.useNewRbacAuthz)) {
// fetch all role rolebindings for the current group
List<RoleBinding> roleBindingList = RoleBinding.getAll(info.getGroupUUID());
for (RoleBinding rb : roleBindingList) {
RoleResourceDefinition roleResourceDefinition =
new RoleResourceDefinition(rb.getRole().getRoleUUID(), rb.getResourceGroup());
roleResourceDefinitions.add(roleResourceDefinition);
}
} else {
// No role bindings present if RBAC is off.
RoleResourceDefinition rrd = new RoleResourceDefinition();
rrd.setRoleUUID(info.getRoleUUID());
roleResourceDefinitions.add(rrd);
}
spec.setRoleResourceDefinitions(
RoleResourceDefinitionMapper.INSTANCE.toV2RoleResourceDefinitionList(
roleResourceDefinitions));
specList.add(spec);
}
return specList;
}

@Transactional
public void updateGroupMappings(
Http.Request request, UUID cUUID, List<GroupMappingSpec> groupMappingSpec) {
boolean isSuperAdmin = tokenAuthenticator.superAdminAuthentication(request);
if (!isSuperAdmin) {
throw new PlatformServiceException(BAD_REQUEST, "Only SuperAdmin can create group mappings!");
}

checkRuntimeConfig();

for (GroupMappingSpec mapping : groupMappingSpec) {
GroupMappingInfo mappingInfo =
GroupMappingInfo.find
.query()
.where()
.eq("customer_uuid", cUUID)
.ieq("identifier", mapping.getGroupIdentifier())
.findOne();

if (mappingInfo == null) {
// new entry for new group
log.info("Adding new group mapping entry for group: " + mapping.getGroupIdentifier());
mappingInfo =
GroupMappingInfo.create(
cUUID,
mapping.getGroupIdentifier(),
GroupType.valueOf(mapping.getType().toString()));
} else {
// clear role bindings for existing group
clearRoleBindings(mappingInfo);
}

List<RoleResourceDefinition> roleResourceDefinitions =
RoleResourceDefinitionMapper.INSTANCE.toV1RoleResourceDefinitionList(
mapping.getRoleResourceDefinitions());

roleBindingUtil.validateRoles(cUUID, roleResourceDefinitions);
roleBindingUtil.validateResourceGroups(cUUID, roleResourceDefinitions);

if (confGetter.getGlobalConf(GlobalConfKeys.useNewRbacAuthz)) {
// Add role bindings if rbac is on.
for (RoleResourceDefinition rrd : roleResourceDefinitions) {
Role rbacRole = Role.getOrBadRequest(cUUID, rrd.getRoleUUID());
RoleBinding.create(mappingInfo, RoleBindingType.Custom, rbacRole, rrd.getResourceGroup());
}
// This role will be ignored when rbac is on.
mappingInfo.setRoleUUID(Role.get(cUUID, "ConnectOnly").getRoleUUID());
} else {
validate(roleResourceDefinitions);
mappingInfo.setRoleUUID(roleResourceDefinitions.get(0).getRoleUUID());
}
mappingInfo.save();
}
}

@Transactional
public void deleteGroupMappings(Http.Request request, UUID cUUID, UUID gUUID) {
boolean isSuperAdmin = tokenAuthenticator.superAdminAuthentication(request);
if (!isSuperAdmin) {
throw new PlatformServiceException(BAD_REQUEST, "Only SuperAdmin can delete group mappings!");
}

checkRuntimeConfig();

GroupMappingInfo entity =
GroupMappingInfo.find
.query()
.where()
.eq("customer_uuid", cUUID)
.eq("uuid", gUUID)
.findOne();
if (entity == null) {
throw new PlatformServiceException(NOT_FOUND, "No group mapping found with uuid: " + gUUID);
}

// Delete all role bindings
clearRoleBindings(entity);
log.info("Deleting Group Mapping with name: " + entity.getIdentifier());
entity.delete();
}

private void clearRoleBindings(GroupMappingInfo mappingInfo) {
log.info("Clearing role bindings for group: " + mappingInfo.getIdentifier());
List<RoleBinding> list = RoleBinding.getAll(mappingInfo.getGroupUUID());
list.forEach(rb -> rb.delete());
}

private void checkRuntimeConfig() {
if (!confGetter.getGlobalConf(GlobalConfKeys.groupMappingRbac)) {
throw new PlatformServiceException(
BAD_REQUEST, "yb.security.group_mapping_rbac_support runtime config is disabled!");
}
}

/**
* Validation to make sure only a single system role is present when RBAC is off.
*
* @param cUUID
* @param rrdList
*/
private void validate(List<RoleResourceDefinition> rrdList) {
if (rrdList.size() != 1) {
throw new PlatformServiceException(BAD_REQUEST, "Need to specify a single system role!");
}
}
}
27 changes: 27 additions & 0 deletions managed/src/main/java/api/v2/mappers/ResourceGroupMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Yugabyte, Inc.

package api.v2.mappers;

import api.v2.models.ResourceDefinitionSpec;
import api.v2.models.ResourceGroupSpec;
import com.yugabyte.yw.models.rbac.ResourceGroup;
import com.yugabyte.yw.models.rbac.ResourceGroup.ResourceDefinition;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(config = CentralConfig.class)
public interface ResourceGroupMapper {
ResourceGroupMapper INSTANCE = Mappers.getMapper(ResourceGroupMapper.class);

ResourceGroupSpec toV2ResourceGroup(ResourceGroup v1ResourceGroup);

ResourceGroup toV1ResourceGroup(ResourceGroupSpec v2ResourceGroup);

@Mapping(target = "resourceUuidSet", source = "resourceUUIDSet")
ResourceDefinitionSpec toV2ResourceDefinitionSpec(ResourceDefinition v1ResourceDefinition);

@InheritInverseConfiguration
ResourceDefinition toV1ResourceDefinitionSpec(ResourceDefinitionSpec v2ResourceDefinition);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Yugabyte, Inc.

package api.v2.mappers;

import api.v2.models.RoleResourceDefinitionSpec;
import com.yugabyte.yw.common.rbac.RoleResourceDefinition;
import java.util.List;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(
config = CentralConfig.class,
uses = {ResourceGroupMapper.class})
public interface RoleResourceDefinitionMapper {
RoleResourceDefinitionMapper INSTANCE = Mappers.getMapper(RoleResourceDefinitionMapper.class);

@Mapping(target = "roleUuid", source = "roleUUID")
RoleResourceDefinitionSpec toV2RoleResourceDefinition(
RoleResourceDefinition v1RoleResourceDefinition);

List<RoleResourceDefinitionSpec> toV2RoleResourceDefinitionList(
List<RoleResourceDefinition> v1RoleResourceDefinition);

@InheritInverseConfiguration
RoleResourceDefinition toV1RoleResourceDefinition(
RoleResourceDefinitionSpec v2RoleResourceDefinitionSpec);

List<RoleResourceDefinition> toV1RoleResourceDefinitionList(
List<RoleResourceDefinitionSpec> v2RoleResourceDefinition);
}
13 changes: 13 additions & 0 deletions managed/src/main/java/com/yugabyte/yw/common/AppInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@
import com.yugabyte.yw.models.ExtraMigration;
import com.yugabyte.yw.models.HighAvailabilityConfig;
import com.yugabyte.yw.models.MetricConfig;
import com.yugabyte.yw.models.Principal;
import com.yugabyte.yw.models.Users;
import com.yugabyte.yw.scheduler.JobScheduler;
import com.yugabyte.yw.scheduler.Scheduler;
import db.migration.default_.common.R__Sync_System_Roles;
import io.ebean.DB;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Gauge;
Expand Down Expand Up @@ -133,6 +136,16 @@ public AppInit(
Customer customer = Customer.getAll().get(0);
alertDestinationService.createDefaultDestination(customer.getUuid());
alertConfigurationService.createDefaultConfigs(customer);
// Create system roles for the newly created customer.
R__Sync_System_Roles.syncSystemRoles();
// Principal entry for newly created users.
for (Users user : Users.find.all()) {
Principal principal = Principal.get(user.getUuid());
if (principal == null) {
log.info("Adding Principal entry for user with email: " + user.getEmail());
new Principal(user).save();
}
}
}

String storagePath = AppConfigHelper.getStoragePath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1414,4 +1414,13 @@ public class GlobalConfKeys extends RuntimeConfigKeysModule {
+ " throwing UNAVAILABLE",
ConfDataType.IntegerType,
ImmutableList.of(ConfKeyTags.BETA));
// Also need rbac to be on for this key to have effect.
public static final ConfKeyInfo<Boolean> groupMappingRbac =
new ConfKeyInfo<>(
"yb.security.group_mapping_rbac_support",
ScopeType.GLOBAL,
"Enable RBAC for Groups",
"Map LDAP/OIDC groups to custom roles defined by RBAC.",
ConfDataType.BooleanType,
ImmutableList.of(ConfKeyTags.INTERNAL));
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public void validateRoles(
if (Users.Role.SuperAdmin.name().equals(role.getName())) {
String errMsg =
String.format(
"Cannot assign SuperAdmin role to a user in the roleResourceDefinition: %s.",
"Cannot assign SuperAdmin role to a user or group in the roleResourceDefinition:"
+ " %s.",
roleResourceDefinition.toString());
log.error(errMsg);
throw new PlatformServiceException(BAD_REQUEST, errMsg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import lombok.Setter;
import lombok.ToString;

@ApiModel(description = "Role and resource group definition.")
@ApiModel(description = "Defines the association of Role to Resource Groups.")
@Getter
@Setter
@AllArgsConstructor
Expand Down
5 changes: 4 additions & 1 deletion managed/src/main/java/com/yugabyte/yw/models/Audit.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ public enum TargetType {
RoleBinding,

@EnumValue("OIDC Group Mapping")
OIDCGroupMapping
OIDCGroupMapping,

@EnumValue("Group Mapping")
GroupMapping
}

public enum ActionType {
Expand Down
Loading

0 comments on commit 95fb188

Please sign in to comment.