Skip to content

Commit

Permalink
[apache#2759] feat(server,core): Add service admin and metalake admin (
Browse files Browse the repository at this point in the history
…apache#2758)

### What changes were proposed in this pull request?
Role in the Gravitino has 3 kinds: service admins, metalake admins,
normal roles.
Service admins are responsible for creating metalake admins. They can't
be changed by API. It is a config option.
Metalake admin is used for creating metalake. It can't be bind to any
metalake. So it's hard to unify it with normal users.
This pull request add the management of service admins and metalake
admins.

### Why are the changes needed?

Fix: apache#2759 

### Does this PR introduce _any_ user-facing change?
Yes, I will add openapi and document.

### How was this patch tested?
New ut.

---------

Co-authored-by: Heng Qin <qqtt@123.com>
  • Loading branch information
2 people authored and unknowntpo committed Apr 9, 2024
1 parent acfc34f commit cf56b3b
Show file tree
Hide file tree
Showing 21 changed files with 738 additions and 37 deletions.
17 changes: 17 additions & 0 deletions core/src/main/java/com/datastrato/gravitino/Configs.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.datastrato.gravitino.config.ConfigEntry;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.List;
import org.apache.commons.lang3.StringUtils;

public interface Configs {
Expand Down Expand Up @@ -239,4 +240,20 @@ public interface Configs {
.version(ConfigConstants.VERSION_0_4_0)
.longConf()
.createWithDefault(CLEAN_INTERVAL_IN_SECS);

ConfigEntry<Boolean> ENABLE_AUTHORIZATION =
new ConfigBuilder("gravitino.authorization.enable")
.doc("Enable the authorization")
.version(ConfigConstants.VERSION_0_5_0)
.booleanConf()
.createWithDefault(false);

ConfigEntry<List<String>> SERVICE_ADMINS =
new ConfigBuilder("gravitino.authorization.serviceAdmins")
.doc("The admins of Gravitino service")
.version(ConfigConstants.VERSION_0_5_0)
.stringConf()
.checkValue(StringUtils::isNotBlank, ConfigConstants.NOT_BLANK_ERROR_MSG)
.toSequence()
.create();
}
22 changes: 22 additions & 0 deletions core/src/main/java/com/datastrato/gravitino/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@
/** This interface defines an entity within the Gravitino framework. */
public interface Entity extends Serializable {

// The below constants are used for virtual metalakes, catalogs and schemas
// The system doesn't need to create them. The system uses these constants
// to organize the system information better.

/** The system reserved metalake name. */
String SYSTEM_METALAKE_RESERVED_NAME = "system";

/** The system reserved catalog name. */
String SYSTEM_CATALOG_RESERVED_NAME = "system";

/** The authorization catalog name in the system metalake. */
String AUTHORIZATION_CATALOG_NAME = "authorization";

/** The user schema name in the system catalog. */
String USER_SCHEMA_NAME = "user";

/** The group schema name in the system catalog. */
String GROUP_SCHEMA_NAME = "group";

/** The admin schema name in the authorization catalog of the system metalake. */
String ADMIN_SCHEMA_NAME = "admin";

/** Enumeration defining the types of entities in the Gravitino framework. */
@Getter
enum EntityType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ public void initialize(Config config) {
new TopicOperationDispatcher(catalogManager, entityStore, idGenerator);

// Create and initialize access control related modules
this.accessControlManager = new AccessControlManager(entityStore, idGenerator);
boolean enableAuthorization = config.get(Configs.ENABLE_AUTHORIZATION);
if (enableAuthorization) {
this.accessControlManager = new AccessControlManager(entityStore, idGenerator, config);
} else {
this.accessControlManager = null;
}

this.auxServiceManager = new AuxiliaryServiceManager();
this.auxServiceManager.serviceInit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package com.datastrato.gravitino.authorization;

import com.datastrato.gravitino.Config;
import com.datastrato.gravitino.EntityStore;
import com.datastrato.gravitino.exceptions.GroupAlreadyExistsException;
import com.datastrato.gravitino.exceptions.NoSuchGroupException;
Expand All @@ -12,15 +13,17 @@
import com.datastrato.gravitino.storage.IdGenerator;

/**
* AccessControlManager is used for manage users, roles, grant information, this class is an
* AccessControlManager is used for manage users, roles, admin, grant information, this class is an
* entrance class for tenant management.
*/
public class AccessControlManager {

private final UserGroupManager userGroupManager;
private final AdminManager adminManager;

public AccessControlManager(EntityStore store, IdGenerator idGenerator) {
public AccessControlManager(EntityStore store, IdGenerator idGenerator, Config config) {
this.userGroupManager = new UserGroupManager(store, idGenerator);
this.adminManager = new AdminManager(store, idGenerator, config);
}

/**
Expand Down Expand Up @@ -98,4 +101,47 @@ public boolean removeGroup(String metalake, String group) {
public Group getGroup(String metalake, String group) throws NoSuchGroupException {
return userGroupManager.getGroup(metalake, group);
}

/**
* Adds a new metalake admin.
*
* @param user The name of the User.
* @return The added User instance.
* @throws UserAlreadyExistsException If a User with the same identifier already exists.
* @throws RuntimeException If adding the User encounters storage issues.
*/
public User addMetalakeAdmin(String user) {
return adminManager.addMetalakeAdmin(user);
}

/**
* Removes a metalake admin.
*
* @param user The name of the User.
* @return `true` if the User was successfully removed, `false` otherwise.
* @throws RuntimeException If removing the User encounters storage issues.
*/
public boolean removeMetalakeAdmin(String user) {
return adminManager.removeMetalakeAdmin(user);
}

/**
* Judges whether the user is the service admin.
*
* @param user the name of the user
* @return true, if the user is service admin, otherwise false.
*/
public boolean isServiceAdmin(String user) {
return adminManager.isServiceAdmin(user);
}

/**
* Judges whether the user is the metalake admin.
*
* @param user the name of the user
* @return true, if the user is metalake admin, otherwise false.
*/
public boolean isMetalakeAdmin(String user) {
return adminManager.isMetalakeAdmin(user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2024 Datastrato Pvt Ltd.
* This software is licensed under the Apache License version 2.
*/
package com.datastrato.gravitino.authorization;

import com.datastrato.gravitino.Config;
import com.datastrato.gravitino.Configs;
import com.datastrato.gravitino.Entity;
import com.datastrato.gravitino.EntityAlreadyExistsException;
import com.datastrato.gravitino.EntityStore;
import com.datastrato.gravitino.NameIdentifier;
import com.datastrato.gravitino.Namespace;
import com.datastrato.gravitino.exceptions.UserAlreadyExistsException;
import com.datastrato.gravitino.meta.AuditInfo;
import com.datastrato.gravitino.meta.UserEntity;
import com.datastrato.gravitino.storage.IdGenerator;
import com.datastrato.gravitino.utils.PrincipalUtils;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* There are two kinds of admin roles in the system: service admin and metalake admin. The service
* admin is configured instead of managing by APIs. It is responsible for creating metalake admin.
* If Gravitino enables authorization, service admin is required. Metalake admin can create a
* metalake or drops its metalake. The metalake admin will be responsible for managing the access
* control. AdminManager operates underlying store using the lock because kv storage needs the lock.
*/
public class AdminManager {

private static final Logger LOG = LoggerFactory.getLogger(AdminManager.class);

private final EntityStore store;
private final IdGenerator idGenerator;
private final List<String> serviceAdmins;

public AdminManager(EntityStore store, IdGenerator idGenerator, Config config) {
this.store = store;
this.idGenerator = idGenerator;
this.serviceAdmins = config.get(Configs.SERVICE_ADMINS);
}

/**
* Adds a new metalake admin.
*
* @param user The name of the User.
* @return The added User instance.
* @throws UserAlreadyExistsException If a User with the same identifier already exists.
* @throws RuntimeException If adding the User encounters storage issues.
*/
public synchronized User addMetalakeAdmin(String user) {

UserEntity userEntity =
UserEntity.builder()
.withId(idGenerator.nextId())
.withName(user)
.withNamespace(
Namespace.of(
Entity.SYSTEM_METALAKE_RESERVED_NAME,
Entity.AUTHORIZATION_CATALOG_NAME,
Entity.ADMIN_SCHEMA_NAME))
.withRoles(Lists.newArrayList())
.withAuditInfo(
AuditInfo.builder()
.withCreator(PrincipalUtils.getCurrentPrincipal().getName())
.withCreateTime(Instant.now())
.build())
.build();
try {
store.put(userEntity, false /* overwritten */);
return userEntity;
} catch (EntityAlreadyExistsException e) {
LOG.warn("User {} in the metalake admin already exists", user, e);
throw new UserAlreadyExistsException("User %s in the metalake admin already exists", user);
} catch (IOException ioe) {
LOG.error("Adding user {} failed to the metalake admin due to storage issues", user, ioe);
throw new RuntimeException(ioe);
}
}

/**
* Removes a metalake admin.
*
* @param user The name of the User.
* @return `true` if the User was successfully removed, `false` otherwise.
* @throws RuntimeException If removing the User encounters storage issues.
*/
public synchronized boolean removeMetalakeAdmin(String user) {
try {
return store.delete(ofMetalakeAdmin(user), Entity.EntityType.USER);
} catch (IOException ioe) {
LOG.error(
"Removing user {} from the metalake admin {} failed due to storage issues", user, ioe);
throw new RuntimeException(ioe);
}
}

/**
* Judges whether the user is the service admin.
*
* @param user the name of the user
* @return true, if the user is service admin, otherwise false.
*/
public boolean isServiceAdmin(String user) {
return serviceAdmins.contains(user);
}

/**
* Judges whether the user is the metalake admin.
*
* @param user the name of the user
* @return true, if the user is metalake admin, otherwise false.
*/
public synchronized boolean isMetalakeAdmin(String user) {
try {
return store.exists(ofMetalakeAdmin(user), Entity.EntityType.USER);
} catch (IOException ioe) {
LOG.error(
"Fail to check whether {} is the metalake admin {} due to storage issues", user, ioe);
throw new RuntimeException(ioe);
}
}

private NameIdentifier ofMetalakeAdmin(String user) {
return NameIdentifier.of(
Entity.SYSTEM_METALAKE_RESERVED_NAME,
Entity.AUTHORIZATION_CATALOG_NAME,
Entity.ADMIN_SCHEMA_NAME,
user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.datastrato.gravitino.exceptions.NoSuchUserException;
import com.datastrato.gravitino.exceptions.UserAlreadyExistsException;
import com.datastrato.gravitino.meta.AuditInfo;
import com.datastrato.gravitino.meta.CatalogEntity;
import com.datastrato.gravitino.meta.GroupEntity;
import com.datastrato.gravitino.meta.UserEntity;
import com.datastrato.gravitino.storage.IdGenerator;
Expand Down Expand Up @@ -65,9 +64,7 @@ public User addUser(String metalake, String name) throws UserAlreadyExistsExcept
.withName(name)
.withNamespace(
Namespace.of(
metalake,
CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME,
UserEntity.USER_SCHEMA_NAME))
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, Entity.USER_SCHEMA_NAME))
.withRoles(Lists.newArrayList())
.withAuditInfo(
AuditInfo.builder()
Expand Down Expand Up @@ -145,9 +142,7 @@ public Group addGroup(String metalake, String group) throws GroupAlreadyExistsEx
.withName(group)
.withNamespace(
Namespace.of(
metalake,
CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME,
GroupEntity.GROUP_SCHEMA_NAME))
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, Entity.GROUP_SCHEMA_NAME))
.withRoles(Collections.emptyList())
.withAuditInfo(
AuditInfo.builder()
Expand Down Expand Up @@ -213,11 +208,11 @@ public Group getGroup(String metalake, String group) {

private NameIdentifier ofUser(String metalake, String user) {
return NameIdentifier.of(
metalake, CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME, UserEntity.USER_SCHEMA_NAME, user);
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, Entity.USER_SCHEMA_NAME, user);
}

private NameIdentifier ofGroup(String metalake, String group) {
return NameIdentifier.of(
metalake, CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME, GroupEntity.GROUP_SCHEMA_NAME, group);
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, Entity.GROUP_SCHEMA_NAME, group);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.datastrato.gravitino.CatalogProvider;
import com.datastrato.gravitino.Config;
import com.datastrato.gravitino.Configs;
import com.datastrato.gravitino.Entity;
import com.datastrato.gravitino.Entity.EntityType;
import com.datastrato.gravitino.EntityAlreadyExistsException;
import com.datastrato.gravitino.EntityStore;
Expand Down Expand Up @@ -287,7 +288,7 @@ public Catalog createCatalog(
Map<String, String> properties)
throws NoSuchMetalakeException, CatalogAlreadyExistsException {

if (CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME.equals(ident.name())) {
if (Entity.SYSTEM_CATALOG_RESERVED_NAME.equals(ident.name())) {
throw new IllegalArgumentException("Can't create a catalog with with reserved name `system`");
}

Expand Down Expand Up @@ -678,7 +679,7 @@ private CatalogEntity.Builder updateEntity(
if (change instanceof CatalogChange.RenameCatalog) {
CatalogChange.RenameCatalog rename = (CatalogChange.RenameCatalog) change;

if (CatalogEntity.SYSTEM_CATALOG_RESERVED_NAME.equals(
if (Entity.SYSTEM_CATALOG_RESERVED_NAME.equals(
((CatalogChange.RenameCatalog) change).getNewName())) {
throw new IllegalArgumentException(
"Can't rename a catalog with with reserved name `system`");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
@ToString
public class CatalogEntity implements Entity, Auditable, HasIdentifier {

public static final String SYSTEM_CATALOG_RESERVED_NAME = "system";

public static final Field ID =
Field.required("id", Long.class, "The catalog's unique identifier");
public static final Field NAME = Field.required("name", String.class, "The catalog's name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

public class GroupEntity implements Group, Entity, Auditable, HasIdentifier {

public static final String GROUP_SCHEMA_NAME = "group";

public static final Field ID =
Field.required("id", Long.class, " The unique id of the group entity.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
@ToString
public class UserEntity implements User, Entity, Auditable, HasIdentifier {

public static final String USER_SCHEMA_NAME = "user";

public static final Field ID =
Field.required("id", Long.class, " The unique id of the user entity.");

Expand Down
Loading

0 comments on commit cf56b3b

Please sign in to comment.