From 602909f9b5cc55da205f15a72daca7a446cf2541 Mon Sep 17 00:00:00 2001 From: libo Date: Wed, 29 Jul 2026 22:04:09 +0800 Subject: [PATCH 1/4] Audit user role membership changes --- .../iotdb/db/i18n/DataNodeMiscMessages.java | 2 + .../iotdb/db/i18n/DataNodeMiscMessages.java | 2 + .../UserRoleModificationAuditContext.java | 99 +++++++++++ .../thrift/impl/ClientRPCServiceImpl.java | 45 ++++- .../UserRoleModificationAuditContextTest.java | 154 ++++++++++++++++++ .../iotdb/commons/audit/AuditEventType.java | 1 + 6 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index b43037206ccdc..3154a3f621bee 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -121,6 +121,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_ERROR_FMT = "create new region %s error, exception:%s"; public static final String CREATE_NEW_REGION_SUCCEED_FMT = "create new region %s succeed"; + public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "user: %s, role: %s"; + private DataNodeMiscMessages() {} // --------------------------------------------------------------------------- diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index a319bce5000e6..c1cc841637e4f 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -121,6 +121,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_ERROR_FMT = "创建新 region %s 错误,异常:%s"; public static final String CREATE_NEW_REGION_SUCCEED_FMT = "创建新 region %s 成功"; + public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "用户:%s,角色:%s"; + private DataNodeMiscMessages() {} // --------------------------------------------------------------------------- diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java new file mode 100644 index 0000000000000..38999655fa3cd --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java @@ -0,0 +1,99 @@ +/* + * 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.db.audit; + +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.audit.AuditEventType; +import org.apache.iotdb.commons.audit.AuditLogFields; +import org.apache.iotdb.commons.audit.AuditLogOperation; +import org.apache.iotdb.commons.auth.entity.PrivilegeType; +import org.apache.iotdb.db.i18n.DataNodeMiscMessages; +import org.apache.iotdb.db.protocol.session.IClientSession; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; +import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; +import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; +import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; +import org.apache.iotdb.rpc.TSStatusCode; + +/** Tracks one user-role membership statement and logs a successful modification. */ +public class UserRoleModificationAuditContext { + + private final DNAuditLogger auditLogger; + private final String sqlString; + + private IClientSession clientSession; + private String targetUsername; + private String targetRoleName; + + public UserRoleModificationAuditContext(String sqlString) { + this(DNAuditLogger.getInstance(), sqlString); + } + + UserRoleModificationAuditContext(DNAuditLogger auditLogger, String sqlString) { + this.auditLogger = auditLogger; + this.sqlString = sqlString; + } + + public void setClientSession(IClientSession clientSession) { + this.clientSession = clientSession; + } + + public void track(AuthorStatement statement) { + if (statement.getAuthorType() == AuthorType.GRANT_USER_ROLE + || statement.getAuthorType() == AuthorType.REVOKE_USER_ROLE) { + targetUsername = statement.getUserName(); + targetRoleName = statement.getRoleName(); + } + } + + public void track(RelationalAuthorStatement statement) { + if (statement.getAuthorType() == AuthorRType.GRANT_USER_ROLE + || statement.getAuthorType() == AuthorRType.REVOKE_USER_ROLE) { + targetUsername = statement.getUserName(); + targetRoleName = statement.getRoleName(); + } + } + + public void log(TSStatus status) { + if (targetUsername == null || clientSession == null || !isSuccessful(status)) { + return; + } + auditLogger.log( + new AuditLogFields( + clientSession.getUserId(), + clientSession.getUsername(), + clientSession.getClientAddress(), + AuditEventType.MODIFY_USER_ROLE, + AuditLogOperation.CONTROL, + PrivilegeType.SECURITY, + true, + clientSession.getDatabaseName(), + sqlString), + () -> + String.format( + DataNodeMiscMessages.LOG_USER_ARG_ROLE_ARG_422D48D3, + targetUsername, + targetRoleName)); + } + + private static boolean isSuccessful(TSStatus status) { + return status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode(); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java index ea67ebb8523df..b6959ca87b92e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java @@ -59,6 +59,7 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.parser.ParsingException; import org.apache.iotdb.commons.utils.PathUtils; import org.apache.iotdb.db.audit.DNAuditLogger; +import org.apache.iotdb.db.audit.UserRoleModificationAuditContext; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; @@ -105,6 +106,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext; import org.apache.iotdb.db.queryengine.plan.relational.sql.ParameterExtractor; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Execute; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetSqlDialect; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser; @@ -130,6 +132,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.SetSchemaTemplateStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.UnsetSchemaTemplateStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.CreateTableViewStatement; +import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.SetSqlDialectStatement; import org.apache.iotdb.db.schemaengine.SchemaEngine; import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegion; @@ -329,7 +332,25 @@ public ClientRPCServiceImpl() { private TSExecuteStatementResp executeStatementInternal( NativeStatementRequest request, SelectResult setResult) { + UserRoleModificationAuditContext userRoleModificationAuditContext = + new UserRoleModificationAuditContext(request.getSql()); + TSExecuteStatementResp response = null; + try { + response = + executeStatementInternalWithoutUserRoleModificationAudit( + request, setResult, userRoleModificationAuditContext); + return response; + } finally { + userRoleModificationAuditContext.log(response == null ? null : response.getStatus()); + } + } + + private TSExecuteStatementResp executeStatementInternalWithoutUserRoleModificationAudit( + NativeStatementRequest request, + SelectResult setResult, + UserRoleModificationAuditContext userRoleModificationAuditContext) { IClientSession clientSession = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime(); + userRoleModificationAuditContext.setClientSession(clientSession); if (!SESSION_MANAGER.checkLogin(clientSession)) { return RpcUtils.getTSExecuteStatementResp(getNotLoggedInStatus()); } @@ -353,6 +374,9 @@ private TSExecuteStatementResp executeStatementInternal( ExecutionResult result; if (clientSession.getSqlDialect() == SqlDialect.TREE) { Statement s = request.getTreeStatement(clientSession.getZoneId()); + if (s instanceof AuthorStatement) { + userRoleModificationAuditContext.track((AuthorStatement) s); + } if (s instanceof SetSqlDialectStatement) { setSqlDialect = true; } @@ -426,6 +450,9 @@ private TSExecuteStatementResp executeStatementInternal( } else { org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement s = request.getTableStatement(relationSqlParser, clientSession.getZoneId(), clientSession); + if (s instanceof RelationalAuthorStatement) { + userRoleModificationAuditContext.track((RelationalAuthorStatement) s); + } if (s instanceof Use) { useDatabase = true; @@ -2144,6 +2171,10 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { try { for (int i = 0; i < req.getStatements().size(); i++) { String statement = req.getStatements().get(i); + UserRoleModificationAuditContext userRoleModificationAuditContext = + new UserRoleModificationAuditContext(statement); + userRoleModificationAuditContext.setClientSession(clientSession); + TSStatus statementStatus = null; long t2 = System.nanoTime(); String type = null; OperationQuota quota = null; @@ -2156,6 +2187,9 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { return RpcUtils.getStatus( TSStatusCode.EXECUTE_STATEMENT_ERROR, "This operation type is not supported"); } + if (s instanceof AuthorStatement) { + userRoleModificationAuditContext.track((AuthorStatement) s); + } if (s instanceof CreateTableViewStatement) { result = @@ -2223,6 +2257,9 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement s = relationSqlParser.createStatement( statement, clientSession.getZoneId(), clientSession); + if (s instanceof RelationalAuthorStatement) { + userRoleModificationAuditContext.track((RelationalAuthorStatement) s); + } if (s instanceof Use) { useDatabase = true; @@ -2266,15 +2303,17 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { } } - results.add(result.status); + statementStatus = result.status; + results.add(statementStatus); } catch (Exception e) { LOGGER.warn(DataNodeMiscMessages.ERROR_EXECUTING_BATCH_STATEMENT, e); - TSStatus status = + statementStatus = onQueryException( e, "\"" + statement + "\". " + OperationType.EXECUTE_BATCH_STATEMENT); isAllSuccessful = false; - results.add(status); + results.add(statementStatus); } finally { + userRoleModificationAuditContext.log(statementStatus); CommonUtils.addStatementExecutionLatency( OperationType.EXECUTE_STATEMENT, type, System.nanoTime() - t2); if (quota != null) { diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java new file mode 100644 index 0000000000000..f9ba1f8ec17cf --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java @@ -0,0 +1,154 @@ +/* + * 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.db.audit; + +import org.apache.iotdb.commons.audit.AuditEventType; +import org.apache.iotdb.commons.audit.AuditLogOperation; +import org.apache.iotdb.commons.audit.IAuditEntity; +import org.apache.iotdb.commons.auth.entity.PrivilegeType; +import org.apache.iotdb.db.protocol.session.IClientSession; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; +import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; +import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; +import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; +import org.apache.iotdb.rpc.RpcUtils; +import org.apache.iotdb.rpc.TSStatusCode; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UserRoleModificationAuditContextTest { + + @Test + public void testTreeGrantRoleSuccess() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "GRANT ROLE role1 TO user1"); + context.setClientSession(mockSession()); + AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + + context.track(statement); + context.log(RpcUtils.SUCCESS_STATUS); + + assertAuditLog(auditLogger, "GRANT ROLE role1 TO user1"); + } + + @Test + public void testTableRevokeRoleSuccess() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "REVOKE ROLE role1 FROM user1"); + context.setClientSession(mockSession()); + RelationalAuthorStatement statement = + new RelationalAuthorStatement(AuthorRType.REVOKE_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + + context.track(statement); + context.log(RpcUtils.SUCCESS_STATUS); + + assertAuditLog(auditLogger, "REVOKE ROLE role1 FROM user1"); + } + + @Test + public void testFailedModificationIsIgnored() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "grant role"); + context.setClientSession(mockSession()); + AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + context.track(statement); + + context.log(RpcUtils.getStatus(TSStatusCode.NO_PERMISSION)); + + verify(auditLogger, never()).log(any(), any()); + } + + @Test + public void testRedirectedModificationIsIgnored() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "grant role"); + context.setClientSession(mockSession()); + AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + context.track(statement); + + context.log(RpcUtils.getStatus(TSStatusCode.REDIRECTION_RECOMMEND)); + + verify(auditLogger, never()).log(any(), any()); + } + + @Test + public void testPrivilegeGrantIsIgnored() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "grant privilege"); + context.setClientSession(mockSession()); + context.track(new AuthorStatement(AuthorType.GRANT_USER)); + + context.log(RpcUtils.SUCCESS_STATUS); + + verify(auditLogger, never()).log(any(), any()); + } + + private static IClientSession mockSession() { + IClientSession session = mock(IClientSession.class); + when(session.getUserId()).thenReturn(7L); + when(session.getUsername()).thenReturn("operator"); + when(session.getClientAddress()).thenReturn("127.0.0.1"); + when(session.getDatabaseName()).thenReturn("database"); + return session; + } + + @SuppressWarnings("unchecked") + private static void assertAuditLog(DNAuditLogger auditLogger, String sql) { + ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(IAuditEntity.class); + ArgumentCaptor> logCaptor = ArgumentCaptor.forClass(Supplier.class); + verify(auditLogger).log(entityCaptor.capture(), logCaptor.capture()); + + IAuditEntity entity = entityCaptor.getValue(); + assertEquals(7L, entity.getUserId()); + assertEquals("operator", entity.getUsername()); + assertEquals("127.0.0.1", entity.getCliHostname()); + assertEquals(AuditEventType.MODIFY_USER_ROLE, entity.getAuditEventType()); + assertEquals(AuditLogOperation.CONTROL, entity.getAuditLogOperation()); + assertEquals(PrivilegeType.SECURITY, entity.getPrivilegeTypes().get(0)); + assertTrue(entity.getResult()); + assertEquals("database", entity.getDatabase()); + assertEquals(sql, entity.getSqlString()); + assertEquals("user: user1, role: role1", logCaptor.getValue().get()); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java index 510912a302fdc..4d8eb9c550e22 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java @@ -37,6 +37,7 @@ public enum AuditEventType { LOGIN_FINAL, MODIFY_SECURITY_OPTIONS, MODIFY_DEFAULT_SECURITY_VALUES, + MODIFY_USER_ROLE, REVOKE_FAILED, GRANT_ROLE_FAILED, LOGIN_RESOURCE_RESTRICT, From 12ea032ad53659c99a4cf61c807f4c1ce287993e Mon Sep 17 00:00:00 2001 From: libo Date: Fri, 31 Jul 2026 22:58:25 +0800 Subject: [PATCH 2/4] Record security function and role membership audit logs --- .../db/it/audit/IoTDBAuditLogBasicIT.java | 131 +++++++++++++++++- .../iotdb/db/i18n/DataNodeMiscMessages.java | 3 + .../iotdb/db/i18n/DataNodeMiscMessages.java | 3 + .../UserRoleModificationAuditContext.java | 65 ++++++--- .../thrift/impl/ClientRPCServiceImpl.java | 45 +----- .../db/queryengine/plan/Coordinator.java | 16 +++ .../UserRoleModificationAuditContextTest.java | 88 +++++++++--- .../iotdb/commons/audit/AuditEventType.java | 3 +- 8 files changed, 275 insertions(+), 79 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java index 83aa06dc61dce..547d7e37bef00 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java @@ -196,6 +196,7 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "DROP DATABASE IF EXISTS test", "REVOKE SELECT,ALTER,INSERT,DELETE ON TEST.TABLE1 FROM USER user1", "REVOKE SELECT ON ANY FROM ROLE role1", + "REVOKE ROLE role1 FROM user2", "DROP USER user1", "DROP USER user2", "DROP ROLE role1"); @@ -634,7 +635,33 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "true", "null", "GRANT ROLE role1 TO user2", - "User root (ID=0) requests authority on object user: user2, role: role1 with result true")), + "User root (ID=0) requests authority on object user: user2, role: role1 with result true"), + Arrays.asList( + "node_1", + "u_0", + "root", + "127.0.0.1", + "MODIFY_SECURITY_FUNCTION", + "CONTROL", + "[SECURITY]", + "GLOBAL", + "true", + "test", + "GRANT ROLE role1 TO user2", + "security function: user-role membership, user: user2, role: role1"), + Arrays.asList( + "node_1", + "u_0", + "root", + "127.0.0.1", + "MODIFY_ROLE_MEMBERSHIP", + "CONTROL", + "[SECURITY]", + "GLOBAL", + "true", + "test", + "GRANT ROLE role1 TO user2", + "user: user2, role: role1")), // List user/role, the authentication object is null since the root can list all new AuditLogSet( Arrays.asList( @@ -1192,7 +1219,46 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "true", "null", "REVOKE SELECT ON ANY FROM ROLE role1", - "User root (ID=0) requests authority on object role1 with result true")), + "User root (ID=0) requests authority on object role1 with result true"), + Arrays.asList( + "node_1", + "u_0", + "root", + "127.0.0.1", + "OBJECT_AUTHENTICATION", + "DDL", + "[SECURITY]", + "GLOBAL", + "true", + "null", + "REVOKE ROLE role1 FROM user2", + "User root (ID=0) requests authority on object user: user2, role: role1 with result true"), + Arrays.asList( + "node_1", + "u_0", + "root", + "127.0.0.1", + "MODIFY_SECURITY_FUNCTION", + "CONTROL", + "[SECURITY]", + "GLOBAL", + "true", + "null", + "REVOKE ROLE role1 FROM user2", + "security function: user-role membership, user: user2, role: role1"), + Arrays.asList( + "node_1", + "u_0", + "root", + "127.0.0.1", + "MODIFY_ROLE_MEMBERSHIP", + "CONTROL", + "[SECURITY]", + "GLOBAL", + "true", + "null", + "REVOKE ROLE role1 FROM user2", + "user: user2, role: role1")), // Drop user, role new AuditLogSet( Arrays.asList( @@ -1352,6 +1418,7 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "drop timeseries root.test.d1.*", "REVOKE READ_DATA, WRITE_DATA ON root.test.** FROM USER user1", "REVOKE READ ON root.test.** FROM ROLE role1", + "REVOKE ROLE role1 FROM user2", "DROP USER user1", "DROP USER user2", "DROP ROLE role1", @@ -1784,6 +1851,30 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "GRANT ROLE role1 TO user2", "OBJECT_AUTHENTICATION", "127.0.0.1", + "root"), + Arrays.asList( + "root.__audit.log.node_1.u_0", + "true", + "GLOBAL", + "[SECURITY]", + "null", + "CONTROL", + "security function: user-role membership, user: user2, role: role1", + "GRANT ROLE role1 TO user2", + "MODIFY_SECURITY_FUNCTION", + "127.0.0.1", + "root"), + Arrays.asList( + "root.__audit.log.node_1.u_0", + "true", + "GLOBAL", + "[SECURITY]", + "null", + "CONTROL", + "user: user2, role: role1", + "GRANT ROLE role1 TO user2", + "MODIFY_ROLE_MEMBERSHIP", + "127.0.0.1", "root")), // List user/role, the target object is null since the root can list all, new AuditLogSet( @@ -2318,6 +2409,42 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "REVOKE READ ON root.test.** FROM ROLE role1", "OBJECT_AUTHENTICATION", "127.0.0.1", + "root"), + Arrays.asList( + "root.__audit.log.node_1.u_0", + "true", + "GLOBAL", + "[MANAGE_ROLE]", + "null", + "DDL", + "User root (ID=0) requests authority on object user: user2, role: role1 with result true", + "REVOKE ROLE role1 FROM user2", + "OBJECT_AUTHENTICATION", + "127.0.0.1", + "root"), + Arrays.asList( + "root.__audit.log.node_1.u_0", + "true", + "GLOBAL", + "[SECURITY]", + "null", + "CONTROL", + "security function: user-role membership, user: user2, role: role1", + "REVOKE ROLE role1 FROM user2", + "MODIFY_SECURITY_FUNCTION", + "127.0.0.1", + "root"), + Arrays.asList( + "root.__audit.log.node_1.u_0", + "true", + "GLOBAL", + "[SECURITY]", + "null", + "CONTROL", + "user: user2, role: role1", + "REVOKE ROLE role1 FROM user2", + "MODIFY_ROLE_MEMBERSHIP", + "127.0.0.1", "root")), // Drop user/role new AuditLogSet( diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index 3154a3f621bee..97947b140971f 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -122,6 +122,9 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_ERROR_FMT = "create new region %s error, exception:%s"; public static final String CREATE_NEW_REGION_SUCCEED_FMT = "create new region %s succeed"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "user: %s, role: %s"; + public static final String + LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = + "security function: user-role membership, user: %s, role: %s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index c1cc841637e4f..0b105feab8c1f 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -122,6 +122,9 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_ERROR_FMT = "创建新 region %s 错误,异常:%s"; public static final String CREATE_NEW_REGION_SUCCEED_FMT = "创建新 region %s 成功"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "用户:%s,角色:%s"; + public static final String + LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = + "安全功能:用户角色成员关系,用户:%s,角色:%s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java index 38999655fa3cd..f4da86e655464 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java @@ -24,21 +24,26 @@ import org.apache.iotdb.commons.audit.AuditLogFields; import org.apache.iotdb.commons.audit.AuditLogOperation; import org.apache.iotdb.commons.auth.entity.PrivilegeType; +import org.apache.iotdb.commons.queryengine.common.SessionInfo; import org.apache.iotdb.db.i18n.DataNodeMiscMessages; -import org.apache.iotdb.db.protocol.session.IClientSession; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.rpc.TSStatusCode; -/** Tracks one user-role membership statement and logs a successful modification. */ +import java.util.function.Supplier; + +/** Tracks one user-role membership statement and logs its successful security changes. */ public class UserRoleModificationAuditContext { private final DNAuditLogger auditLogger; private final String sqlString; - private IClientSession clientSession; + private Long userId; + private String username; + private String clientAddress; + private String database; private String targetUsername; private String targetRoleName; @@ -51,8 +56,22 @@ public UserRoleModificationAuditContext(String sqlString) { this.sqlString = sqlString; } - public void setClientSession(IClientSession clientSession) { - this.clientSession = clientSession; + public void setSessionInfo(SessionInfo sessionInfo) { + if (sessionInfo == null) { + return; + } + setActor( + sessionInfo.getUserId(), + sessionInfo.getUserName(), + sessionInfo.getCliHostname(), + sessionInfo.getDatabaseName().orElse(null)); + } + + private void setActor(long userId, String username, String clientAddress, String database) { + this.userId = userId; + this.username = username; + this.clientAddress = clientAddress; + this.database = database; } public void track(AuthorStatement statement) { @@ -72,25 +91,39 @@ public void track(RelationalAuthorStatement statement) { } public void log(TSStatus status) { - if (targetUsername == null || clientSession == null || !isSuccessful(status)) { + if (targetUsername == null || userId == null || !isSuccessful(status)) { return; } + logEvent( + AuditEventType.MODIFY_SECURITY_FUNCTION, + () -> + String.format( + DataNodeMiscMessages + .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, + targetUsername, + targetRoleName)); + logEvent( + AuditEventType.MODIFY_ROLE_MEMBERSHIP, + () -> + String.format( + DataNodeMiscMessages.LOG_USER_ARG_ROLE_ARG_422D48D3, + targetUsername, + targetRoleName)); + } + + private void logEvent(AuditEventType eventType, Supplier log) { auditLogger.log( new AuditLogFields( - clientSession.getUserId(), - clientSession.getUsername(), - clientSession.getClientAddress(), - AuditEventType.MODIFY_USER_ROLE, + userId, + username, + clientAddress, + eventType, AuditLogOperation.CONTROL, PrivilegeType.SECURITY, true, - clientSession.getDatabaseName(), + database, sqlString), - () -> - String.format( - DataNodeMiscMessages.LOG_USER_ARG_ROLE_ARG_422D48D3, - targetUsername, - targetRoleName)); + log); } private static boolean isSuccessful(TSStatus status) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java index f3035dc38a1dd..1752c8acf7674 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java @@ -59,7 +59,6 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.parser.ParsingException; import org.apache.iotdb.commons.utils.PathUtils; import org.apache.iotdb.db.audit.DNAuditLogger; -import org.apache.iotdb.db.audit.UserRoleModificationAuditContext; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; @@ -106,7 +105,6 @@ import org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext; import org.apache.iotdb.db.queryengine.plan.relational.sql.ParameterExtractor; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Execute; -import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetSqlDialect; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser; @@ -132,7 +130,6 @@ import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.SetSchemaTemplateStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.UnsetSchemaTemplateStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.CreateTableViewStatement; -import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.SetSqlDialectStatement; import org.apache.iotdb.db.schemaengine.SchemaEngine; import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegion; @@ -332,25 +329,7 @@ public ClientRPCServiceImpl() { private TSExecuteStatementResp executeStatementInternal( NativeStatementRequest request, SelectResult setResult) { - UserRoleModificationAuditContext userRoleModificationAuditContext = - new UserRoleModificationAuditContext(request.getSql()); - TSExecuteStatementResp response = null; - try { - response = - executeStatementInternalWithoutUserRoleModificationAudit( - request, setResult, userRoleModificationAuditContext); - return response; - } finally { - userRoleModificationAuditContext.log(response == null ? null : response.getStatus()); - } - } - - private TSExecuteStatementResp executeStatementInternalWithoutUserRoleModificationAudit( - NativeStatementRequest request, - SelectResult setResult, - UserRoleModificationAuditContext userRoleModificationAuditContext) { IClientSession clientSession = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime(); - userRoleModificationAuditContext.setClientSession(clientSession); if (!SESSION_MANAGER.checkLogin(clientSession)) { return RpcUtils.getTSExecuteStatementResp(getNotLoggedInStatus()); } @@ -374,9 +353,6 @@ private TSExecuteStatementResp executeStatementInternalWithoutUserRoleModificati ExecutionResult result; if (clientSession.getSqlDialect() == SqlDialect.TREE) { Statement s = request.getTreeStatement(clientSession.getZoneId()); - if (s instanceof AuthorStatement) { - userRoleModificationAuditContext.track((AuthorStatement) s); - } if (s instanceof SetSqlDialectStatement) { setSqlDialect = true; } @@ -450,9 +426,6 @@ private TSExecuteStatementResp executeStatementInternalWithoutUserRoleModificati } else { org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement s = request.getTableStatement(relationSqlParser, clientSession.getZoneId(), clientSession); - if (s instanceof RelationalAuthorStatement) { - userRoleModificationAuditContext.track((RelationalAuthorStatement) s); - } if (s instanceof Use) { useDatabase = true; @@ -2171,10 +2144,6 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { try { for (int i = 0; i < req.getStatements().size(); i++) { String statement = req.getStatements().get(i); - UserRoleModificationAuditContext userRoleModificationAuditContext = - new UserRoleModificationAuditContext(statement); - userRoleModificationAuditContext.setClientSession(clientSession); - TSStatus statementStatus = null; long t2 = System.nanoTime(); String type = null; OperationQuota quota = null; @@ -2187,9 +2156,6 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { return RpcUtils.getStatus( TSStatusCode.EXECUTE_STATEMENT_ERROR, "This operation type is not supported"); } - if (s instanceof AuthorStatement) { - userRoleModificationAuditContext.track((AuthorStatement) s); - } if (s instanceof CreateTableViewStatement) { result = @@ -2257,9 +2223,6 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement s = relationSqlParser.createStatement( statement, clientSession.getZoneId(), clientSession); - if (s instanceof RelationalAuthorStatement) { - userRoleModificationAuditContext.track((RelationalAuthorStatement) s); - } if (s instanceof Use) { useDatabase = true; @@ -2303,17 +2266,15 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) { } } - statementStatus = result.status; - results.add(statementStatus); + results.add(result.status); } catch (Exception e) { LOGGER.warn(DataNodeMiscMessages.ERROR_EXECUTING_BATCH_STATEMENT, e); - statementStatus = + TSStatus status = onQueryException( e, "\"" + statement + "\". " + OperationType.EXECUTE_BATCH_STATEMENT); isAllSuccessful = false; - results.add(statementStatus); + results.add(status); } finally { - userRoleModificationAuditContext.log(statementStatus); CommonUtils.addStatementExecutionLatency( OperationType.EXECUTE_STATEMENT, type, System.nanoTime() - t2); if (quota != null) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java index 8919182c7ce93..9245402b68161 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java @@ -43,6 +43,7 @@ import org.apache.iotdb.commons.queryengine.plan.relational.type.InternalTypeManager; import org.apache.iotdb.commons.queryengine.plan.relational.type.TypeManager; import org.apache.iotdb.db.audit.DNAuditLogger; +import org.apache.iotdb.db.audit.UserRoleModificationAuditContext; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; @@ -157,6 +158,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.IConfigStatement; import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.pipe.PipeEnrichedStatement; +import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.db.queryengine.udf.InternalQueryExecutor; import org.apache.iotdb.db.utils.SetThreadName; @@ -415,6 +417,13 @@ public ExecutionResult executeForTreeModel( } finally { DNAuditLogger.getInstance() .logRevokeFailure(statement, session, sql, result == null ? null : result.status); + if (statement instanceof AuthorStatement) { + final UserRoleModificationAuditContext auditContext = + new UserRoleModificationAuditContext(sql); + auditContext.setSessionInfo(session); + auditContext.track((AuthorStatement) statement); + auditContext.log(result == null ? null : result.status); + } } } @@ -563,6 +572,13 @@ public ExecutionResult executeForTableModel( } finally { DNAuditLogger.getInstance() .logRevokeFailure(statement, session, sql, result == null ? null : result.status); + if (statement instanceof RelationalAuthorStatement) { + final UserRoleModificationAuditContext auditContext = + new UserRoleModificationAuditContext(sql); + auditContext.setSessionInfo(session); + auditContext.track((RelationalAuthorStatement) statement); + auditContext.log(result == null ? null : result.status); + } } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java index f9ba1f8ec17cf..350ce38c76061 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java @@ -23,7 +23,8 @@ import org.apache.iotdb.commons.audit.AuditLogOperation; import org.apache.iotdb.commons.audit.IAuditEntity; import org.apache.iotdb.commons.auth.entity.PrivilegeType; -import org.apache.iotdb.db.protocol.session.IClientSession; +import org.apache.iotdb.commons.queryengine.common.SessionInfo; +import org.apache.iotdb.db.i18n.DataNodeMiscMessages; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement; import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; @@ -34,6 +35,8 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; @@ -41,6 +44,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,7 +55,7 @@ public void testTreeGrantRoleSuccess() { DNAuditLogger auditLogger = mock(DNAuditLogger.class); UserRoleModificationAuditContext context = new UserRoleModificationAuditContext(auditLogger, "GRANT ROLE role1 TO user1"); - context.setClientSession(mockSession()); + context.setSessionInfo(mockSessionInfo()); AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); statement.setUserName("user1"); statement.setRoleName("role1"); @@ -59,7 +63,7 @@ public void testTreeGrantRoleSuccess() { context.track(statement); context.log(RpcUtils.SUCCESS_STATUS); - assertAuditLog(auditLogger, "GRANT ROLE role1 TO user1"); + assertAuditLogs(auditLogger, "GRANT ROLE role1 TO user1"); } @Test @@ -67,7 +71,7 @@ public void testTableRevokeRoleSuccess() { DNAuditLogger auditLogger = mock(DNAuditLogger.class); UserRoleModificationAuditContext context = new UserRoleModificationAuditContext(auditLogger, "REVOKE ROLE role1 FROM user1"); - context.setClientSession(mockSession()); + context.setSessionInfo(mockSessionInfo()); RelationalAuthorStatement statement = new RelationalAuthorStatement(AuthorRType.REVOKE_USER_ROLE); statement.setUserName("user1"); @@ -76,7 +80,40 @@ public void testTableRevokeRoleSuccess() { context.track(statement); context.log(RpcUtils.SUCCESS_STATUS); - assertAuditLog(auditLogger, "REVOKE ROLE role1 FROM user1"); + assertAuditLogs(auditLogger, "REVOKE ROLE role1 FROM user1"); + } + + @Test + public void testTreeRevokeRoleSuccess() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "REVOKE ROLE role1 FROM user1"); + context.setSessionInfo(mockSessionInfo()); + AuthorStatement statement = new AuthorStatement(AuthorType.REVOKE_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + + context.track(statement); + context.log(RpcUtils.SUCCESS_STATUS); + + assertAuditLogs(auditLogger, "REVOKE ROLE role1 FROM user1"); + } + + @Test + public void testTableGrantRoleSuccess() { + DNAuditLogger auditLogger = mock(DNAuditLogger.class); + UserRoleModificationAuditContext context = + new UserRoleModificationAuditContext(auditLogger, "GRANT ROLE role1 TO user1"); + context.setSessionInfo(mockSessionInfo()); + RelationalAuthorStatement statement = + new RelationalAuthorStatement(AuthorRType.GRANT_USER_ROLE); + statement.setUserName("user1"); + statement.setRoleName("role1"); + + context.track(statement); + context.log(RpcUtils.SUCCESS_STATUS); + + assertAuditLogs(auditLogger, "GRANT ROLE role1 TO user1"); } @Test @@ -84,7 +121,7 @@ public void testFailedModificationIsIgnored() { DNAuditLogger auditLogger = mock(DNAuditLogger.class); UserRoleModificationAuditContext context = new UserRoleModificationAuditContext(auditLogger, "grant role"); - context.setClientSession(mockSession()); + context.setSessionInfo(mockSessionInfo()); AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); statement.setUserName("user1"); statement.setRoleName("role1"); @@ -100,7 +137,7 @@ public void testRedirectedModificationIsIgnored() { DNAuditLogger auditLogger = mock(DNAuditLogger.class); UserRoleModificationAuditContext context = new UserRoleModificationAuditContext(auditLogger, "grant role"); - context.setClientSession(mockSession()); + context.setSessionInfo(mockSessionInfo()); AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE); statement.setUserName("user1"); statement.setRoleName("role1"); @@ -116,7 +153,7 @@ public void testPrivilegeGrantIsIgnored() { DNAuditLogger auditLogger = mock(DNAuditLogger.class); UserRoleModificationAuditContext context = new UserRoleModificationAuditContext(auditLogger, "grant privilege"); - context.setClientSession(mockSession()); + context.setSessionInfo(mockSessionInfo()); context.track(new AuthorStatement(AuthorType.GRANT_USER)); context.log(RpcUtils.SUCCESS_STATUS); @@ -124,31 +161,46 @@ public void testPrivilegeGrantIsIgnored() { verify(auditLogger, never()).log(any(), any()); } - private static IClientSession mockSession() { - IClientSession session = mock(IClientSession.class); + private static SessionInfo mockSessionInfo() { + SessionInfo session = mock(SessionInfo.class); when(session.getUserId()).thenReturn(7L); - when(session.getUsername()).thenReturn("operator"); - when(session.getClientAddress()).thenReturn("127.0.0.1"); - when(session.getDatabaseName()).thenReturn("database"); + when(session.getUserName()).thenReturn("operator"); + when(session.getCliHostname()).thenReturn("127.0.0.1"); + when(session.getDatabaseName()).thenReturn(Optional.of("database")); return session; } @SuppressWarnings("unchecked") - private static void assertAuditLog(DNAuditLogger auditLogger, String sql) { + private static void assertAuditLogs(DNAuditLogger auditLogger, String sql) { ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(IAuditEntity.class); ArgumentCaptor> logCaptor = ArgumentCaptor.forClass(Supplier.class); - verify(auditLogger).log(entityCaptor.capture(), logCaptor.capture()); + verify(auditLogger, times(2)).log(entityCaptor.capture(), logCaptor.capture()); + + List entities = entityCaptor.getAllValues(); + List> logs = logCaptor.getAllValues(); + assertAuditLog(entities.get(0), sql, AuditEventType.MODIFY_SECURITY_FUNCTION); + assertAuditLog(entities.get(1), sql, AuditEventType.MODIFY_ROLE_MEMBERSHIP); + assertEquals( + String.format( + DataNodeMiscMessages + .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, + "user1", + "role1"), + logs.get(0).get()); + assertEquals( + String.format(DataNodeMiscMessages.LOG_USER_ARG_ROLE_ARG_422D48D3, "user1", "role1"), + logs.get(1).get()); + } - IAuditEntity entity = entityCaptor.getValue(); + private static void assertAuditLog(IAuditEntity entity, String sql, AuditEventType eventType) { assertEquals(7L, entity.getUserId()); assertEquals("operator", entity.getUsername()); assertEquals("127.0.0.1", entity.getCliHostname()); - assertEquals(AuditEventType.MODIFY_USER_ROLE, entity.getAuditEventType()); + assertEquals(eventType, entity.getAuditEventType()); assertEquals(AuditLogOperation.CONTROL, entity.getAuditLogOperation()); assertEquals(PrivilegeType.SECURITY, entity.getPrivilegeTypes().get(0)); assertTrue(entity.getResult()); assertEquals("database", entity.getDatabase()); assertEquals(sql, entity.getSqlString()); - assertEquals("user: user1, role: role1", logCaptor.getValue().get()); } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java index 4d8eb9c550e22..47eea37169ac1 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java @@ -37,7 +37,8 @@ public enum AuditEventType { LOGIN_FINAL, MODIFY_SECURITY_OPTIONS, MODIFY_DEFAULT_SECURITY_VALUES, - MODIFY_USER_ROLE, + MODIFY_SECURITY_FUNCTION, + MODIFY_ROLE_MEMBERSHIP, REVOKE_FAILED, GRANT_ROLE_FAILED, LOGIN_RESOURCE_RESTRICT, From 8c5bf01fa78d210fdc2f3772e2120dba31bb6fc9 Mon Sep 17 00:00:00 2001 From: libo Date: Fri, 31 Jul 2026 23:15:17 +0800 Subject: [PATCH 3/4] Fix security attribute audit event semantics --- .../iotdb/db/it/audit/IoTDBAuditLogBasicIT.java | 16 ++++++++-------- .../iotdb/db/i18n/DataNodeMiscMessages.java | 4 ++-- .../iotdb/db/i18n/DataNodeMiscMessages.java | 4 ++-- .../audit/UserRoleModificationAuditContext.java | 4 ++-- .../UserRoleModificationAuditContextTest.java | 4 ++-- .../iotdb/commons/audit/AuditEventType.java | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java index 547d7e37bef00..6ff0751bde66d 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java @@ -641,14 +641,14 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "u_0", "root", "127.0.0.1", - "MODIFY_SECURITY_FUNCTION", + "MODIFY_SECURITY_ATTRIBUTE", "CONTROL", "[SECURITY]", "GLOBAL", "true", "test", "GRANT ROLE role1 TO user2", - "security function: user-role membership, user: user2, role: role1"), + "security attribute: user-role membership, user: user2, role: role1"), Arrays.asList( "node_1", "u_0", @@ -1238,14 +1238,14 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "u_0", "root", "127.0.0.1", - "MODIFY_SECURITY_FUNCTION", + "MODIFY_SECURITY_ATTRIBUTE", "CONTROL", "[SECURITY]", "GLOBAL", "true", "null", "REVOKE ROLE role1 FROM user2", - "security function: user-role membership, user: user2, role: role1"), + "security attribute: user-role membership, user: user2, role: role1"), Arrays.asList( "node_1", "u_0", @@ -1859,9 +1859,9 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "[SECURITY]", "null", "CONTROL", - "security function: user-role membership, user: user2, role: role1", + "security attribute: user-role membership, user: user2, role: role1", "GRANT ROLE role1 TO user2", - "MODIFY_SECURITY_FUNCTION", + "MODIFY_SECURITY_ATTRIBUTE", "127.0.0.1", "root"), Arrays.asList( @@ -2429,9 +2429,9 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "[SECURITY]", "null", "CONTROL", - "security function: user-role membership, user: user2, role: role1", + "security attribute: user-role membership, user: user2, role: role1", "REVOKE ROLE role1 FROM user2", - "MODIFY_SECURITY_FUNCTION", + "MODIFY_SECURITY_ATTRIBUTE", "127.0.0.1", "root"), Arrays.asList( diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index 97947b140971f..9f52338f5be6b 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -123,8 +123,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_SUCCEED_FMT = "create new region %s succeed"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "user: %s, role: %s"; public static final String - LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = - "security function: user-role membership, user: %s, role: %s"; + LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233 = + "security attribute: user-role membership, user: %s, role: %s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index 0b105feab8c1f..758610a2d8b67 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -123,8 +123,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_SUCCEED_FMT = "创建新 region %s 成功"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "用户:%s,角色:%s"; public static final String - LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = - "安全功能:用户角色成员关系,用户:%s,角色:%s"; + LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233 = + "安全属性:用户角色成员关系,用户:%s,角色:%s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java index f4da86e655464..ce75a896b54a0 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java @@ -95,11 +95,11 @@ public void log(TSStatus status) { return; } logEvent( - AuditEventType.MODIFY_SECURITY_FUNCTION, + AuditEventType.MODIFY_SECURITY_ATTRIBUTE, () -> String.format( DataNodeMiscMessages - .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, + .LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233, targetUsername, targetRoleName)); logEvent( diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java index 350ce38c76061..502c21b6afa72 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java @@ -178,12 +178,12 @@ private static void assertAuditLogs(DNAuditLogger auditLogger, String sql) { List entities = entityCaptor.getAllValues(); List> logs = logCaptor.getAllValues(); - assertAuditLog(entities.get(0), sql, AuditEventType.MODIFY_SECURITY_FUNCTION); + assertAuditLog(entities.get(0), sql, AuditEventType.MODIFY_SECURITY_ATTRIBUTE); assertAuditLog(entities.get(1), sql, AuditEventType.MODIFY_ROLE_MEMBERSHIP); assertEquals( String.format( DataNodeMiscMessages - .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, + .LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233, "user1", "role1"), logs.get(0).get()); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java index 47eea37169ac1..9acd86f0e9bea 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java @@ -37,7 +37,7 @@ public enum AuditEventType { LOGIN_FINAL, MODIFY_SECURITY_OPTIONS, MODIFY_DEFAULT_SECURITY_VALUES, - MODIFY_SECURITY_FUNCTION, + MODIFY_SECURITY_ATTRIBUTE, MODIFY_ROLE_MEMBERSHIP, REVOKE_FAILED, GRANT_ROLE_FAILED, From 8392f8cd66262757d8466db2b14d3c15abb04023 Mon Sep 17 00:00:00 2001 From: libo Date: Sat, 1 Aug 2026 06:55:08 +0800 Subject: [PATCH 4/4] Revert "Fix security attribute audit event semantics" This reverts commit 8c5bf01fa78d210fdc2f3772e2120dba31bb6fc9. --- .../iotdb/db/it/audit/IoTDBAuditLogBasicIT.java | 16 ++++++++-------- .../iotdb/db/i18n/DataNodeMiscMessages.java | 4 ++-- .../iotdb/db/i18n/DataNodeMiscMessages.java | 4 ++-- .../audit/UserRoleModificationAuditContext.java | 4 ++-- .../UserRoleModificationAuditContextTest.java | 4 ++-- .../iotdb/commons/audit/AuditEventType.java | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java index 6ff0751bde66d..547d7e37bef00 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/audit/IoTDBAuditLogBasicIT.java @@ -641,14 +641,14 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "u_0", "root", "127.0.0.1", - "MODIFY_SECURITY_ATTRIBUTE", + "MODIFY_SECURITY_FUNCTION", "CONTROL", "[SECURITY]", "GLOBAL", "true", "test", "GRANT ROLE role1 TO user2", - "security attribute: user-role membership, user: user2, role: role1"), + "security function: user-role membership, user: user2, role: role1"), Arrays.asList( "node_1", "u_0", @@ -1238,14 +1238,14 @@ public static void closeConnectionCompletely(Connection connection) throws Inter "u_0", "root", "127.0.0.1", - "MODIFY_SECURITY_ATTRIBUTE", + "MODIFY_SECURITY_FUNCTION", "CONTROL", "[SECURITY]", "GLOBAL", "true", "null", "REVOKE ROLE role1 FROM user2", - "security attribute: user-role membership, user: user2, role: role1"), + "security function: user-role membership, user: user2, role: role1"), Arrays.asList( "node_1", "u_0", @@ -1859,9 +1859,9 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "[SECURITY]", "null", "CONTROL", - "security attribute: user-role membership, user: user2, role: role1", + "security function: user-role membership, user: user2, role: role1", "GRANT ROLE role1 TO user2", - "MODIFY_SECURITY_ATTRIBUTE", + "MODIFY_SECURITY_FUNCTION", "127.0.0.1", "root"), Arrays.asList( @@ -2429,9 +2429,9 @@ public void basicAuditLogTestForTableModel() throws SQLException, InterruptedExc "[SECURITY]", "null", "CONTROL", - "security attribute: user-role membership, user: user2, role: role1", + "security function: user-role membership, user: user2, role: role1", "REVOKE ROLE role1 FROM user2", - "MODIFY_SECURITY_ATTRIBUTE", + "MODIFY_SECURITY_FUNCTION", "127.0.0.1", "root"), Arrays.asList( diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index 9f52338f5be6b..97947b140971f 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -123,8 +123,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_SUCCEED_FMT = "create new region %s succeed"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "user: %s, role: %s"; public static final String - LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233 = - "security attribute: user-role membership, user: %s, role: %s"; + LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = + "security function: user-role membership, user: %s, role: %s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java index 758610a2d8b67..0b105feab8c1f 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java @@ -123,8 +123,8 @@ public final class DataNodeMiscMessages { public static final String CREATE_NEW_REGION_SUCCEED_FMT = "创建新 region %s 成功"; public static final String LOG_USER_ARG_ROLE_ARG_422D48D3 = "用户:%s,角色:%s"; public static final String - LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233 = - "安全属性:用户角色成员关系,用户:%s,角色:%s"; + LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D = + "安全功能:用户角色成员关系,用户:%s,角色:%s"; private DataNodeMiscMessages() {} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java index ce75a896b54a0..f4da86e655464 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContext.java @@ -95,11 +95,11 @@ public void log(TSStatus status) { return; } logEvent( - AuditEventType.MODIFY_SECURITY_ATTRIBUTE, + AuditEventType.MODIFY_SECURITY_FUNCTION, () -> String.format( DataNodeMiscMessages - .LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233, + .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, targetUsername, targetRoleName)); logEvent( diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java index 502c21b6afa72..350ce38c76061 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/UserRoleModificationAuditContextTest.java @@ -178,12 +178,12 @@ private static void assertAuditLogs(DNAuditLogger auditLogger, String sql) { List entities = entityCaptor.getAllValues(); List> logs = logCaptor.getAllValues(); - assertAuditLog(entities.get(0), sql, AuditEventType.MODIFY_SECURITY_ATTRIBUTE); + assertAuditLog(entities.get(0), sql, AuditEventType.MODIFY_SECURITY_FUNCTION); assertAuditLog(entities.get(1), sql, AuditEventType.MODIFY_ROLE_MEMBERSHIP); assertEquals( String.format( DataNodeMiscMessages - .LOG_SECURITY_ATTRIBUTE_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_D6DC8233, + .LOG_SECURITY_FUNCTION_USER_ROLE_MEMBERSHIP_USER_ARG_ROLE_ARG_0D500C5D, "user1", "role1"), logs.get(0).get()); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java index 9acd86f0e9bea..47eea37169ac1 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java @@ -37,7 +37,7 @@ public enum AuditEventType { LOGIN_FINAL, MODIFY_SECURITY_OPTIONS, MODIFY_DEFAULT_SECURITY_VALUES, - MODIFY_SECURITY_ATTRIBUTE, + MODIFY_SECURITY_FUNCTION, MODIFY_ROLE_MEMBERSHIP, REVOKE_FAILED, GRANT_ROLE_FAILED,