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
@@ -0,0 +1,91 @@
/*
* 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.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 role-grant statement and records an audit event only when it fails. */
public class GrantRoleFailureAuditContext {

private final DNAuditLogger auditLogger;
private final String sqlString;

private IClientSession clientSession;
private String targetUsername;

public GrantRoleFailureAuditContext(String sqlString) {
this(DNAuditLogger.getInstance(), sqlString);
}

GrantRoleFailureAuditContext(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) {
targetUsername = statement.getUserName();
}
}

public void track(RelationalAuthorStatement statement) {
if (statement.getAuthorType() == AuthorRType.GRANT_USER_ROLE) {
targetUsername = statement.getUserName();
}
}

public void log(TSStatus status) {
if (targetUsername == null || clientSession == null || isSuccessfulOrRedirected(status)) {
return;
}
auditLogger.log(
new AuditLogFields(
clientSession.getUserId(),
clientSession.getUsername(),
clientSession.getClientAddress(),
AuditEventType.GRANT_ROLE_FAILED,
AuditLogOperation.CONTROL,
PrivilegeType.SECURITY,
false,
clientSession.getDatabaseName(),
sqlString),
() -> targetUsername);
}

private static boolean isSuccessfulOrRedirected(TSStatus status) {
return status != null
&& (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
|| status.getCode() == TSStatusCode.REDIRECTION_RECOMMEND.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.GrantRoleFailureAuditContext;
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) {
GrantRoleFailureAuditContext grantRoleFailureAuditContext =
new GrantRoleFailureAuditContext(request.getSql());
TSExecuteStatementResp response = null;
try {
response =
executeStatementInternalWithoutGrantRoleFailureAudit(
request, setResult, grantRoleFailureAuditContext);
return response;
} finally {
grantRoleFailureAuditContext.log(response == null ? null : response.getStatus());
}
}

private TSExecuteStatementResp executeStatementInternalWithoutGrantRoleFailureAudit(
NativeStatementRequest request,
SelectResult setResult,
GrantRoleFailureAuditContext grantRoleFailureAuditContext) {
IClientSession clientSession = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime();
grantRoleFailureAuditContext.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) {
grantRoleFailureAuditContext.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) {
grantRoleFailureAuditContext.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);
GrantRoleFailureAuditContext grantRoleFailureAuditContext =
new GrantRoleFailureAuditContext(statement);
grantRoleFailureAuditContext.setClientSession(clientSession);
TSStatus auditStatus = null;
long t2 = System.nanoTime();
String type = null;
OperationQuota quota = null;
Expand All @@ -2152,6 +2183,9 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) {
ExecutionResult result;
if (clientSession.getSqlDialect() == SqlDialect.TREE) {
Statement s = StatementGenerator.createStatement(statement, clientSession.getZoneId());
if (s instanceof AuthorStatement) {
grantRoleFailureAuditContext.track((AuthorStatement) s);
}
if (s == null) {
return RpcUtils.getStatus(
TSStatusCode.EXECUTE_STATEMENT_ERROR, "This operation type is not supported");
Expand Down Expand Up @@ -2181,6 +2215,7 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) {
clientSession.getClientAddress())
.setSqlString(statement));
if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
auditStatus = status;
return status;
}

Expand Down Expand Up @@ -2223,6 +2258,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) {
grantRoleFailureAuditContext.track((RelationalAuthorStatement) s);
}

if (s instanceof Use) {
useDatabase = true;
Expand Down Expand Up @@ -2266,15 +2304,18 @@ public TSStatus executeBatchStatement(TSExecuteBatchStatementReq req) {
}
}

results.add(result.status);
auditStatus = result.status;
results.add(auditStatus);
} catch (Exception e) {
LOGGER.warn(DataNodeMiscMessages.ERROR_EXECUTING_BATCH_STATEMENT, e);
TSStatus status =
onQueryException(
e, "\"" + statement + "\". " + OperationType.EXECUTE_BATCH_STATEMENT);
isAllSuccessful = false;
results.add(status);
auditStatus = status;
results.add(auditStatus);
} finally {
grantRoleFailureAuditContext.log(auditStatus);
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,149 @@
/*
* 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.assertFalse;
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 GrantRoleFailureAuditContextTest {

@Test
public void testTreeGrantRoleFailure() {
DNAuditLogger auditLogger = mock(DNAuditLogger.class);
GrantRoleFailureAuditContext context = newContext(auditLogger);
AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE);
statement.setUserName("user1");

context.track(statement);
context.log(RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));

assertAuditLog(auditLogger);
}

@Test
public void testTableGrantRoleFailure() {
DNAuditLogger auditLogger = mock(DNAuditLogger.class);
GrantRoleFailureAuditContext context = newContext(auditLogger);
RelationalAuthorStatement statement =
new RelationalAuthorStatement(AuthorRType.GRANT_USER_ROLE);
statement.setUserName("user1");

context.track(statement);
context.log(RpcUtils.getStatus(TSStatusCode.ROLE_NOT_EXIST));

assertAuditLog(auditLogger);
}

@Test
public void testSuccessfulGrantIsIgnored() {
DNAuditLogger auditLogger = mock(DNAuditLogger.class);
GrantRoleFailureAuditContext context = newContext(auditLogger);
context.track(treeGrantRoleStatement());

context.log(RpcUtils.SUCCESS_STATUS);

verify(auditLogger, never()).log(any(), any());
}

@Test
public void testRedirectedGrantIsIgnored() {
DNAuditLogger auditLogger = mock(DNAuditLogger.class);
GrantRoleFailureAuditContext context = newContext(auditLogger);
context.track(treeGrantRoleStatement());

context.log(RpcUtils.getStatus(TSStatusCode.REDIRECTION_RECOMMEND));

verify(auditLogger, never()).log(any(), any());
}

@Test
public void testPrivilegeGrantIsIgnored() {
DNAuditLogger auditLogger = mock(DNAuditLogger.class);
GrantRoleFailureAuditContext context = newContext(auditLogger);
context.track(new AuthorStatement(AuthorType.GRANT_USER));

context.log(RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));

verify(auditLogger, never()).log(any(), any());
}

private static GrantRoleFailureAuditContext newContext(DNAuditLogger auditLogger) {
GrantRoleFailureAuditContext context =
new GrantRoleFailureAuditContext(auditLogger, "GRANT ROLE role1 TO user1");
context.setClientSession(mockSession());
return context;
}

private static AuthorStatement treeGrantRoleStatement() {
AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER_ROLE);
statement.setUserName("user1");
return statement;
}

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) {
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.GRANT_ROLE_FAILED, entity.getAuditEventType());
assertEquals(AuditLogOperation.CONTROL, entity.getAuditLogOperation());
assertEquals(PrivilegeType.SECURITY, entity.getPrivilegeTypes().get(0));
assertFalse(entity.getResult());
assertEquals("database", entity.getDatabase());
assertEquals("GRANT ROLE role1 TO user1", entity.getSqlString());
assertEquals("user1", logCaptor.getValue().get());
}
}