Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 =
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IAuditEntity> entityCaptor = ArgumentCaptor.forClass(IAuditEntity.class);
ArgumentCaptor<Supplier<String>> 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());
}
}
Loading