From 122523df8c302a2a1cc0efe8c73628a36a1ccaaf Mon Sep 17 00:00:00 2001 From: Paul Chandler Date: Wed, 25 Oct 2023 10:22:30 +0100 Subject: [PATCH 1/4] Audit prepare statements (#239) --- CHANGES.md | 1 + conf/audit.yaml | 5 + .../bss/cassandra/ecaudit/AuditAdapter.java | 26 ++++ .../cassandra/ecaudit/config/AuditConfig.java | 6 + .../ecaudit/config/AuditYamlConfig.java | 8 ++ .../suppressor/PrepareAuditOperation.java | 44 ++++++ .../bss/cassandra/ecaudit/facade/Auditor.java | 2 + .../ecaudit/facade/DefaultAuditor.java | 6 + .../cassandra/ecaudit/filter/AuditFilter.java | 2 + .../ecaudit/filter/DefaultAuditFilter.java | 6 + .../ecaudit/filter/role/RoleAuditFilter.java | 3 + .../ecaudit/filter/yaml/YamlAuditFilter.java | 6 + .../yamlandrole/YamlAndRoleAuditFilter.java | 6 + .../ecaudit/handler/AuditQueryHandler.java | 15 ++- .../TestAuditYamlConfigurationLoader.java | 2 + .../handler/TestAuditQueryHandler.java | 1 + .../test/resources/mock_configuration.yaml | 1 + .../querylogger/ITQueryLogger.java | 26 ++++ .../querylogger/PrepareAuditQueryLogger.java | 127 ++++++++++++++++++ .../src/test/resources/integration_audit.yaml | 2 + .../ecaudit/test/mode/ClientInitializer.java | 7 +- 21 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java create mode 100644 integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java diff --git a/CHANGES.md b/CHANGES.md index 37bd8249..87dc70d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ## Version 2.12.0 * Test with java11 (only flavor ecaudit_c4.0) - #214 +* Audit Prepare statements ## Version 2.11.0 diff --git a/conf/audit.yaml b/conf/audit.yaml index 3aa86505..59274987 100644 --- a/conf/audit.yaml +++ b/conf/audit.yaml @@ -149,3 +149,8 @@ whitelist_cache_update_interval_in_ms: 20000 # Maximum number of entries in the whitelist cache # Default to 10 x the value of roles_cache_max_entries (specified in cassandra.yaml) whitelist_cache_max_entries: 10000 + +# Whether to suppress the auditing of prepare statements +# Default is to suppress the audit statements this is to match the previous versions which do not audit prepare statements + +suppress_prepare_statements: true \ No newline at end of file diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java index b80c1946..0dbe1b23 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java @@ -30,6 +30,7 @@ import com.ericsson.bss.cassandra.ecaudit.entry.PreparedAuditOperation; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.BoundValueSuppressor; +import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.PrepareAuditOperation; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.utils.Exceptions; import org.apache.cassandra.cql3.BatchQueryOptions; @@ -108,6 +109,31 @@ public void auditRegular(String operation, ClientState state, Status status, lon } } + /** + * Audit a regular CQL statement. + * + * @param operation the CQL statement to audit + * @param state the client state accompanying the statement + * @param status the statement operation status + * @param timestamp the system timestamp for the request + */ + public void auditPrepare(String operation, ClientState state, Status status, long timestamp) + { + if (auditor.shouldLogForStatus(status) && auditor.shouldLogPrepareStatements()) + { + AuditEntry logEntry = entryBuilderFactory.createEntryBuilder(operation, state) + .client(state.getRemoteAddress()) + .coordinator(FBUtilities.getJustBroadcastAddress()) + .user(state.getUser().getName()) + .operation(new PrepareAuditOperation(operation)) + .status(status) + .timestamp(timestamp) + .build(); + + auditor.audit(logEntry); + } + } + /** * Audit a prepared statement. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java index 74b6d2da..964db060 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java @@ -132,6 +132,12 @@ public void setWhitelistCacheActiveUpdate(boolean whitelistCacheActiveUpdate) yamlConfig.setWhitelistCacheActiveUpdate(whitelistCacheActiveUpdate); } + public boolean isSuppressPrepareStatements() + { + loadConfigIfNeeded(); + return yamlConfig.isSuppressPrepareStatements(); + } + private synchronized void loadConfigIfNeeded() { if (yamlConfig == null) diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java index 5d60848d..35a4a6e9 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java @@ -53,6 +53,7 @@ public final class AuditYamlConfig public Integer whitelist_cache_update_interval_in_ms; public Integer whitelist_cache_max_entries; public Boolean whitelist_cache_active_update; + public Boolean suppress_prepare_statements; static AuditYamlConfig createWithoutFile() { @@ -170,4 +171,11 @@ public void setWhitelistCacheActiveUpdate(Boolean whitelistCacheActiveUpdate) { this.whitelist_cache_active_update = whitelistCacheActiveUpdate; } + + public Boolean isSuppressPrepareStatements() + { + return suppress_prepare_statements == null + ? Boolean.TRUE + : suppress_prepare_statements; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java new file mode 100644 index 00000000..692ca018 --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.entry.suppressor; + +import com.ericsson.bss.cassandra.ecaudit.common.record.AuditOperation; + +public class PrepareAuditOperation implements AuditOperation +{ + private final String operationString; + + /** + * Construct a new audit operation. + * @param operationString the operation/statement to wrap. + */ + public PrepareAuditOperation(String operationString) + { + this.operationString = "Prepared: " + operationString; + } + + @Override + public String getOperationString() + { + return operationString; + } + + @Override + public String getNakedOperationString() + { + return operationString; + } +} diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java index c23ae520..f601b239 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java @@ -53,6 +53,8 @@ public interface Auditor */ boolean shouldLogFailedBatchSummary(); + boolean shouldLogPrepareStatements(); + /** * Sets the log timing strategy to use. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java index 0ede13ae..20513368 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java @@ -122,6 +122,12 @@ public boolean shouldLogFailedBatchSummary() return logTimingStrategy.shouldLogFailedBatchSummary(); } + @Override + public boolean shouldLogPrepareStatements() + { + return filter.shouldLogPrepareStatements(); + } + @Override public void setLogTimingStrategy(LogTimingStrategy logTimingStrategy) { diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java index e35ef38b..65630d7c 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java @@ -37,4 +37,6 @@ public interface AuditFilter { * For example, use this method to create any required keyspaces/tables. */ void setup(); + + boolean shouldLogPrepareStatements(); } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java index 3cc2cf3e..2b66b805 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java @@ -32,4 +32,10 @@ public void setup() { // Intentionally left empty } + + @Override + public boolean shouldLogPrepareStatements() + { + return true; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java index 3903229d..26f4a376 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java @@ -74,6 +74,9 @@ public void setup() whitelistDataAccess.setup(); } + @Override + public boolean shouldLogPrepareStatements() { return true; } + /** * Returns true if the supplied log entry's role or any other role granted to it (directly or indirectly) is * white-listed for the log entry's specified operations and resource. diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java index b23686d2..15742522 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java @@ -53,4 +53,10 @@ public void setup() { whitelist = auditConfig.getYamlWhitelist(); } + + @Override + public boolean shouldLogPrepareStatements() + { + return !auditConfig.isSuppressPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java index 682c84d1..e2442cce 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java @@ -57,4 +57,10 @@ public void setup() yamlFilter.setup(); roleFilter.setup(); } + + @Override + public boolean shouldLogPrepareStatements() + { + return yamlFilter.shouldLogPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java index 0ca679f1..7497530d 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java @@ -199,7 +199,20 @@ private ResultMessage processBatchWithAudit(BatchStatement statement, List customPayload) throws RequestValidationException { - return wrappedQueryHandler.prepare(query, state, customPayload); + long timestamp = System.currentTimeMillis(); + auditAdapter.auditPrepare(query, state, Status.ATTEMPT, timestamp); + ResultMessage.Prepared preparedStatement; + try + { + preparedStatement = wrappedQueryHandler.prepare(query, state, customPayload); + } + catch (RuntimeException e) + { + auditAdapter.auditPrepare(query, state, Status.FAILED, timestamp); + throw e; + } + + return preparedStatement; } @Override diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java index 22e7950a..ab22d8b2 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java @@ -97,6 +97,7 @@ public void testDefaultConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(DatabaseDescriptor.getRolesValidity()); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(DatabaseDescriptor.getRolesUpdateInterval()); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(DatabaseDescriptor.getRolesCacheMaxEntries() * 10); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(true); } @Test @@ -122,6 +123,7 @@ public void testCustomConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(42); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(41); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(40); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(false); } @Test diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java index 47b08ab9..8fa60c1d 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java @@ -146,6 +146,7 @@ public void testPrepareAndGetPrepared() verify(mockHandler, times(1)).prepare(eq(query), eq(mockClientState), eq(customPayload)); verify(mockHandler, times(1)).getPrepared(eq(statementId)); + verify(mockAdapter, times(1)).auditPrepare(eq(query), eq(mockClientState), eq(Status.ATTEMPT), longThat(isCloseToNow())); } @Test diff --git a/ecaudit/src/test/resources/mock_configuration.yaml b/ecaudit/src/test/resources/mock_configuration.yaml index 248f54a1..5f69ef10 100644 --- a/ecaudit/src/test/resources/mock_configuration.yaml +++ b/ecaudit/src/test/resources/mock_configuration.yaml @@ -27,3 +27,4 @@ bound_value_suppressor: SuppressBlobs whitelist_cache_validity_in_ms: 42 whitelist_cache_update_interval_in_ms: 41 whitelist_cache_max_entries: 40 +suppress_prepare_statements: false diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java index fff0bdac..32339660 100644 --- a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java @@ -15,6 +15,7 @@ */ package com.ericsson.bss.cassandra.ecaudit.integration.querylogger; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -30,6 +31,8 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.UnauthorizedException; import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; @@ -41,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -94,6 +98,28 @@ public void testBasicStatement() assertThat(getLogEntries()).containsOnly("client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (42, 'Kalle')'"); } + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } @Test public void testGrantFails() diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java new file mode 100644 index 00000000..500222aa --- /dev/null +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java @@ -0,0 +1,127 @@ +/* + * Copyright 2020 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.integration.querylogger; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.servererrors.UnauthorizedException; +import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; +import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class PrepareAuditQueryLogger +{ + private static CqlSession session; + + @Captor + private ArgumentCaptor loggingEventCaptor; + + @Mock + private Appender mockAuditAppender; + + @BeforeClass + public static void beforeClass() throws Exception + { + CassandraDaemonForAuditTest cdt = CassandraDaemonForAuditTest.getInstance(); + session = cdt.createSession(); + } + + @Before + public void before() + { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).addAppender(mockAuditAppender); + } + + @After + public void after() + { + verifyNoMoreInteractions(mockAuditAppender); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).detachAppender(mockAuditAppender); + } + + @AfterClass + public static void afterClass() + { + session.close(); + } + + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } + + private void givenTable(String keyspace, String table) + { + session.execute("CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} AND DURABLE_WRITES = false"); + session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + "." + table + " (key int PRIMARY KEY, value text)"); + } + + private List getLogEntries() + { + verify(mockAuditAppender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + return loggingEventCaptor.getAllValues() + .stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } +} diff --git a/integration-test-query-logger/src/test/resources/integration_audit.yaml b/integration-test-query-logger/src/test/resources/integration_audit.yaml index cc45407d..010bd073 100644 --- a/integration-test-query-logger/src/test/resources/integration_audit.yaml +++ b/integration-test-query-logger/src/test/resources/integration_audit.yaml @@ -15,3 +15,5 @@ # whitelist_cache_validity_in_ms: 0 + +suppress_prepare_statements: false diff --git a/test-utils/src/main/java/com/ericsson/bss/cassandra/ecaudit/test/mode/ClientInitializer.java b/test-utils/src/main/java/com/ericsson/bss/cassandra/ecaudit/test/mode/ClientInitializer.java index 5f25be29..b0be4591 100644 --- a/test-utils/src/main/java/com/ericsson/bss/cassandra/ecaudit/test/mode/ClientInitializer.java +++ b/test-utils/src/main/java/com/ericsson/bss/cassandra/ecaudit/test/mode/ClientInitializer.java @@ -22,8 +22,8 @@ import org.apache.cassandra.auth.INetworkAuthorizer; import org.apache.cassandra.config.DatabaseDescriptor; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public final class ClientInitializer { @@ -36,11 +36,12 @@ private ClientInitializer() public static void beforeClass() { + // This method is used by multiple tests, so lenient() is required for tests that don't require the when statements DatabaseDescriptor.clientInitialization(true); DatabaseDescriptor.setAuthenticator(mock(IAuthenticator.class)); - when(authorizerMock.bulkLoader()).thenReturn(Collections::emptyMap); + lenient().when(authorizerMock.bulkLoader()).thenReturn(Collections::emptyMap); DatabaseDescriptor.setAuthorizer(authorizerMock); - when(networkAuthorizerMock.bulkLoader()).thenReturn(Collections::emptyMap); + lenient().when(networkAuthorizerMock.bulkLoader()).thenReturn(Collections::emptyMap); DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizerMock); } From a9c46c6f16017ba90206dedd8f87efefce99f495 Mon Sep 17 00:00:00 2001 From: Paul Chandler Date: Wed, 25 Oct 2023 10:30:47 +0100 Subject: [PATCH 2/4] Add auditing for prepare statements (#238) --- .gitignore | 1 + .idea/misc.xml | 2 +- CHANGES.md | 1 + conf/audit.yaml | 5 + .../bss/cassandra/ecaudit/AuditAdapter.java | 26 ++++ .../cassandra/ecaudit/config/AuditConfig.java | 6 + .../ecaudit/config/AuditYamlConfig.java | 10 +- .../suppressor/PrepareAuditOperation.java | 44 ++++++ .../bss/cassandra/ecaudit/facade/Auditor.java | 2 + .../ecaudit/facade/DefaultAuditor.java | 6 + .../cassandra/ecaudit/filter/AuditFilter.java | 2 + .../ecaudit/filter/DefaultAuditFilter.java | 6 + .../ecaudit/filter/role/RoleAuditFilter.java | 3 + .../ecaudit/filter/yaml/YamlAuditFilter.java | 6 + .../yamlandrole/YamlAndRoleAuditFilter.java | 6 + .../ecaudit/handler/AuditQueryHandler.java | 16 ++- .../TestAuditYamlConfigurationLoader.java | 2 + .../handler/TestAuditQueryHandler.java | 2 + .../test/resources/mock_configuration.yaml | 1 + .../querylogger/ITQueryLogger.java | 27 ++++ .../querylogger/PrepareAuditQueryLogger.java | 129 ++++++++++++++++++ .../src/test/resources/integration_audit.yaml | 2 + 22 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java create mode 100644 integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java diff --git a/.gitignore b/.gitignore index be816f6c..ed6bc8ac 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ target/ .idea *.iml dependency-reduced-pom.xml +.idea/ diff --git a/.idea/misc.xml b/.idea/misc.xml index 9896aeb3..86b97c7b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/CHANGES.md b/CHANGES.md index 685d94d5..d1dbc406 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ * Use SnakeYaml's SafeConstructor to avoid CVE-2022-1471 * Support escaping characters in log messages - #207 +* Add Audit Prepare statements - #226 ## Version 2.10.0 * Build with Cassandra 4.0.7 (only flavor ecaudit_c4.0) diff --git a/conf/audit.yaml b/conf/audit.yaml index 3aa86505..59274987 100644 --- a/conf/audit.yaml +++ b/conf/audit.yaml @@ -149,3 +149,8 @@ whitelist_cache_update_interval_in_ms: 20000 # Maximum number of entries in the whitelist cache # Default to 10 x the value of roles_cache_max_entries (specified in cassandra.yaml) whitelist_cache_max_entries: 10000 + +# Whether to suppress the auditing of prepare statements +# Default is to suppress the audit statements this is to match the previous versions which do not audit prepare statements + +suppress_prepare_statements: true \ No newline at end of file diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java index 31a49503..f20eb34d 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java @@ -30,6 +30,7 @@ import com.ericsson.bss.cassandra.ecaudit.entry.PreparedAuditOperation; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.BoundValueSuppressor; +import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.PrepareAuditOperation; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.utils.Exceptions; import org.apache.cassandra.cql3.BatchQueryOptions; @@ -108,6 +109,31 @@ public void auditRegular(String operation, ClientState state, Status status, lon } } + /** + * Audit a regular CQL statement. + * + * @param operation the CQL statement to audit + * @param state the client state accompanying the statement + * @param status the statement operation status + * @param timestamp the system timestamp for the request + */ + public void auditPrepare(String operation, ClientState state, Status status, long timestamp) + { + if (auditor.shouldLogForStatus(status) && auditor.shouldLogPrepareStatements()) + { + AuditEntry logEntry = entryBuilderFactory.createEntryBuilder(operation, state) + .client(state.getRemoteAddress()) + .coordinator(FBUtilities.getBroadcastAddress()) + .user(state.getUser().getName()) + .operation(new PrepareAuditOperation(operation)) + .status(status) + .timestamp(timestamp) + .build(); + + auditor.audit(logEntry); + } + } + /** * Audit a prepared statement. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java index 4001245f..ae3cb51c 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java @@ -120,6 +120,12 @@ public void setWhitelistCacheMaxEntries(int whitelistCacheMaxEntries) yamlConfig.setWhitelistCacheMaxEntries(whitelistCacheMaxEntries); } + public boolean isSuppressPrepareStatements() + { + loadConfigIfNeeded(); + return yamlConfig.isSuppressPrepareStatements(); + } + private synchronized void loadConfigIfNeeded() { if (yamlConfig == null) diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java index ff5d5e15..e00431a3 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java @@ -52,6 +52,7 @@ public final class AuditYamlConfig public Integer whitelist_cache_validity_in_ms; public Integer whitelist_cache_update_interval_in_ms; public Integer whitelist_cache_max_entries; + public Boolean suppress_prepare_statements; static AuditYamlConfig createWithoutFile() { @@ -157,4 +158,11 @@ public void setWhitelistCacheMaxEntries(Integer whitelistCacheMaxEntries) { this.whitelist_cache_max_entries = whitelistCacheMaxEntries; } -} + + public Boolean isSuppressPrepareStatements() + { + return suppress_prepare_statements == null + ? Boolean.TRUE + : suppress_prepare_statements; + } + } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java new file mode 100644 index 00000000..692ca018 --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.entry.suppressor; + +import com.ericsson.bss.cassandra.ecaudit.common.record.AuditOperation; + +public class PrepareAuditOperation implements AuditOperation +{ + private final String operationString; + + /** + * Construct a new audit operation. + * @param operationString the operation/statement to wrap. + */ + public PrepareAuditOperation(String operationString) + { + this.operationString = "Prepared: " + operationString; + } + + @Override + public String getOperationString() + { + return operationString; + } + + @Override + public String getNakedOperationString() + { + return operationString; + } +} diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java index c23ae520..f601b239 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java @@ -53,6 +53,8 @@ public interface Auditor */ boolean shouldLogFailedBatchSummary(); + boolean shouldLogPrepareStatements(); + /** * Sets the log timing strategy to use. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java index 0ede13ae..20513368 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java @@ -122,6 +122,12 @@ public boolean shouldLogFailedBatchSummary() return logTimingStrategy.shouldLogFailedBatchSummary(); } + @Override + public boolean shouldLogPrepareStatements() + { + return filter.shouldLogPrepareStatements(); + } + @Override public void setLogTimingStrategy(LogTimingStrategy logTimingStrategy) { diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java index e35ef38b..65630d7c 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java @@ -37,4 +37,6 @@ public interface AuditFilter { * For example, use this method to create any required keyspaces/tables. */ void setup(); + + boolean shouldLogPrepareStatements(); } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java index 3cc2cf3e..2b66b805 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java @@ -32,4 +32,10 @@ public void setup() { // Intentionally left empty } + + @Override + public boolean shouldLogPrepareStatements() + { + return true; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java index 3903229d..26f4a376 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java @@ -74,6 +74,9 @@ public void setup() whitelistDataAccess.setup(); } + @Override + public boolean shouldLogPrepareStatements() { return true; } + /** * Returns true if the supplied log entry's role or any other role granted to it (directly or indirectly) is * white-listed for the log entry's specified operations and resource. diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java index b23686d2..15742522 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java @@ -53,4 +53,10 @@ public void setup() { whitelist = auditConfig.getYamlWhitelist(); } + + @Override + public boolean shouldLogPrepareStatements() + { + return !auditConfig.isSuppressPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java index 682c84d1..e2442cce 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java @@ -57,4 +57,10 @@ public void setup() yamlFilter.setup(); roleFilter.setup(); } + + @Override + public boolean shouldLogPrepareStatements() + { + return yamlFilter.shouldLogPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java index 26e2cb18..1ec4e440 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java @@ -203,9 +203,21 @@ private ResultMessage processBatchWithAudit(BatchStatement statement, List customPayload) throws RequestValidationException { - return wrappedQueryHandler.prepare(query, state, customPayload); - } + long timestamp = System.currentTimeMillis(); + auditAdapter.auditPrepare(query, state.getClientState(), Status.ATTEMPT, timestamp); + ResultMessage.Prepared preparedStatement; + try + { + preparedStatement = wrappedQueryHandler.prepare(query, state, customPayload); + } + catch (RuntimeException e) + { + auditAdapter.auditPrepare(query, state.getClientState(), Status.FAILED, timestamp); + throw e; + } + return preparedStatement; + } @Override public ParsedStatement.Prepared getPrepared(MD5Digest id) { diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java index 22e7950a..ab22d8b2 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java @@ -97,6 +97,7 @@ public void testDefaultConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(DatabaseDescriptor.getRolesValidity()); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(DatabaseDescriptor.getRolesUpdateInterval()); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(DatabaseDescriptor.getRolesCacheMaxEntries() * 10); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(true); } @Test @@ -122,6 +123,7 @@ public void testCustomConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(42); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(41); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(40); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(false); } @Test diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java index 04d62cd3..4e0b4245 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java @@ -148,6 +148,7 @@ public void testPrepareAndGetPrepared() verify(mockHandler, times(1)).prepare(eq(query), eq(mockQueryState), eq(customPayload)); verify(mockHandler, times(1)).getPrepared(eq(statementId)); + verify(mockAdapter, times(1)).auditPrepare(eq(query), eq(mockClientState), eq(Status.ATTEMPT), longThat(isCloseToNow())); } @Test @@ -169,6 +170,7 @@ public void testPrepareAndGetPreparedWhenPreloaded() verify(mockHandler, times(1)).prepare(eq(query), eq(mockQueryState), eq(customPayload)); verify(mockHandler, times(1)).getPrepared(eq(statementId)); + verify(mockAdapter, times(1)).auditPrepare(eq(query), eq(mockClientState), eq(Status.ATTEMPT), longThat(isCloseToNow())); } @Test diff --git a/ecaudit/src/test/resources/mock_configuration.yaml b/ecaudit/src/test/resources/mock_configuration.yaml index 248f54a1..5f69ef10 100644 --- a/ecaudit/src/test/resources/mock_configuration.yaml +++ b/ecaudit/src/test/resources/mock_configuration.yaml @@ -27,3 +27,4 @@ bound_value_suppressor: SuppressBlobs whitelist_cache_validity_in_ms: 42 whitelist_cache_update_interval_in_ms: 41 whitelist_cache_max_entries: 40 +suppress_prepare_statements: false diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java index 747ca356..95fd39da 100644 --- a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java @@ -15,6 +15,7 @@ */ package com.ericsson.bss.cassandra.ecaudit.integration.querylogger; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -30,8 +31,10 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; +import com.datastax.driver.core.exceptions.InvalidQueryException; import com.datastax.driver.core.exceptions.UnauthorizedException; import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; @@ -43,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -88,6 +92,29 @@ public static void afterClass() session.close(); cluster.close(); } + + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } @Test public void testBasicStatement() diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java new file mode 100644 index 00000000..1c852d8e --- /dev/null +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java @@ -0,0 +1,129 @@ +/* + * Copyright 2020 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.integration.querylogger; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.exceptions.InvalidQueryException; +import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; +import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class PrepareAuditQueryLogger +{ + private static Cluster cluster; + private static Session session; + + @Captor + private ArgumentCaptor loggingEventCaptor; + + @Mock + private Appender mockAuditAppender; + + @BeforeClass + public static void beforeClass() throws Exception + { + CassandraDaemonForAuditTest cdt = CassandraDaemonForAuditTest.getInstance(); + cluster = cdt.createCluster(); + session = cluster.connect(); + } + + @Before + public void before() + { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).addAppender(mockAuditAppender); + } + + @After + public void after() + { + verifyNoMoreInteractions(mockAuditAppender); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).detachAppender(mockAuditAppender); + } + + @AfterClass + public static void afterClass() + { + session.close(); + } + + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } + + private void givenTable(String keyspace, String table) + { + session.execute("CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} AND DURABLE_WRITES = false"); + session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + "." + table + " (key int PRIMARY KEY, value text)"); + } + + private List getLogEntries() + { + verify(mockAuditAppender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + return loggingEventCaptor.getAllValues() + .stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } +} diff --git a/integration-test-query-logger/src/test/resources/integration_audit.yaml b/integration-test-query-logger/src/test/resources/integration_audit.yaml index cc45407d..010bd073 100644 --- a/integration-test-query-logger/src/test/resources/integration_audit.yaml +++ b/integration-test-query-logger/src/test/resources/integration_audit.yaml @@ -15,3 +15,5 @@ # whitelist_cache_validity_in_ms: 0 + +suppress_prepare_statements: false From 1fc3f009e502a65d10665f5dc30525e2c08b1eb1 Mon Sep 17 00:00:00 2001 From: Paul Chandler Date: Wed, 25 Oct 2023 10:50:40 +0100 Subject: [PATCH 3/4] Audit prepare statements (#237) --- CHANGES.md | 1 + conf/audit.yaml | 5 + .../bss/cassandra/ecaudit/AuditAdapter.java | 25 ++++ .../cassandra/ecaudit/config/AuditConfig.java | 6 + .../ecaudit/config/AuditYamlConfig.java | 10 +- .../suppressor/PrepareAuditOperation.java | 44 ++++++ .../bss/cassandra/ecaudit/facade/Auditor.java | 2 + .../ecaudit/facade/DefaultAuditor.java | 6 + .../cassandra/ecaudit/filter/AuditFilter.java | 2 + .../ecaudit/filter/DefaultAuditFilter.java | 6 + .../ecaudit/filter/role/RoleAuditFilter.java | 3 + .../ecaudit/filter/yaml/YamlAuditFilter.java | 6 + .../yamlandrole/YamlAndRoleAuditFilter.java | 5 + .../ecaudit/handler/AuditQueryHandler.java | 15 ++- .../TestAuditYamlConfigurationLoader.java | 2 + .../handler/TestAuditQueryHandler.java | 1 + .../test/resources/mock_configuration.yaml | 1 + .../querylogger/ITQueryLogger.java | 28 ++++ .../querylogger/PrepareAuditQueryLogger.java | 127 ++++++++++++++++++ .../src/test/resources/integration_audit.yaml | 2 + 20 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java create mode 100644 integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java diff --git a/CHANGES.md b/CHANGES.md index d1578f31..b4035c69 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Version 2.12.0 * Test with java11 (only flavor ecaudit_c4.0) - #214 +* Audit Prepare statements ## Version 2.11.0 diff --git a/conf/audit.yaml b/conf/audit.yaml index 3aa86505..59274987 100644 --- a/conf/audit.yaml +++ b/conf/audit.yaml @@ -149,3 +149,8 @@ whitelist_cache_update_interval_in_ms: 20000 # Maximum number of entries in the whitelist cache # Default to 10 x the value of roles_cache_max_entries (specified in cassandra.yaml) whitelist_cache_max_entries: 10000 + +# Whether to suppress the auditing of prepare statements +# Default is to suppress the audit statements this is to match the previous versions which do not audit prepare statements + +suppress_prepare_statements: true \ No newline at end of file diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java index b80c1946..a5433ab2 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapter.java @@ -30,6 +30,7 @@ import com.ericsson.bss.cassandra.ecaudit.entry.PreparedAuditOperation; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.BoundValueSuppressor; +import com.ericsson.bss.cassandra.ecaudit.entry.suppressor.PrepareAuditOperation; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.utils.Exceptions; import org.apache.cassandra.cql3.BatchQueryOptions; @@ -108,6 +109,30 @@ public void auditRegular(String operation, ClientState state, Status status, lon } } + /** + * Audit a regular CQL statement. + * + * @param operation the CQL statement to audit + * @param state the client state accompanying the statement + * @param status the statement operation status + * @param timestamp the system timestamp for the request + */ + public void auditPrepare(String operation, ClientState state, Status status, long timestamp) + { + if (auditor.shouldLogForStatus(status) && auditor.shouldLogPrepareStatements()) + { + AuditEntry logEntry = entryBuilderFactory.createEntryBuilder(operation, state) + .client(state.getRemoteAddress()) + .coordinator(FBUtilities.getJustBroadcastAddress()) + .user(state.getUser().getName()) + .operation(new PrepareAuditOperation(operation)) + .status(status) + .timestamp(timestamp) + .build(); + + auditor.audit(logEntry); + } + } /** * Audit a prepared statement. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java index 4001245f..ae3cb51c 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditConfig.java @@ -120,6 +120,12 @@ public void setWhitelistCacheMaxEntries(int whitelistCacheMaxEntries) yamlConfig.setWhitelistCacheMaxEntries(whitelistCacheMaxEntries); } + public boolean isSuppressPrepareStatements() + { + loadConfigIfNeeded(); + return yamlConfig.isSuppressPrepareStatements(); + } + private synchronized void loadConfigIfNeeded() { if (yamlConfig == null) diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java index ff5d5e15..e00431a3 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/config/AuditYamlConfig.java @@ -52,6 +52,7 @@ public final class AuditYamlConfig public Integer whitelist_cache_validity_in_ms; public Integer whitelist_cache_update_interval_in_ms; public Integer whitelist_cache_max_entries; + public Boolean suppress_prepare_statements; static AuditYamlConfig createWithoutFile() { @@ -157,4 +158,11 @@ public void setWhitelistCacheMaxEntries(Integer whitelistCacheMaxEntries) { this.whitelist_cache_max_entries = whitelistCacheMaxEntries; } -} + + public Boolean isSuppressPrepareStatements() + { + return suppress_prepare_statements == null + ? Boolean.TRUE + : suppress_prepare_statements; + } + } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java new file mode 100644 index 00000000..692ca018 --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/suppressor/PrepareAuditOperation.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.entry.suppressor; + +import com.ericsson.bss.cassandra.ecaudit.common.record.AuditOperation; + +public class PrepareAuditOperation implements AuditOperation +{ + private final String operationString; + + /** + * Construct a new audit operation. + * @param operationString the operation/statement to wrap. + */ + public PrepareAuditOperation(String operationString) + { + this.operationString = "Prepared: " + operationString; + } + + @Override + public String getOperationString() + { + return operationString; + } + + @Override + public String getNakedOperationString() + { + return operationString; + } +} diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java index c23ae520..f601b239 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/Auditor.java @@ -53,6 +53,8 @@ public interface Auditor */ boolean shouldLogFailedBatchSummary(); + boolean shouldLogPrepareStatements(); + /** * Sets the log timing strategy to use. * diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java index 0ede13ae..20513368 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/facade/DefaultAuditor.java @@ -122,6 +122,12 @@ public boolean shouldLogFailedBatchSummary() return logTimingStrategy.shouldLogFailedBatchSummary(); } + @Override + public boolean shouldLogPrepareStatements() + { + return filter.shouldLogPrepareStatements(); + } + @Override public void setLogTimingStrategy(LogTimingStrategy logTimingStrategy) { diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java index e35ef38b..65630d7c 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/AuditFilter.java @@ -37,4 +37,6 @@ public interface AuditFilter { * For example, use this method to create any required keyspaces/tables. */ void setup(); + + boolean shouldLogPrepareStatements(); } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java index 3cc2cf3e..2b66b805 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/DefaultAuditFilter.java @@ -32,4 +32,10 @@ public void setup() { // Intentionally left empty } + + @Override + public boolean shouldLogPrepareStatements() + { + return true; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java index 3903229d..26f4a376 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/role/RoleAuditFilter.java @@ -74,6 +74,9 @@ public void setup() whitelistDataAccess.setup(); } + @Override + public boolean shouldLogPrepareStatements() { return true; } + /** * Returns true if the supplied log entry's role or any other role granted to it (directly or indirectly) is * white-listed for the log entry's specified operations and resource. diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java index b23686d2..15742522 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yaml/YamlAuditFilter.java @@ -53,4 +53,10 @@ public void setup() { whitelist = auditConfig.getYamlWhitelist(); } + + @Override + public boolean shouldLogPrepareStatements() + { + return !auditConfig.isSuppressPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java index 682c84d1..62a9d8ae 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/filter/yamlandrole/YamlAndRoleAuditFilter.java @@ -57,4 +57,9 @@ public void setup() yamlFilter.setup(); roleFilter.setup(); } + @Override + public boolean shouldLogPrepareStatements() + { + return yamlFilter.shouldLogPrepareStatements(); + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java index 60f20638..8b54a578 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/handler/AuditQueryHandler.java @@ -199,7 +199,20 @@ private ResultMessage processBatchWithAudit(BatchStatement statement, List customPayload) throws RequestValidationException { - return wrappedQueryHandler.prepare(query, state, customPayload); + long timestamp = System.currentTimeMillis(); + auditAdapter.auditPrepare(query, state, Status.ATTEMPT, timestamp); + ResultMessage.Prepared preparedStatement; + try + { + preparedStatement = wrappedQueryHandler.prepare(query, state, customPayload); + } + catch (RuntimeException e) + { + auditAdapter.auditPrepare(query, state, Status.FAILED, timestamp); + throw e; + } + + return preparedStatement; } @Override diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java index 22e7950a..ab22d8b2 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/config/TestAuditYamlConfigurationLoader.java @@ -97,6 +97,7 @@ public void testDefaultConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(DatabaseDescriptor.getRolesValidity()); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(DatabaseDescriptor.getRolesUpdateInterval()); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(DatabaseDescriptor.getRolesCacheMaxEntries() * 10); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(true); } @Test @@ -122,6 +123,7 @@ public void testCustomConfiguration() assertThat(config.getWhitelistCacheValidity()).isEqualTo(42); assertThat(config.getWhitelistCacheUpdateInterval()).isEqualTo(41); assertThat(config.getWhitelistCacheMaxEntries()).isEqualTo(40); + assertThat(config.isSuppressPrepareStatements()).isEqualTo(false); } @Test diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java index 47b08ab9..8fa60c1d 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/handler/TestAuditQueryHandler.java @@ -146,6 +146,7 @@ public void testPrepareAndGetPrepared() verify(mockHandler, times(1)).prepare(eq(query), eq(mockClientState), eq(customPayload)); verify(mockHandler, times(1)).getPrepared(eq(statementId)); + verify(mockAdapter, times(1)).auditPrepare(eq(query), eq(mockClientState), eq(Status.ATTEMPT), longThat(isCloseToNow())); } @Test diff --git a/ecaudit/src/test/resources/mock_configuration.yaml b/ecaudit/src/test/resources/mock_configuration.yaml index 248f54a1..5f69ef10 100644 --- a/ecaudit/src/test/resources/mock_configuration.yaml +++ b/ecaudit/src/test/resources/mock_configuration.yaml @@ -27,3 +27,4 @@ bound_value_suppressor: SuppressBlobs whitelist_cache_validity_in_ms: 42 whitelist_cache_update_interval_in_ms: 41 whitelist_cache_max_entries: 40 +suppress_prepare_statements: false diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java index fff0bdac..6aef68ef 100644 --- a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/ITQueryLogger.java @@ -15,6 +15,7 @@ */ package com.ericsson.bss.cassandra.ecaudit.integration.querylogger; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -30,6 +31,8 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.UnauthorizedException; import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; @@ -41,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -95,6 +99,30 @@ public void testBasicStatement() assertThat(getLogEntries()).containsOnly("client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (42, 'Kalle')'"); } + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } + @Test public void testGrantFails() { diff --git a/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java new file mode 100644 index 00000000..7e4ed0ea --- /dev/null +++ b/integration-test-query-logger/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/querylogger/PrepareAuditQueryLogger.java @@ -0,0 +1,127 @@ +/* + * Copyright 2020 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecaudit.integration.querylogger; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.servererrors.UnauthorizedException; +import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; +import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class PrepareAuditQueryLogger +{ + private static CqlSession session; + + @Captor + private ArgumentCaptor loggingEventCaptor; + + @Mock + private Appender mockAuditAppender; + + @BeforeClass + public static void beforeClass() throws Exception + { + CassandraDaemonForAuditTest cdt = CassandraDaemonForAuditTest.getInstance(); + session = cdt.createSession(); + } + + @Before + public void before() + { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).addAppender(mockAuditAppender); + } + + @After + public void after() + { + verifyNoMoreInteractions(mockAuditAppender); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Slf4jAuditLogger.AUDIT_LOGGER_NAME).detachAppender(mockAuditAppender); + } + + @AfterClass + public static void afterClass() + { + session.close(); + } + + @Test + public void testPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + PreparedStatement prepared = session.prepare("INSERT INTO school.students (key, value) VALUES (?, ?)"); + session.execute(prepared.bind(42, "Kalle")); + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.students (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'INSERT INTO school.students (key, value) VALUES (?, ?)[42, 'Kalle']'"); + } + + @Test + public void testFailedPrepareStatement() + { + givenTable("school", "students"); + reset(mockAuditAppender); + + assertThatExceptionOfType(InvalidQueryException.class).isThrownBy(() -> session.prepare("INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)")); + + assertThat(getLogEntries()).containsOnly( "client:'127.0.0.1'|user:'anonymous'|status:'ATTEMPT'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'", + "client:'127.0.0.1'|user:'anonymous'|status:'FAILED'|operation:'Prepared: INSERT INTO school.invalidestudents (key, value) VALUES (?, ?)'"); + } + + private void givenTable(String keyspace, String table) + { + session.execute("CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} AND DURABLE_WRITES = false"); + session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + "." + table + " (key int PRIMARY KEY, value text)"); + } + + private List getLogEntries() + { + verify(mockAuditAppender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + return loggingEventCaptor.getAllValues() + .stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } +} diff --git a/integration-test-query-logger/src/test/resources/integration_audit.yaml b/integration-test-query-logger/src/test/resources/integration_audit.yaml index cc45407d..010bd073 100644 --- a/integration-test-query-logger/src/test/resources/integration_audit.yaml +++ b/integration-test-query-logger/src/test/resources/integration_audit.yaml @@ -15,3 +15,5 @@ # whitelist_cache_validity_in_ms: 0 + +suppress_prepare_statements: false From 57a3431bfb57c9a2ddb9370ee25656e4be39b356 Mon Sep 17 00:00:00 2001 From: tommystendahl Date: Tue, 31 Oct 2023 09:57:20 +0100 Subject: [PATCH 4/4] Align versions across the flavours (#243) --- CHANGES.md | 7 ++++++- common/pom.xml | 2 +- ecaudit/pom.xml | 2 +- eclog/pom.xml | 2 +- integration-test-allow-all-authorizer/pom.xml | 2 +- integration-test-query-logger/pom.xml | 2 +- integration-test-standard/pom.xml | 2 +- pom.xml | 2 +- test-utils/pom.xml | 2 +- 9 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d1dbc406..f1fcd255 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,15 @@ # Changes +## Version 3.1.0 (unreleased) + +* Add Audit Prepare statements - #226 + +## Version 3.0.0 (only flavor ecaudit_c4.1) + ## Version 2.11.0 * Use SnakeYaml's SafeConstructor to avoid CVE-2022-1471 * Support escaping characters in log messages - #207 -* Add Audit Prepare statements - #226 ## Version 2.10.0 * Build with Cassandra 4.0.7 (only flavor ecaudit_c4.0) diff --git a/common/pom.xml b/common/pom.xml index 71c73d10..0933fe6a 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT common_c3.11 jar diff --git a/ecaudit/pom.xml b/ecaudit/pom.xml index 6aebb41f..dd1766ca 100644 --- a/ecaudit/pom.xml +++ b/ecaudit/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT ecaudit_c3.11 jar diff --git a/eclog/pom.xml b/eclog/pom.xml index cd60624e..180ee57f 100644 --- a/eclog/pom.xml +++ b/eclog/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT eclog_c3.11 jar diff --git a/integration-test-allow-all-authorizer/pom.xml b/integration-test-allow-all-authorizer/pom.xml index aff38cf3..7cd8dea8 100644 --- a/integration-test-allow-all-authorizer/pom.xml +++ b/integration-test-allow-all-authorizer/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT integration-test-allow-all-authorizer_c3.11 diff --git a/integration-test-query-logger/pom.xml b/integration-test-query-logger/pom.xml index cd8833cc..382fa8e5 100644 --- a/integration-test-query-logger/pom.xml +++ b/integration-test-query-logger/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT integration-test-query-logger_c3.11 diff --git a/integration-test-standard/pom.xml b/integration-test-standard/pom.xml index 8c56cf32..0dea55fc 100644 --- a/integration-test-standard/pom.xml +++ b/integration-test-standard/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT integration-test-standard_c3.11 diff --git a/pom.xml b/pom.xml index 0075b680..4fd7e5fe 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT pom Ericsson Cassandra Audit diff --git a/test-utils/pom.xml b/test-utils/pom.xml index ed1df08b..61c46198 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -20,7 +20,7 @@ com.ericsson.bss.cassandra.ecaudit parent_c3.11 - 2.12.0-SNAPSHOT + 3.1.0-SNAPSHOT test-utils_c3.11 jar