diff --git a/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java b/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java index 4f1217b6f66db..c8fed4130c20f 100644 --- a/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java +++ b/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java @@ -45,7 +45,7 @@ public class StatisticsUpdaterTrigger implements Trigger { private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsUpdaterTrigger.class); - private static final String TARGET_DEVICE = "root.__system.statistics"; + private static final String TARGET_DEVICE = "root.statistics"; private static final String TARGET_SERIES = "total_count"; private String ip; 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 53c5da0ddc415..ccf79c782405a 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 @@ -226,6 +226,7 @@ public static TSStatus checkAuthority(Statement statement, IAuditEntity auditEnt auditEntity.getUserId(), auditEntity.getUsername(), auditEntity.getCliHostname()) + .setInternal(auditEntity.isInternal()) .setSqlString(auditEntity.getSqlString())); } return status; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java index 5d3bce4e4c32b..1f5f95b446020 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java @@ -3557,12 +3557,13 @@ public TSStatus insertRecord(TSInsertRecordReq req) throws TException { InsertRowStatement statement = StatementGenerator.createStatement(req); SessionInfo sessionInfo = new SessionInfo( - 0, - new UserEntity( - AuthorityChecker.SUPER_USER_ID, - AuthorityChecker.SUPER_USER, - IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), - ZoneId.systemDefault()); + 0, + new UserEntity( + AuthorityChecker.SUPER_USER_ID, + AuthorityChecker.SUPER_USER, + IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), + ZoneId.systemDefault()) + .setInternal(true); long queryId = SESSION_MANAGER.requestQueryId(); ExecutionResult result = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java index 311bf04322ad4..d609c5c8e028e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java @@ -1086,6 +1086,11 @@ public String getUsername() { return session.getUserName(); } + @Override + public boolean isInternal() { + return session != null && session.isInternal(); + } + @Override public String getCliHostname() { if (session == null || session.getCliHostname() == null) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java index a1d5dc7a27f9e..95bf157035002 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java @@ -20,7 +20,9 @@ package org.apache.iotdb.db.queryengine.plan.analyze; import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet; +import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot; +import org.apache.iotdb.commons.audit.IAuditEntity; import org.apache.iotdb.commons.exception.IoTDBException; import org.apache.iotdb.commons.exception.IoTDBRuntimeException; import org.apache.iotdb.commons.exception.SemanticException; @@ -56,6 +58,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaFetcher; import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.ExpressionRewriter; import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.ExpressionTreeRewriter; +import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TimeRange; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertBaseStatement; @@ -168,7 +171,9 @@ public static InsertBaseStatement analyzeInsert( validateSchema(analysis, insertBaseStatement, schemaValidation); InsertBaseStatement realStatement = - removeLogicalView ? removeLogicalView(analysis, insertBaseStatement) : insertBaseStatement; + removeLogicalView + ? removeLogicalView(analysis, insertBaseStatement, context) + : insertBaseStatement; if (analysis.isFinishQueryAfterAnalyze()) { return realStatement; } @@ -343,9 +348,19 @@ public static void validateSchema( } public static InsertBaseStatement removeLogicalView( - IAnalysis analysis, InsertBaseStatement insertBaseStatement) { + IAnalysis analysis, InsertBaseStatement insertBaseStatement, IAuditEntity context) { try { - return insertBaseStatement.removeLogicalView(); + final InsertBaseStatement realStatement = insertBaseStatement.removeLogicalView(); + // Inserts into a logical view are rewritten to the view's source series. Check the real + // target paths so a view created before this restriction cannot write root.__system. + final TSStatus systemDatabaseStatus = + TreeAccessCheckVisitor.checkSystemDatabaseWrite( + context, realStatement.getDevicePathsStream().toList()); + if (systemDatabaseStatus != null) { + analysis.setFinishQueryAfterAnalyze(true); + analysis.setFailStatus(systemDatabaseStatus); + } + return realStatement; } catch (SemanticException e) { analysis.setFinishQueryAfterAnalyze(true); if (e.getCause() instanceof IoTDBException) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java index c9a422dec9849..5d42cde585966 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java @@ -87,6 +87,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.GroupByVariationParameter; import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.IntoPathDescriptor; import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.OrderByParameter; +import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor; import org.apache.iotdb.db.queryengine.plan.statement.StatementNode; import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor; import org.apache.iotdb.db.queryengine.plan.statement.component.FillComponent; @@ -2903,7 +2904,8 @@ public Analysis visitInsertRow(InsertRowStatement insertRowStatement, MPPQueryCo analysis, insertRowStatement, () -> SchemaValidator.validate(schemaFetcher, insertRowStatement, context)); - InsertBaseStatement realInsertStatement = removeLogicalView(analysis, insertRowStatement); + InsertBaseStatement realInsertStatement = + removeLogicalView(analysis, insertRowStatement, context); if (analysis.isFinishQueryAfterAnalyze()) { return analysis; } @@ -2943,7 +2945,7 @@ public Analysis visitInsertRows( insertRowsStatement, () -> SchemaValidator.validate(schemaFetcher, insertRowsStatement, context)); InsertRowsStatement realInsertRowsStatement = - (InsertRowsStatement) removeLogicalView(analysis, insertRowsStatement); + (InsertRowsStatement) removeLogicalView(analysis, insertRowsStatement, context); if (analysis.isFinishQueryAfterAnalyze()) { return analysis; } @@ -2968,7 +2970,8 @@ public Analysis visitInsertMultiTablets( insertMultiTabletsStatement, () -> SchemaValidator.validate(schemaFetcher, insertMultiTabletsStatement, context)); InsertMultiTabletsStatement realStatement = - (InsertMultiTabletsStatement) removeLogicalView(analysis, insertMultiTabletsStatement); + (InsertMultiTabletsStatement) + removeLogicalView(analysis, insertMultiTabletsStatement, context); if (analysis.isFinishQueryAfterAnalyze()) { return analysis; } @@ -2993,7 +2996,7 @@ public Analysis visitInsertRowsOfOneDevice( insertRowsOfOneDeviceStatement, () -> SchemaValidator.validate(schemaFetcher, insertRowsOfOneDeviceStatement, context)); InsertBaseStatement realInsertStatement = - removeLogicalView(analysis, insertRowsOfOneDeviceStatement); + removeLogicalView(analysis, insertRowsOfOneDeviceStatement, context); if (analysis.isFinishQueryAfterAnalyze()) { return analysis; } @@ -3725,6 +3728,15 @@ public Analysis visitDeleteData( } } deleteDataStatement.setPathList(new ArrayList<>(deletePatternSet)); + // Deletes through a writable view are rewritten to the view's source series as well. + final TSStatus systemDatabaseStatus = + TreeAccessCheckVisitor.checkSystemDatabaseWrite( + context, deleteDataStatement.getPathList()); + if (systemDatabaseStatus != null) { + analysis.setFinishQueryAfterAnalyze(true); + analysis.setFailStatus(systemDatabaseStatus); + return analysis; + } } else { for (PartialPath devicePattern : patternTree.getAllDevicePaths()) { schemaTree diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java index fe8695c859d8b..2c5836c96c8f6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java @@ -567,8 +567,7 @@ private List executeInternalCreateTimeSeriesStatement( private void internalActivateTemplate(PartialPath devicePath, MPPQueryContext context) { ActivateTemplateStatement statement = new ActivateTemplateStatement(devicePath); - TSStatus status = - AuthorityChecker.checkAuthority(statement, context.getSession().getUserEntity()); + TSStatus status = AuthorityChecker.checkAuthority(statement, context); if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { throw new IoTDBRuntimeException(status.getMessage(), status.getCode()); } @@ -585,8 +584,7 @@ private void internalActivateTemplate( MPPQueryContext context) { InternalBatchActivateTemplateStatement statement = new InternalBatchActivateTemplateStatement(devicesNeedActivateTemplate); - TSStatus status = - AuthorityChecker.checkAuthority(statement, context.getSession().getUserEntity()); + TSStatus status = AuthorityChecker.checkAuthority(statement, context); if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { throw new IoTDBRuntimeException(status.getMessage(), status.getCode()); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java index 0db343b44d9ac..d0f847878a297 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java @@ -606,6 +606,12 @@ public TSStatus checkFullPathWriteDataPermission( IAuditEntity auditEntity, IDeviceID device, String measurementId) { try { PartialPath path = new MeasurementPath(device, measurementId); + final TSStatus systemDatabaseStatus = + TreeAccessCheckVisitor.checkSystemDatabaseWrite( + auditEntity, Collections.singletonList(path)); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } // Apache IoTDB does not support external writes to the audit database. if (includeByAuditTreeDB(path) && !auditEntity.getUsername().equals(AuthorityChecker.INTERNAL_AUDIT_USER)) { @@ -633,6 +639,20 @@ public TSStatus checkCanAlterTemplate(IAuditEntity entity, Supplier audi @Override public TSStatus checkCanAlterView( IAuditEntity entity, List sourcePaths, List targetPaths) { + if (sourcePaths != null) { + final TSStatus sourceSystemDatabaseStatus = + TreeAccessCheckVisitor.checkSystemDatabaseWrite(entity, sourcePaths); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } + if (targetPaths != null) { + final TSStatus targetSystemDatabaseStatus = + TreeAccessCheckVisitor.checkSystemDatabaseWrite(entity, targetPaths); + if (targetSystemDatabaseStatus != null) { + return targetSystemDatabaseStatus; + } + } if (AuthorityChecker.SUPER_USER_ID == entity.getUserId()) { DNAuditLogger.getInstance() .recordObjectAuthenticationAuditLog( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java index 5dbb91cdbe6aa..ac09431fc2caf 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java @@ -32,6 +32,7 @@ public class TreeAccessCheckContext implements IAuditEntity { private final long userId; private final String username; private final String cliHostname; + private boolean internal; public TreeAccessCheckContext(long userId, String username, String cliHostname) { this.userId = userId; @@ -54,6 +55,16 @@ public String getCliHostname() { return cliHostname; } + @Override + public boolean isInternal() { + return internal; + } + + public TreeAccessCheckContext setInternal(final boolean internal) { + this.internal = internal; + return this; + } + private AuditEventType auditEventType; private AuditLogOperation auditLogOperation; private List privilegeTypeList; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java index ae1ba59837923..c1945f9d728e4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java @@ -30,6 +30,7 @@ import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.path.PathPatternTree; import org.apache.iotdb.commons.path.PathPatternTreeUtils; +import org.apache.iotdb.commons.schema.InternalDatabaseUtils; import org.apache.iotdb.commons.schema.SchemaConstant; import org.apache.iotdb.commons.schema.table.Audit; import org.apache.iotdb.commons.utils.StatusUtils; @@ -180,6 +181,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -278,6 +280,12 @@ public TSStatus visitCreateSchemaTemplate( public TSStatus visitSetSchemaTemplate( SetSchemaTemplateStatement setSchemaTemplateStatement, TreeAccessCheckContext context) { context.setAuditLogOperation(AuditLogOperation.DDL); + final TSStatus systemDatabaseStatus = + checkSystemDatabaseWrite( + context, Collections.singletonList(setSchemaTemplateStatement.getPath())); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } // root.__audit can never be set template TSStatus status = checkUnsupportedAuditDatabaseWrite( @@ -379,6 +387,11 @@ public TSStatus visitDeactivateTemplate( @Override public TSStatus visitUnsetSchemaTemplate( UnsetSchemaTemplateStatement unsetSchemaTemplateStatement, TreeAccessCheckContext context) { + final TSStatus systemDatabaseStatus = + checkSystemDatabaseWrite(context, unsetSchemaTemplateStatement.getPaths()); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } return checkSystemAuth( context.setAuditLogOperation(AuditLogOperation.DDL), () -> unsetSchemaTemplateStatement.getPaths().toString()); @@ -437,6 +450,34 @@ public TSStatus visitCreateLogicalView( .setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE)); } } + final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, paths); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } + // A logical view redirects inserts and deletes to its source series during analysis, so the + // source paths must be checked here as well. + if (statement.getSourcePaths().fullPathList != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite(context, statement.getSourcePaths().fullPathList); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } + if (statement.getSourcePaths().prefixOfPathsGroup != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite( + context, Collections.singletonList(statement.getSourcePaths().prefixOfPathsGroup)); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } + if (statement.getQueryStatement() != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite(context, statement.getQueryStatement().getPaths()); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) { statement.setCanSeeAuditDB(true); @@ -496,6 +537,35 @@ public TSStatus visitShowLogicalView( public TSStatus visitAlterLogicalView( AlterLogicalViewStatement statement, TreeAccessCheckContext context) { context.setAuditLogOperation(AuditLogOperation.DDL); + final List targetPathList = statement.getTargetPathList(); + if (targetPathList != null) { + final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, targetPathList); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } + } + if (statement.getSourcePaths().fullPathList != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite(context, statement.getSourcePaths().fullPathList); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } + if (statement.getSourcePaths().prefixOfPathsGroup != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite( + context, Collections.singletonList(statement.getSourcePaths().prefixOfPathsGroup)); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } + if (statement.getQueryStatement() != null) { + final TSStatus sourceSystemDatabaseStatus = + checkSystemDatabaseWrite(context, statement.getQueryStatement().getPaths()); + if (sourceSystemDatabaseStatus != null) { + return sourceSystemDatabaseStatus; + } + } if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) { statement.setCanSeeAuditDB(true); if (statement.getQueryStatement() != null) { @@ -1060,6 +1130,18 @@ && isAuditTreeDatabase(statement.getDatabasePath())) { return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) .setMessage(getReservedDatabaseNameErrorMsg(TREE_MODEL_AUDIT_DATABASE)); } + // The system database is created by the database itself, not through SQL. + if (!context.isInternal() + && InternalDatabaseUtils.isSystemDatabasePath(statement.getDatabasePath())) { + context + .setDatabase(statement.getDatabasePath().getFullPath()) + .setPrivilegeType(PrivilegeType.MANAGE_DATABASE) + .setAuditLogOperation(AuditLogOperation.DDL); + AUDIT_LOGGER.recordObjectAuthenticationAuditLog( + context.setResult(false), statement.getDatabasePath()::getFullPath); + return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) + .setMessage(getReservedDatabaseNameErrorMsg(SchemaConstant.SYSTEM_DATABASE)); + } return checkCreateOrAlterDatabasePermission( context.setAuditLogOperation(AuditLogOperation.DDL), statement.getDatabasePath()); } @@ -1067,6 +1149,12 @@ && isAuditTreeDatabase(statement.getDatabasePath())) { @Override public TSStatus visitAlterDatabase( DatabaseSchemaStatement databaseSchemaStatement, TreeAccessCheckContext context) { + final TSStatus systemDatabaseStatus = + checkSystemDatabaseWrite( + context, Collections.singletonList(databaseSchemaStatement.getDatabasePath())); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } return checkCreateOrAlterDatabasePermission( context.setAuditLogOperation(AuditLogOperation.DDL), databaseSchemaStatement.getDatabasePath()); @@ -1116,6 +1204,14 @@ public TSStatus visitDeleteDatabase( context .setAuditLogOperation(AuditLogOperation.DDL) .setDatabase(statement.getPrefixPath().toString()); + if (!context.isInternal() + && SchemaConstant.SYSTEM_DATABASE.equals(String.join(".", statement.getPrefixPath()))) { + AUDIT_LOGGER.recordObjectAuthenticationAuditLog( + context.setPrivilegeType(PrivilegeType.MANAGE_DATABASE).setResult(false), + () -> String.join(".", statement.getPrefixPath())); + return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) + .setMessage(getUnsupportedAuditDatabaseOperationMessage(SchemaConstant.SYSTEM_DATABASE)); + } for (String prefixPath : statement.getPrefixPath()) { // root.__audit can never be deleted if (TREE_MODEL_AUDIT_DATABASE.equals(prefixPath)) { @@ -1183,6 +1279,27 @@ private TSStatus checkShowOrCountDatabasePermission( } // ==================================== data related ======================================== + /** + * Rejects writes to {@code root.__system} from sessions that are not dedicated internal writers. + * Callers must pass the paths that will actually be written, including the source paths of a + * writable view, because analysis rewrites inserts and deletes to those source series. + */ + public static TSStatus checkSystemDatabaseWrite( + final IAuditEntity context, final Collection checkedPaths) { + if (context.isInternal()) { + return null; + } + for (PartialPath path : checkedPaths) { + if (InternalDatabaseUtils.isSystemDatabasePath(path)) { + AUDIT_LOGGER.recordObjectAuthenticationAuditLog(context.setResult(false), path::toString); + return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) + .setMessage( + getUnsupportedAuditDatabaseOperationMessage(SchemaConstant.SYSTEM_DATABASE)); + } + } + return null; + } + @Override public TSStatus visitInsertBase(InsertBaseStatement statement, TreeAccessCheckContext context) { context.setAuditLogOperation(AuditLogOperation.DML).setPrivilegeType(PrivilegeType.WRITE_DATA); @@ -1201,6 +1318,11 @@ public TSStatus visitInsertBase(InsertBaseStatement statement, TreeAccessCheckCo return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) .setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE)); } + final TSStatus unsupportedSystemStatus = + checkSystemDatabaseWrite(context, statement.getDevicePathsStream().toList()); + if (unsupportedSystemStatus != null) { + return unsupportedSystemStatus; + } if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) { AUDIT_LOGGER.recordObjectAuthenticationAuditLog( @@ -1301,6 +1423,13 @@ private static TSStatus checkTimeSeriesPermission( PrivilegeType permission, Function, String> auditObjectFormatter) { context.setPrivilegeType(permission); + if (permission == PrivilegeType.WRITE_SCHEMA || permission == PrivilegeType.WRITE_DATA) { + final TSStatus systemDatabaseStatus = + checkSystemDatabaseWrite(context, checkedPathsSupplier.get()); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } + } if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) { AUDIT_LOGGER.recordObjectAuthenticationAuditLog( context.setResult(true), () -> auditObjectFormatter.apply(checkedPathsSupplier.get())); @@ -1760,6 +1889,11 @@ public TSStatus visitDeleteTimeSeries( .setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE)); } } + final TSStatus systemDatabaseStatus = + checkSystemDatabaseWrite(context, statement.getPathPatternList()); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } return checkTimeSeriesPermission(context, statement::getPaths, PrivilegeType.WRITE_SCHEMA); } @@ -2068,6 +2202,10 @@ public TSStatus visitLoadConfiguration( public TSStatus visitSetTTL(SetTTLStatement statement, TreeAccessCheckContext context) { context.setPrivilegeType(PrivilegeType.SYSTEM).setAuditLogOperation(AuditLogOperation.DDL); List checkedPaths = statement.getPaths(); + final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, checkedPaths); + if (systemDatabaseStatus != null) { + return systemDatabaseStatus; + } boolean[] pathsNotEndWithMultiLevelWildcard = null; for (int i = 0; i < checkedPaths.size(); i++) { PartialPath checkedPath = checkedPaths.get(i); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java index ad13233dbac28..5749d99b66ff7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java @@ -97,12 +97,13 @@ public IoTDBInternalLocalReporter() { Executors.newSingleThreadScheduledExecutor(), Coordinator.getInstance(), new SessionInfo( - 0, - new UserEntity( - AuthorityChecker.SUPER_USER_ID, - AuthorityChecker.SUPER_USER, - IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), - ZoneId.systemDefault()), + 0, + new UserEntity( + AuthorityChecker.SUPER_USER_ID, + AuthorityChecker.SUPER_USER, + IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), + ZoneId.systemDefault()) + .setInternal(true), ClusterPartitionFetcher.getInstance(), ClusterSchemaFetcher.getInstance()); diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java index 04fdde706134f..1bd9ffb2f7a71 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java @@ -23,12 +23,21 @@ import org.apache.iotdb.commons.auth.entity.User; import org.apache.iotdb.commons.conf.CommonConfig; import org.apache.iotdb.commons.conf.CommonDescriptor; +import org.apache.iotdb.commons.path.MeasurementPath; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext; import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor; import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; +import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountLevelTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSeriesStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateTimeSeriesStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.DatabaseSchemaStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.DeleteTimeSeriesStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ActivateTemplateStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.SetSchemaTemplateStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.CreateLogicalViewStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.rpc.TSStatusCode; @@ -238,6 +247,99 @@ public void testUnsupportedAuditDatabaseWrite() throws Exception { new TreeAccessCheckContext(10000L, "user1", ""), new PartialPath("root.sg"))); } + @Test + public void testSystemDatabaseWriteRequiresInternalCaller() throws Exception { + final TreeAccessCheckVisitor visitor = new TreeAccessCheckVisitor(); + final TreeAccessCheckContext externalCaller = + new TreeAccessCheckContext(AuthorityChecker.SUPER_USER_ID, AuthorityChecker.SUPER_USER, ""); + final TreeAccessCheckContext internalCaller = + new TreeAccessCheckContext(AuthorityChecker.SUPER_USER_ID, AuthorityChecker.SUPER_USER, "") + .setInternal(true); + + final InsertStatement systemInsert = new InsertStatement(); + systemInsert.setDevice(new PartialPath("root.__system.d")); + systemInsert.setMeasurementList(new String[] {"s"}); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitInsert(systemInsert, externalCaller).getCode()); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitInsert(systemInsert, internalCaller).getCode()); + + final CreateTimeSeriesStatement systemCreate = new CreateTimeSeriesStatement(); + systemCreate.setPath(new MeasurementPath("root.__system.d.s")); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitCreateTimeseries(systemCreate, externalCaller).getCode()); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitCreateTimeseries(systemCreate, internalCaller).getCode()); + + final DeleteTimeSeriesStatement deleteTimeSeries = + new DeleteTimeSeriesStatement( + Collections.singletonList(new PartialPath("root.__system.**"))); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitDeleteTimeSeries(deleteTimeSeries, externalCaller).getCode()); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitDeleteTimeSeries(deleteTimeSeries, internalCaller).getCode()); + + final AlterTimeSeriesStatement alterTimeSeries = new AlterTimeSeriesStatement(); + alterTimeSeries.setPath(new MeasurementPath("root.__system.d.s")); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitAlterTimeSeries(alterTimeSeries, externalCaller).getCode()); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitAlterTimeSeries(alterTimeSeries, internalCaller).getCode()); + + final ActivateTemplateStatement activateTemplate = + new ActivateTemplateStatement(new PartialPath("root.__system.d")); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitActivateTemplate(activateTemplate, internalCaller).getCode()); + + final SetSchemaTemplateStatement setSchemaTemplate = + new SetSchemaTemplateStatement("t", new PartialPath("root.__system")); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitSetSchemaTemplate(setSchemaTemplate, externalCaller).getCode()); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitSetSchemaTemplate(setSchemaTemplate, internalCaller).getCode()); + + final CreateLogicalViewStatement systemView = new CreateLogicalViewStatement(); + systemView.setTargetFullPaths(Collections.singletonList(new PartialPath("root.sg.v.s"))); + systemView.setSourceFullPaths(Collections.singletonList(new PartialPath("root.__system.d.s"))); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitCreateLogicalView(systemView, externalCaller).getCode()); + + // The system database cannot be created through SQL. + final DatabaseSchemaStatement systemDatabaseCreate = + new DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.CREATE); + systemDatabaseCreate.setDatabasePath(new PartialPath("root.__system")); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitSetDatabase(systemDatabaseCreate, externalCaller).getCode()); + + final DatabaseSchemaStatement systemCaseVariantCreate = + new DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.CREATE); + systemCaseVariantCreate.setDatabasePath(new PartialPath("root.__System")); + Assert.assertEquals( + TSStatusCode.NO_PERMISSION.getStatusCode(), + visitor.visitSetDatabase(systemCaseVariantCreate, externalCaller).getCode()); + + // Writes to a normal database are unaffected. + final InsertStatement normalInsert = new InsertStatement(); + normalInsert.setDevice(new PartialPath("root.sg.d")); + normalInsert.setMeasurementList(new String[] {"s"}); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + visitor.visitInsert(normalInsert, externalCaller).getCode()); + } + @Test public void testPathLogLimitDoesNotLimitPermissionCheck() throws Exception { final CommonConfig config = CommonDescriptor.getInstance().getConfig(); 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 d952bce23cac6..4db46c0cdbf34 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 @@ -391,6 +391,8 @@ prometheus_reporter_async_update=true # The reporters of metric module to report metrics # If there are more than one reporter, please separate them by commas ",". # Options: [JMX, PROMETHEUS] +# The IOTDB session reporter is not supported: it writes to root.__system, which rejects writes +# from external client sessions. # effectiveMode: restart # Datatype: String cn_metric_reporter_list= @@ -414,6 +416,8 @@ cn_metric_prometheus_reporter_port=9091 # The reporters of metric module to report metrics # If there are more than one reporter, please separate them by commas ",". # Options: [JMX, PROMETHEUS] +# The IOTDB session reporter is not supported: it writes to root.__system, which rejects writes +# from external client sessions. # effectiveMode: restart # Datatype: String dn_metric_reporter_list= diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java index 0b46b0fd286e1..ad4c85f18d35d 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java @@ -58,4 +58,9 @@ public interface IAuditEntity { String getSqlString(); IAuditEntity setSqlString(String sqlString); + + /** Whether the caller is a dedicated internal writer of the database itself. */ + default boolean isInternal() { + return false; + } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java index de681067c93af..d6a0f4120abb7 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java @@ -50,6 +50,10 @@ public class SessionInfo { // SessionInfo is sent to execution nodes, so this field does not need serialization. private final transient boolean enableAutoCreateSchema; + // Whether the session is opened by the database itself. Like enableAutoCreateSchema, this is + // only consulted during analysis and therefore does not need serialization. + private transient boolean internal; + public SessionInfo(long sessionId, UserEntity userEntity, ZoneId zoneId) { this.sessionId = sessionId; this.userEntity = userEntity; @@ -165,6 +169,15 @@ public boolean isEnableAutoCreateSchema() { return enableAutoCreateSchema; } + public boolean isInternal() { + return internal; + } + + public SessionInfo setInternal(final boolean internal) { + this.internal = internal; + return this; + } + public static SessionInfo deserializeFrom(final ByteBuffer buffer) { final long sessionId = ReadWriteIOUtils.readLong(buffer); final long userId = ReadWriteIOUtils.readLong(buffer); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java new file mode 100644 index 0000000000000..3ed5ba24fd018 --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.commons.schema; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.schema.table.Audit; + +public final class InternalDatabaseUtils { + + private InternalDatabaseUtils() {} + + /** Whether the given database (or one of its descendants) is an internal database. */ + public static boolean isInternalDatabase(final String databaseName) { + return isSystemDatabase(databaseName) || Audit.isAuditDatabase(databaseName); + } + + /** Whether the given database (or one of its descendants) is {@code root.__system}. */ + public static boolean isSystemDatabase(final String databaseName) { + return isDatabaseOrDescendant(databaseName, SchemaConstant.SYSTEM_DATABASE); + } + + /** Whether the device or measurement path belongs to {@code root.__system}. */ + public static boolean isSystemDatabasePath(final PartialPath path) { + return path != null && isSystemDatabase(path.getFullPath()); + } + + // Internal database names are matched case-insensitively: root.__System is also internal. + private static boolean isDatabaseOrDescendant( + final String databaseName, final String internalDatabase) { + return databaseName != null + && databaseName.regionMatches(true, 0, internalDatabase, 0, internalDatabase.length()) + && (databaseName.length() == internalDatabase.length() + || databaseName.charAt(internalDatabase.length()) == '.'); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java index a33cda1b5dc21..fa672344081ae 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java @@ -57,6 +57,7 @@ public static boolean isAuditDatabase(final String databaseName) { || isDatabaseOrDescendant(databaseName, TREE_MODEL_AUDIT_DATABASE); } + // Audit database names are matched case-insensitively: root.__Audit is also an audit database. private static boolean isDatabaseOrDescendant( final String databaseName, final String auditDatabaseName) { return databaseName != null diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java index ad66cd0f284a5..ddb01615798d9 100644 --- a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java @@ -34,6 +34,7 @@ public void testIsAuditDatabase() { assertTrue(Audit.isAuditDatabase("ROOT.__AUDIT")); assertTrue(Audit.isAuditDatabase("__audit.data")); assertTrue(Audit.isAuditDatabase("root.__audit.data")); + assertTrue(Audit.isAuditDatabase("root.__Audit.data")); assertFalse(Audit.isAuditDatabase("__audit_data")); assertFalse(Audit.isAuditDatabase("root.__audit_data")); assertFalse(Audit.isAuditDatabase(null));