This is an automated email from the ASF dual-hosted git repository. shuwenwei pushed a commit to branch separationOfAdminPowers in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 48d4912946cb2d0dd2bb1544ee2d5a4eaaab9c83 Author: shuwenwei <[email protected]> AuthorDate: Wed Sep 10 10:21:07 2025 +0800 add admin privileges --- .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 | 11 ++++++++ .../org/apache/iotdb/db/auth/AuthorityChecker.java | 30 +++++++++++++++++--- .../iotdb/db/auth/ClusterAuthorityFetcher.java | 22 ++++++++++----- .../apache/iotdb/db/auth/IAuthorityFetcher.java | 2 +- .../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 10 +++++++ .../db/queryengine/plan/statement/Statement.java | 4 +++ .../conf/iotdb-system.properties.template | 15 ++++++++++ .../iotdb/commons/auth/entity/PrivilegeType.java | 17 +++++++++-- .../org/apache/iotdb/commons/auth/entity/Role.java | 3 +- .../org/apache/iotdb/commons/utils/AuthUtils.java | 33 ++++++++++++++++++++++ .../db/relational/grammar/sql/RelationalSql.g4 | 4 +++ 11 files changed, 136 insertions(+), 15 deletions(-) diff --git a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 index 787412e0812..0054bf779fd 100644 --- a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 +++ b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 @@ -1072,6 +1072,9 @@ PRIVILEGE_VALUE | EXTEND_TEMPLATE | MANAGE_DATABASE | MAINTAIN + | SYSTEM + | SECURITY + | AUDIT ; READ_DATA @@ -1130,6 +1133,14 @@ MAINTAIN : M A I N T A I N ; +SECURITY + : S E C U R I T Y + ; + +AUDIT + : A U D I T + ; + REPAIR : R E P A I R ; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java index 71f064008df..f8693173c8f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java @@ -28,12 +28,14 @@ import org.apache.iotdb.commons.path.PathPatternTree; import org.apache.iotdb.commons.schema.column.ColumnHeader; import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant; import org.apache.iotdb.commons.service.metric.PerformanceOverviewMetrics; +import org.apache.iotdb.commons.utils.AuthUtils; import org.apache.iotdb.confignode.rpc.thrift.TAuthorizerResp; import org.apache.iotdb.confignode.rpc.thrift.TDBPrivilege; import org.apache.iotdb.confignode.rpc.thrift.TPathPrivilege; import org.apache.iotdb.confignode.rpc.thrift.TRoleResp; import org.apache.iotdb.confignode.rpc.thrift.TTablePrivilege; import org.apache.iotdb.confignode.rpc.thrift.TUserResp; +import org.apache.iotdb.db.conf.IoTDBDescriptor; import org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener; import org.apache.iotdb.db.protocol.session.IClientSession; import org.apache.iotdb.db.queryengine.common.header.DatasetHeader; @@ -55,6 +57,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import java.util.stream.Collectors; import static org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.LIST_USER_OR_ROLE_PRIVILEGES_COLUMN_HEADERS; @@ -122,7 +125,11 @@ public class AuthorityChecker { public static TSStatus checkAuthority(Statement statement, IClientSession session) { long startTime = System.nanoTime(); try { - return statement.checkPermissionBeforeProcess(session.getUsername()); + if (IoTDBDescriptor.getInstance().getConfig().isEnableSeparationOfPowers()) { + return statement.checkSeparatedAdminPermissionBeforeProcess(session.getUsername()); + } else { + return statement.checkPermissionBeforeProcess(session.getUsername()); + } } finally { PERFORMANCE_OVERVIEW_METRICS.recordAuthCost(System.nanoTime() - startTime); } @@ -131,7 +138,11 @@ public class AuthorityChecker { public static TSStatus checkAuthority(Statement statement, String userName) { long startTime = System.nanoTime(); try { - return statement.checkPermissionBeforeProcess(userName); + if (IoTDBDescriptor.getInstance().getConfig().isEnableSeparationOfPowers()) { + return statement.checkSeparatedAdminPermissionBeforeProcess(userName); + } else { + return statement.checkPermissionBeforeProcess(userName); + } } finally { PERFORMANCE_OVERVIEW_METRICS.recordAuthCost(System.nanoTime() - startTime); } @@ -154,7 +165,18 @@ public class AuthorityChecker { return hasPermission ? SUCCEED : new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) - .setMessage(NO_PERMISSION_PROMOTION + neededPrivilege); + .setMessage( + NO_PERMISSION_PROMOTION + + getSatisfyAnyNeededPrivilegeString( + AuthUtils.getAllPrivilegesContainingCurrentPrivilege(neededPrivilege))); + } + + private static String getSatisfyAnyNeededPrivilegeString(List<PrivilegeType> privileges) { + StringJoiner sj = new StringJoiner("/"); + for (PrivilegeType privilege : privileges) { + sj.add(privilege.toString()); + } + return sj.toString(); } public static TSStatus getGrantOptTSStatus( @@ -243,7 +265,7 @@ public class AuthorityChecker { } public static boolean checkSystemPermission(String userName, PrivilegeType permission) { - return authorityFetcher.get().checkUserSysPrivileges(userName, permission).getCode() + return authorityFetcher.get().checkUserHasAnySysPrivileges(userName, permission).getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode(); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java index 863a3653263..ad57817ce78 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java @@ -117,14 +117,22 @@ public class ClusterAuthorityFetcher implements IAuthorityFetcher { } @Override - public TSStatus checkUserSysPrivileges(String username, PrivilegeType permission) { + public TSStatus checkUserHasAnySysPrivileges(String username, PrivilegeType... permissions) { checkCacheAvailable(); - return checkPrivilege( - username, - new PrivilegeUnion(permission, false), - (role, union) -> role.checkSysPrivilege(union.getPrivilegeType()), - new TCheckUserPrivilegesReq( - username, PrivilegeModelType.SYSTEM.ordinal(), permission.ordinal(), false)); + TSStatus status; + for (PrivilegeType permission : permissions) { + status = + checkPrivilege( + username, + new PrivilegeUnion(permission, false), + (role, union) -> role.checkSysPrivilege(union.getPrivilegeType()), + new TCheckUserPrivilegesReq( + username, PrivilegeModelType.SYSTEM.ordinal(), permission.ordinal(), false)); + if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + return status; + } + } + return RpcUtils.getStatus(TSStatusCode.NO_PERMISSION); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java index 6b93c211fd6..6fce706d6d4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java @@ -45,7 +45,7 @@ public interface IAuthorityFetcher { TSStatus checkUserPathPrivilegesGrantOpt( String username, List<? extends PartialPath> allPath, PrivilegeType permission); - TSStatus checkUserSysPrivileges(String username, PrivilegeType permission); + TSStatus checkUserHasAnySysPrivileges(String username, PrivilegeType... permissions); TSStatus checkUserDBPrivileges(String username, String database, PrivilegeType permission); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java index ecb18b3c575..66c72113913 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java @@ -929,6 +929,8 @@ public class IoTDBConfig { /** Cache expire time of user and role */ private int authorCacheExpireTime = 30; + private boolean enableSeparationOfPowers = false; + /** Number of queues per forwarding trigger */ private int triggerForwardMaxQueueNumber = 8; @@ -3138,6 +3140,14 @@ public class IoTDBConfig { this.authorCacheExpireTime = authorCacheExpireTime; } + public boolean isEnableSeparationOfPowers() { + return enableSeparationOfPowers; + } + + public void setEnableSeparationOfPowers(boolean enableSeparationOfPowers) { + this.enableSeparationOfPowers = enableSeparationOfPowers; + } + public int getTriggerForwardMaxQueueNumber() { return triggerForwardMaxQueueNumber; } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/Statement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/Statement.java index fc9aa1486a9..6e2cba7a0cc 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/Statement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/Statement.java @@ -68,6 +68,10 @@ public abstract class Statement extends StatementNode { "Only the admin user can perform this operation"); } + public TSStatus checkSeparatedAdminPermissionBeforeProcess(final String userName) { + throw new RuntimeException("Not implemented yet"); + } + public org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Statement toRelationalStatement( final MPPQueryContext context) { throw new UnsupportedOperationException("Method not implemented yet"); diff --git a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index c03f49a3ac3..b0a853d5525 100644 --- a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template +++ b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template @@ -1713,6 +1713,21 @@ author_cache_expire_time=30 # If you want to allow all URIs, you can specify it as .* trusted_uri_pattern=file:.* +# Enable separation of powers +# effectiveMode: first_start +# Datatype: Boolean +enable_separation_of_powers=false + +# The user name of default database security admin +# effectiveMode: first_start +# Datatype: string +dsa_name=iotdb_security + +# The user name of default database auditor admin +# effectiveMode: first_start +# Datatype: string +daa_name=iotdb_audit + #################### ### UDF Configuration #################### diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/PrivilegeType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/PrivilegeType.java index 773a1be38ba..bb455073d49 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/PrivilegeType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/PrivilegeType.java @@ -44,7 +44,11 @@ public enum PrivilegeType { ALTER(PrivilegeModelType.RELATIONAL), SELECT(PrivilegeModelType.RELATIONAL), INSERT(PrivilegeModelType.RELATIONAL), - DELETE(PrivilegeModelType.RELATIONAL); + DELETE(PrivilegeModelType.RELATIONAL), + + SYSTEM(PrivilegeModelType.SYSTEM), + SECURITY(PrivilegeModelType.SYSTEM), + AUDIT(PrivilegeModelType.SYSTEM); private final PrivilegeModelType modelType; @@ -93,7 +97,16 @@ public enum PrivilegeType { } public boolean forRelationalSys() { - return this == MANAGE_USER || this == MANAGE_ROLE; + switch (this) { + case MANAGE_USER: + case MANAGE_ROLE: + case SYSTEM: + case SECURITY: + case AUDIT: + return true; + default: + return false; + } } public PrivilegeModelType getModelType() { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java index 21481de80c4..d798f0085ca 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java @@ -515,7 +515,8 @@ public class Role { } public boolean checkSysPrivilege(PrivilegeType priv) { - return sysPrivilegeSet.contains(priv); + return AuthUtils.getAllPrivilegesContainingCurrentPrivilege(priv).stream() + .anyMatch(sysPrivilegeSet::contains); } public boolean checkSysPriGrantOpt(PrivilegeType priv) { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java index 09824dfbd69..1ee4c8beb46 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java @@ -41,6 +41,8 @@ import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -508,6 +510,12 @@ public class AuthUtils { return PrivilegeType.MAINTAIN; case 9: return PrivilegeType.USE_MODEL; + case 10: + return PrivilegeType.SYSTEM; + case 11: + return PrivilegeType.SECURITY; + case 12: + return PrivilegeType.AUDIT; default: // Not reach here. LOGGER.warn("Not support position"); @@ -537,11 +545,36 @@ public class AuthUtils { return 8; case USE_MODEL: return 9; + case SYSTEM: + return 10; + case SECURITY: + return 11; + case AUDIT: + return 12; default: return -1; } } + public static List<PrivilegeType> getAllPrivilegesContainingCurrentPrivilege(PrivilegeType priv) { + switch (priv) { + case MANAGE_USER: + case MANAGE_ROLE: + return Arrays.asList(priv, PrivilegeType.SECURITY); + case MAINTAIN: + case USE_UDF: + case USE_MODEL: + case USE_TRIGGER: + case USE_CQ: + case USE_PIPE: + case MANAGE_DATABASE: + case EXTEND_TEMPLATE: + return Arrays.asList(priv, PrivilegeType.SYSTEM); + default: + return Collections.singletonList(priv); + } + } + public static int pathPosToPri(int pos) { switch (pos) { case 0: diff --git a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 index 80a869d2644..f7f5517dcc2 100644 --- a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 +++ b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 @@ -761,6 +761,9 @@ objectScope systemPrivilege : MANAGE_USER | MANAGE_ROLE + | SYSTEM + | SECURITY + | AUDIT ; objectPrivilege @@ -1773,6 +1776,7 @@ WRAPPER: 'WRAPPER'; WRITE: 'WRITE'; YEAR: 'YEAR' | 'Y'; ZONE: 'ZONE'; +AUDIT: 'AUDIT'; EQ: '='; NEQ: '<>' | '!=';
