diff --git a/CHANGES.md b/CHANGES.md index 025678e9..db64fe43 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Version 2.3.0 +* Avoid logging BLOB values #126 * Configurable format of printed records with eclog tool #102 ## Version 2.2.0 diff --git a/conf/audit.yaml b/conf/audit.yaml index b2ee8b58..7bdfb50f 100644 --- a/conf/audit.yaml +++ b/conf/audit.yaml @@ -89,3 +89,24 @@ logger_backend: # - log_format: "{?client:'${CLIENT_IP}'|?}user:'${USER}'{?|batchId:'${BATCH_ID}'?}|status:'${STATUS}'|operation:'${OPERATION}'" # time_format: # time_zone: + + +# The column obfuscator determines how column values should be logged for prepared statements (i.e. bound values). +# The obfuscator is applied to all prepared statements regardless of which table is used. +# +# - ShowAllObfuscator -> Will not obfuscate. Prints all values for prepared statements. +# +# - HideBlobsObfuscator -> Will obfuscate all blob values. Values will be replaced by a ""-tag in the log. +# Collection types containing blobs will also be replaced, e.g. ">". +# This obfuscator can be useful since blobs can be very big and perhaps not that useful +# to log. +# +# Note - The obfuscator only impacts to the log message if the OPERATION field is logged (see log_format above). +# If OPERATION_NAKED is selected then no bound values will be logged. +# +# A custom column obfuscator can be plugged in by setting the full name of an obfuscator class implementing the +# ColumnObfuscator interface. +# +# By default the ShowAllObfuscator will be used. +# +column_obfuscator: ShowAllObfuscator diff --git a/doc/slf4j_logger.md b/doc/slf4j_logger.md index af310d2a..6c7d21b8 100644 --- a/doc/slf4j_logger.md +++ b/doc/slf4j_logger.md @@ -84,6 +84,17 @@ Which will generate logs entries like this (assuming Logback pattern does not co 2019-02-28 15:18:14.091-> client=127.0.0.1, user=cassandra, status=ATTEMPT, batch-id=6f3cae9b-f1f1-4a4c-baa2-ed168ee79f9d, operation='INSERT INTO ecks.ectbl (partk, clustk, value) VALUES (?, ?, ?)[2, '2', 'valid']' ``` +### Column Obfuscator + +A column obfuscator defining how column values should be logged for prepared statements. Using one of the non-default column obfuscators +can be useful to avoid logging sensitive or less useful information, e.g. large blobs. + +For more information about the available column obfuscators, see the ```audit.yaml``` reference documentation. +Use the example below will obfuscate/hide blobs values from being logged. +```YAML +column_obfuscator: HideBlobsObfuscator +``` +It is also possible to plugin a custom obfuscator. ## Configure Logback 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 854ee32e..64ad0150 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 @@ -22,11 +22,14 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import com.google.common.annotations.VisibleForTesting; + import com.ericsson.bss.cassandra.ecaudit.common.record.SimpleAuditOperation; import com.ericsson.bss.cassandra.ecaudit.common.record.Status; import com.ericsson.bss.cassandra.ecaudit.entry.AuditEntry; import com.ericsson.bss.cassandra.ecaudit.entry.PreparedAuditOperation; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.utils.Exceptions; import org.apache.cassandra.cql3.BatchQueryOptions; @@ -49,6 +52,7 @@ public class AuditAdapter private final Auditor auditor; private final AuditEntryBuilderFactory entryBuilderFactory; + private ColumnObfuscator columnObfuscator; private final Map idQueryCache = new ConcurrentHashMap<>(); @@ -57,11 +61,13 @@ public class AuditAdapter * * @param auditor the auditor to use * @param entryBuilderFactory the audit entry builder factory to use + * @param columnObfuscator the column obfuscator */ - AuditAdapter(Auditor auditor, AuditEntryBuilderFactory entryBuilderFactory) + AuditAdapter(Auditor auditor, AuditEntryBuilderFactory entryBuilderFactory, ColumnObfuscator columnObfuscator) { this.auditor = auditor; this.entryBuilderFactory = entryBuilderFactory; + this.columnObfuscator = columnObfuscator; } public static AuditAdapter getInstance() @@ -122,7 +128,7 @@ public void auditPrepared(MD5Digest id, CQLStatement statement, ClientState stat .client(state.getRemoteAddress()) .coordinator(FBUtilities.getBroadcastAddress()) .user(state.getUser().getName()) - .operation(new PreparedAuditOperation(idQueryCache.get(id), options)) + .operation(new PreparedAuditOperation(idQueryCache.get(id), options, columnObfuscator)) .status(status) .timestamp(timestamp) .build(); @@ -234,7 +240,7 @@ private Collection getBatchOperations(AuditEntry.Builder builder, Ba if (queryOrId instanceof MD5Digest) { entryBuilderFactory.updateBatchEntryBuilder(builder, batchStatement.getStatements().get(statementIndex)); - builder.operation(new PreparedAuditOperation(idQueryCache.get(queryOrId), options.forStatement(statementIndex))); + builder.operation(new PreparedAuditOperation(idQueryCache.get(queryOrId), options.forStatement(statementIndex), columnObfuscator)); batchOperations.add(builder.build()); } else @@ -253,4 +259,10 @@ public Auditor getAuditor() { return auditor; } + + @VisibleForTesting + public void setColumnObfuscator(ColumnObfuscator columnObfuscator) + { + this.columnObfuscator = columnObfuscator; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapterFactory.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapterFactory.java index 2b153d5b..e36dd809 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapterFactory.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/AuditAdapterFactory.java @@ -25,6 +25,7 @@ import com.ericsson.bss.cassandra.ecaudit.config.AuditConfig; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.facade.DefaultAuditor; import com.ericsson.bss.cassandra.ecaudit.filter.AuditFilter; @@ -36,6 +37,7 @@ import com.ericsson.bss.cassandra.ecaudit.obfuscator.PasswordObfuscator; import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.FBUtilities; /** * Factory class for creating configured instances of AuditAdapter. @@ -77,7 +79,9 @@ static AuditAdapter createAuditAdapter(AuditConfig auditConfig) Auditor auditor = new DefaultAuditor(logger, filter, obfuscator, logStrategy); AuditEntryBuilderFactory entryBuilderFactory = new AuditEntryBuilderFactory(); - return new AuditAdapter(auditor, entryBuilderFactory); + ColumnObfuscator columnObfuscator = createColumnObfuscator(auditConfig); + + return new AuditAdapter(auditor, entryBuilderFactory, columnObfuscator); } /** @@ -144,4 +148,15 @@ private static LogTimingStrategy getLogTimingStrategy(AuditConfig auditConfig) ? LogTimingStrategy.POST_LOGGING_STRATEGY : LogTimingStrategy.PRE_LOGGING_STRATEGY; } + + private static ColumnObfuscator createColumnObfuscator(AuditConfig auditConfig) + { + String obfuscatorClassName = auditConfig.getColumnObfuscator(); + if (!obfuscatorClassName.contains(".")) + { + String packageName = ColumnObfuscator.class.getPackage().getName(); + obfuscatorClassName = packageName + "." + obfuscatorClassName; + } + return FBUtilities.construct(obfuscatorClassName, "ColumnObfuscator"); + } } 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 3a502897..9cc2125a 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 @@ -76,6 +76,13 @@ public String getWrappedAuthorizer() return yamlConfig.getWrappedAuthorizer(); } + public String getColumnObfuscator() + { + loadConfigIfNeeded(); + + return yamlConfig.getColumnObfuscator(); + } + 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 44c2be95..e1ceed40 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 @@ -20,6 +20,7 @@ import java.util.Map; import java.util.stream.Collectors; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ShowAllObfuscator; import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; import org.apache.cassandra.config.ParameterizedClass; @@ -31,6 +32,7 @@ public final class AuditYamlConfig private static final List DEFAULT_WHITELIST = Collections.emptyList(); private static final ParameterizedClass DEFAULT_LOGGER_BACKEND = new ParameterizedClass(Slf4jAuditLogger.class.getCanonicalName(), Collections.emptyMap()); private static final String DEFAULT_WRAPPED_AUTHORIZER = "org.apache.cassandra.auth.CassandraAuthorizer"; + private static final String DEFAULT_COLUMN_OBFUSCATOR = ShowAllObfuscator.class.getName(); private boolean fromFile = true; @@ -40,6 +42,7 @@ public final class AuditYamlConfig public ParameterizedClass logger_backend; public LoggerTiming log_timing_strategy; public String wrapped_authorizer; + public String column_obfuscator; static AuditYamlConfig createWithoutFile() { @@ -99,4 +102,9 @@ String getWrappedAuthorizer() { return wrapped_authorizer != null ? wrapped_authorizer : DEFAULT_WRAPPED_AUTHORIZER; } + + String getColumnObfuscator() + { + return column_obfuscator != null ? column_obfuscator : DEFAULT_COLUMN_OBFUSCATOR; + } } diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/CqlLiteralVersionAdapter.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/CqlLiteralVersionAdapter.java index 836cc81d..efa57c47 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/CqlLiteralVersionAdapter.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/CqlLiteralVersionAdapter.java @@ -23,6 +23,7 @@ import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.db.marshal.AsciiType; +import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.TimestampType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.serializers.TimestampSerializer; @@ -54,6 +55,10 @@ public static String toCQLLiteral(ByteBuffer serializedValue, ColumnSpecificatio { return DATE_FORMAT.format(TimestampSerializer.instance.deserialize(serializedValue)); } + if (isBlobType(column)) + { + return "0x" + column.type.getString(serializedValue); + } return column.type.getString(serializedValue); } @@ -73,6 +78,11 @@ private static boolean isTimestampType(ColumnSpecification column) return column.type instanceof TimestampType; } + private static boolean isBlobType(ColumnSpecification column) + { + return column.type instanceof BytesType; + } + private static DateFormat createDateFormat() { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault(Locale.Category.FORMAT)); diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/PreparedAuditOperation.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/PreparedAuditOperation.java index 4d62e472..84d82660 100644 --- a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/PreparedAuditOperation.java +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/PreparedAuditOperation.java @@ -20,6 +20,7 @@ import java.util.Queue; import com.ericsson.bss.cassandra.ecaudit.common.record.AuditOperation; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.QueryOptions; @@ -37,6 +38,7 @@ public class PreparedAuditOperation implements AuditOperation private final String preparedStatement; private final QueryOptions options; private String effectiveStatement; // lazy initialization + private final ColumnObfuscator columnObfuscator; /** * Construct a new prepared audit operation based on the prepared statement and options. @@ -45,11 +47,14 @@ public class PreparedAuditOperation implements AuditOperation * the prepared statement * @param options * the query options of an operation + * @param columnObfuscator + * the column obfuscator to process bound values */ - public PreparedAuditOperation(String preparedStatement, QueryOptions options) + public PreparedAuditOperation(String preparedStatement, QueryOptions options, ColumnObfuscator columnObfuscator) { this.preparedStatement = preparedStatement; this.options = options; + this.columnObfuscator = columnObfuscator; } @Override @@ -87,9 +92,10 @@ private String preparedWithValues() Queue values = new LinkedList<>(options.getValues()); for (ColumnSpecification column : options.getColumnSpecifications()) { - String value = CqlLiteralVersionAdapter.toCQLLiteral(values.remove(), column); - - fullStatement.append(value).append(", "); + ByteBuffer value = values.remove(); + String valueString = columnObfuscator.obfuscate(column, value) + .orElseGet(() -> CqlLiteralVersionAdapter.toCQLLiteral(value, column)); + fullStatement.append(valueString).append(", "); } fullStatement.setLength(fullStatement.length() - 1); diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ColumnObfuscator.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ColumnObfuscator.java new file mode 100644 index 00000000..98fb3f89 --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ColumnObfuscator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 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.obfuscator; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import org.apache.cassandra.cql3.ColumnSpecification; + +/** + * Column obfuscator used to handle prepared statement bound values. + */ +public interface ColumnObfuscator +{ + /** + * Creates an obfuscated string that represent the column value only IF the column should be obfuscated. + * + * @param column the column to check + * @param value the value that may be obfuscated + * @return the obfuscated string representation of the column value, or {@link Optional#empty()} if the value + * should not be obfuscated. + */ + Optional obfuscate(ColumnSpecification column, ByteBuffer value); +} diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/HideBlobsObfuscator.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/HideBlobsObfuscator.java new file mode 100644 index 00000000..67039694 --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/HideBlobsObfuscator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 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.obfuscator; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import org.apache.cassandra.cql3.CQL3Type; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.db.marshal.AbstractType; + +public class HideBlobsObfuscator implements ColumnObfuscator +{ + @Override + public Optional obfuscate(ColumnSpecification column, ByteBuffer value) + { + return isBlobType(column.type) || isCollectionContainingBlobType(column.type) + ? Optional.of("<" + column.type.asCQL3Type() + ">") + : Optional.empty(); + } + + private static boolean isBlobType(AbstractType type) + { + return type.asCQL3Type() == CQL3Type.Native.BLOB; + } + + private static boolean isCollectionContainingBlobType(AbstractType type) + { + return type.isCollection() && type.asCQL3Type().toString().contains("blob"); + } +} diff --git a/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ShowAllObfuscator.java b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ShowAllObfuscator.java new file mode 100644 index 00000000..6cacc7fc --- /dev/null +++ b/ecaudit/src/main/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/ShowAllObfuscator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.obfuscator; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import org.apache.cassandra.cql3.ColumnSpecification; + +public class ShowAllObfuscator implements ColumnObfuscator +{ + @Override + public Optional obfuscate(ColumnSpecification column, ByteBuffer value) + { + return Optional.empty(); // No values should be obfuscated + } +} diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapter.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapter.java index a6b71b77..4e1cdeba 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapter.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapter.java @@ -40,6 +40,7 @@ import com.ericsson.bss.cassandra.ecaudit.common.record.Status; import com.ericsson.bss.cassandra.ecaudit.entry.AuditEntry; import com.ericsson.bss.cassandra.ecaudit.entry.factory.AuditEntryBuilderFactory; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.test.mode.ClientInitializer; import org.apache.cassandra.auth.AuthenticatedUser; @@ -109,6 +110,8 @@ public class TestAuditAdapter private BatchStatement mockBatchStatement; @Mock private BatchQueryOptions mockBatchOptions; + @Mock + private ColumnObfuscator mockColumnObfuscator; private InetAddress clientAddress; private InetSocketAddress clientSocketAddress; @@ -128,7 +131,7 @@ public void before() throws UnknownHostException { clientAddress = InetAddress.getByName(CLIENT_IP); clientSocketAddress = new InetSocketAddress(clientAddress, CLIENT_PORT); - auditAdapter = new AuditAdapter(mockAuditor, mockAuditEntryBuilderFactory); + auditAdapter = new AuditAdapter(mockAuditor, mockAuditEntryBuilderFactory, mockColumnObfuscator); when(mockState.getUser()).thenReturn(mockUser); when(mockAuditor.shouldLogForStatus(any(Status.class))).thenReturn(true); } diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapterFactory.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapterFactory.java index c42c1246..a4f97e46 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapterFactory.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/TestAuditAdapterFactory.java @@ -18,9 +18,11 @@ import java.io.File; import java.lang.reflect.Field; import java.net.URL; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; @@ -33,6 +35,8 @@ import com.ericsson.bss.cassandra.ecaudit.config.AuditConfig; import com.ericsson.bss.cassandra.ecaudit.config.AuditYamlConfigurationLoader; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ShowAllObfuscator; import com.ericsson.bss.cassandra.ecaudit.facade.Auditor; import com.ericsson.bss.cassandra.ecaudit.facade.DefaultAuditor; import com.ericsson.bss.cassandra.ecaudit.facade.TestDefaultAuditor; @@ -48,6 +52,7 @@ import com.ericsson.bss.cassandra.ecaudit.obfuscator.PasswordObfuscator; import com.ericsson.bss.cassandra.ecaudit.test.mode.ClientInitializer; import org.apache.cassandra.config.ParameterizedClass; +import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.exceptions.ConfigurationException; import org.mockito.junit.MockitoJUnitRunner; @@ -232,6 +237,37 @@ public void testLogTimingStrategy() throws Exception assertThat(logTimingStrategyIn(adapterWithPostLogging)).isSameAs(LogTimingStrategy.POST_LOGGING_STRATEGY); } + @Test + public void testCreateColumnObfuscatorThrows() + { + // Given + AuditConfig config = givenAuditConfigWithColumnObfuscator("com.obfuscator.InvalidObfuscatorClass"); + // Then throw + assertThatExceptionOfType(ConfigurationException.class) + .isThrownBy(() -> AuditAdapterFactory.createAuditAdapter(config)) + .withMessageContaining("Unable to find ColumnObfuscator class") + .withMessageContaining("com.obfuscator.InvalidObfuscatorClass"); + } + + @Test + public void testCreateCustomColumnObfuscator() throws Exception + { + // Given + AuditConfig config = givenAuditConfigWithColumnObfuscator(CustomTestObfuscator.class.getName()); + // When + AuditAdapter adapter = AuditAdapterFactory.createAuditAdapter(config); + // Then + assertThat(columnObfuscatorIn(adapter)).isInstanceOf(CustomTestObfuscator.class); + } + + public static class CustomTestObfuscator implements ColumnObfuscator + { + + public Optional obfuscate(ColumnSpecification column, ByteBuffer value) + { + return Optional.empty(); + } + } private static String getPathToTestResourceFile() { URL url = TestAuditAdapterFactory.class.getResource("/mock_configuration.yaml"); @@ -243,6 +279,7 @@ private static AuditConfig givenAuditConfig(String s, Map parame ParameterizedClass parameterizedClass = new ParameterizedClass(s, parameters); AuditConfig auditConfig = mock(AuditConfig.class); when(auditConfig.getLoggerBackendParameters()).thenReturn(parameterizedClass); + when(auditConfig.getColumnObfuscator()).thenReturn(ShowAllObfuscator.class.getName()); return auditConfig; } @@ -278,4 +315,18 @@ private static LogTimingStrategy logTimingStrategyIn(AuditAdapter auditAdapter) { return TestDefaultAuditor.getLogTimingStrategy(auditAdapter.getAuditor()); } + + private AuditConfig givenAuditConfigWithColumnObfuscator(String obfuscatorName) + { + AuditConfig config = givenAuditConfig("com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger", Collections.emptyMap()); + when(config.getColumnObfuscator()).thenReturn(obfuscatorName); + return config; + } + + private static ColumnObfuscator columnObfuscatorIn(AuditAdapter adapter) throws Exception + { + Field field = AuditAdapter.class.getDeclaredField("columnObfuscator"); + field.setAccessible(true); + return (ColumnObfuscator) field.get(adapter); + } } diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestCqlLiteralVersionAdapter.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestCqlLiteralVersionAdapter.java index 7a127272..174b3424 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestCqlLiteralVersionAdapter.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestCqlLiteralVersionAdapter.java @@ -26,6 +26,7 @@ import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AsciiType; import org.apache.cassandra.db.marshal.BooleanType; +import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.TimestampType; import org.apache.cassandra.db.marshal.UTF8Type; @@ -60,6 +61,7 @@ public Object[][] testToCQLLiteral_parameters() { AsciiType.instance, AsciiType.instance.fromString("Anka"), "'Anka'" }, { TimestampType.instance, TimestampType.instance.fromTimeInMillis(42), "1970-01-01T00:00:00.042Z" }, // 42 ms after EPOCH { BooleanType.instance, BooleanType.instance.fromString("True"), "true" }, + { BytesType.instance, BytesType.instance.fromString("AABBCCDD"), "0xaabbccdd" }, }; } } diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestPreparedAuditOperation.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestPreparedAuditOperation.java index 15b91682..4bee1c87 100644 --- a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestPreparedAuditOperation.java +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/TestPreparedAuditOperation.java @@ -18,11 +18,14 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ShowAllObfuscator; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.QueryOptions; @@ -31,6 +34,7 @@ import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,24 +42,27 @@ @RunWith(MockitoJUnitRunner.StrictStubs.class) public class TestPreparedAuditOperation { + private static final ColumnObfuscator OBFUSCATOR = new ShowAllObfuscator(); @Mock private QueryOptions mockOptions; + @Mock + private ColumnObfuscator mockObfuscator; @Test public void testThatValuesAreBound() { String preparedStatement = "select value1, value2 from ks.cf where pk = ? and ck = ?"; - String expectedStatement = "select value1, value2 from ks.cf where pk = ? and ck = ?['text', 'text']"; + String expectedStatement = "select value1, value2 from ks.cf where pk = ? and ck = ?['text1', 'text2']"; - List values = createValues("text", "text"); - ImmutableList columns = createTextColumns("text", "text"); + List values = createValues("text1", "text2"); + ImmutableList columns = createTextColumns("col1", "col2"); when(mockOptions.hasColumnSpecifications()).thenReturn(true); when(mockOptions.getColumnSpecifications()).thenReturn(columns); when(mockOptions.getValues()).thenReturn(values); PreparedAuditOperation auditOperation; - auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions); + auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions, OBFUSCATOR); assertThat(auditOperation.getOperationString()).isEqualTo(expectedStatement); assertThat(auditOperation.getNakedOperationString()).isEqualTo(preparedStatement); @@ -65,17 +72,17 @@ public void testThatValuesAreBound() public void testThatValuesAreBoundWithFixedValues() { String preparedStatement = "select value1, value2 from ks.cf where pk = ? and ck = 'text'"; - String expectedStatement = "select value1, value2 from ks.cf where pk = ? and ck = 'text'['text']"; + String expectedStatement = "select value1, value2 from ks.cf where pk = ? and ck = 'text'['text1']"; - List values = createValues("text"); - ImmutableList columns = createTextColumns("text"); + List values = createValues("text1"); + ImmutableList columns = createTextColumns("col1"); when(mockOptions.hasColumnSpecifications()).thenReturn(true); when(mockOptions.getColumnSpecifications()).thenReturn(columns); when(mockOptions.getValues()).thenReturn(values); PreparedAuditOperation auditOperation; - auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions); + auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions, OBFUSCATOR); assertThat(auditOperation.getOperationString()).isEqualTo(expectedStatement); assertThat(auditOperation.getNakedOperationString()).isEqualTo(preparedStatement); @@ -95,7 +102,7 @@ public void testThatAdditionalValuesAreLogged() when(mockOptions.getValues()).thenReturn(values); PreparedAuditOperation auditOperation; - auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions); + auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions, OBFUSCATOR); assertThat(auditOperation.getOperationString()).isEqualTo(expectedStatement); assertThat(auditOperation.getNakedOperationString()).isEqualTo(preparedStatement); @@ -109,7 +116,7 @@ public void testFallbackWhenColumnSpecIsMissing() when(mockOptions.hasColumnSpecifications()).thenReturn(false); PreparedAuditOperation auditOperation; - auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions); + auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions, OBFUSCATOR); assertThat(auditOperation.getOperationString()).isEqualTo(preparedStatement); assertThat(auditOperation.getNakedOperationString()).isEqualTo(preparedStatement); @@ -118,6 +125,28 @@ public void testFallbackWhenColumnSpecIsMissing() verify(mockOptions, times(0)).getColumnSpecifications(); } + @Test + public void testObfuscator() + { + String preparedStatement = "insert into ks1.t1 (k1, k2, k3) values (?, ?, ?)"; + String expectedStatement = "insert into ks1.t1 (k1, k2, k3) values (?, ?, ?)[, 'text2', ]"; + + List values = createValues("text1", "text2", "text3"); + ImmutableList columns = createTextColumns("col1", "col2", "col3"); + + when(mockOptions.hasColumnSpecifications()).thenReturn(true); + when(mockOptions.getColumnSpecifications()).thenReturn(columns); + when(mockOptions.getValues()).thenReturn(values); + + when(mockObfuscator.obfuscate(eq(columns.get(0)), eq(values.get(0)))).thenReturn(Optional.of("")); + when(mockObfuscator.obfuscate(eq(columns.get(1)), eq(values.get(1)))).thenReturn(Optional.empty()); + when(mockObfuscator.obfuscate(eq(columns.get(2)), eq(values.get(2)))).thenReturn(Optional.of("")); + + PreparedAuditOperation auditOperation = new PreparedAuditOperation(preparedStatement, mockOptions, mockObfuscator); + + assertThat(auditOperation.getOperationString()).isEqualTo(expectedStatement); + } + private List createValues(String... values) { List rawValues = new ArrayList<>(); diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestHideBlobsObfuscator.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestHideBlobsObfuscator.java new file mode 100644 index 00000000..dd1f684d --- /dev/null +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestHideBlobsObfuscator.java @@ -0,0 +1,123 @@ +/* + * Copyright 2019 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.obfuscator; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.BytesType; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests the {@link HideBlobsObfuscator} class. + */ +@RunWith(JUnitParamsRunner.class) +public class TestHideBlobsObfuscator +{ + private static final ColumnSpecification BLOB_COLUMN = createColumn(BytesType.instance); + private static final ColumnSpecification BLOB_LIST_COLUMN = createColumn(listOf(BytesType.instance)); + private static final ColumnSpecification BLOB_SET_COLUMN = createColumn(setOf(BytesType.instance)); + private static final ColumnSpecification BLOB_TEXT_MAP_COLUMN = createColumn(mapOf(BytesType.instance, UTF8Type.instance)); + private static final ColumnSpecification TEXT_BLOB_MAP_COLUMN = createColumn(mapOf(UTF8Type.instance, BytesType.instance)); + + private static final ColumnSpecification TEXT_COLUMN = createColumn(UTF8Type.instance); + private static final ColumnSpecification TEXT_LIST_COLUMN = createColumn(listOf(UTF8Type.instance)); + private static final ColumnSpecification TEXT_SET_COLUMN = createColumn(setOf(UTF8Type.instance)); + private static final ColumnSpecification TEXT_MAP_COLUMN = createColumn(mapOf(UTF8Type.instance, UTF8Type.instance)); + + @Test + @Parameters(method = "testBlobsAreHidden_parameters") + public void testBlobsAreHidden(ColumnSpecification column, String expectedString) + { + // Given + ByteBuffer valueMock = Mockito.mock(ByteBuffer.class); + ColumnObfuscator obfuscator = new HideBlobsObfuscator(); + // When + Optional result = obfuscator.obfuscate(column, valueMock); + // Then + assertThat(result).contains(expectedString); + verifyZeroInteractions(valueMock); + } + + public Object[][] testBlobsAreHidden_parameters() + { + return new Object[][]{ + { BLOB_COLUMN, "" }, + { BLOB_LIST_COLUMN, ">" }, + { BLOB_SET_COLUMN, ">" }, + { BLOB_TEXT_MAP_COLUMN, ">" }, + { TEXT_BLOB_MAP_COLUMN, ">" }, + }; + } + + @Test + @Parameters(method = "testNonBlobsColumns_parameters") + public void testNonBlobsColumns(ColumnSpecification column) + { + // Given + ByteBuffer valueMock = Mockito.mock(ByteBuffer.class); + ColumnObfuscator obfuscator = new HideBlobsObfuscator(); + // When + Optional result = obfuscator.obfuscate(column, valueMock); + // Then + assertThat(result).isEmpty(); + verifyZeroInteractions(valueMock); + } + + public Object[][] testNonBlobsColumns_parameters() + { + return new Object[][]{ + { TEXT_COLUMN }, + { TEXT_LIST_COLUMN }, + { TEXT_SET_COLUMN }, + { TEXT_MAP_COLUMN }, + }; + } + + private static ColumnSpecification createColumn(AbstractType type) + { + return new ColumnSpecification("ks", "cf", null, type); + } + + private static ListType listOf(AbstractType type) + { + return ListType.getInstance(type, true); + } + + private static SetType setOf(AbstractType type) + { + return SetType.getInstance(type, true); + } + + private static MapType mapOf(AbstractType keyType, AbstractType valueType) + { + return MapType.getInstance(keyType, valueType, true); + } +} diff --git a/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestShowAllObfuscator.java b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestShowAllObfuscator.java new file mode 100644 index 00000000..3b4b7f57 --- /dev/null +++ b/ecaudit/src/test/java/com/ericsson/bss/cassandra/ecaudit/entry/obfuscator/TestShowAllObfuscator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 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.obfuscator; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests the {@link ShowAllObfuscator} class. + */ +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class TestShowAllObfuscator +{ + @Mock + ByteBuffer valueMock; + + @Test + public void testObfuscatorNeverObfuscates() + { + // Given + ColumnObfuscator obfuscator = new ShowAllObfuscator(); + // When + Optional result = obfuscator.obfuscate(null, valueMock); + // Then + assertThat(result).isEmpty(); + verifyZeroInteractions(valueMock); + } +} diff --git a/integration-test-standard/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/custom/ITVerifyCustomColumnObfuscator.java b/integration-test-standard/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/custom/ITVerifyCustomColumnObfuscator.java new file mode 100644 index 00000000..1e75f3fd --- /dev/null +++ b/integration-test-standard/src/test/java/com/ericsson/bss/cassandra/ecaudit/integration/custom/ITVerifyCustomColumnObfuscator.java @@ -0,0 +1,180 @@ +/* + * Copyright 2019 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.custom; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Collections; +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.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SimpleStatement; +import com.ericsson.bss.cassandra.ecaudit.AuditAdapter; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ColumnObfuscator; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.HideBlobsObfuscator; +import com.ericsson.bss.cassandra.ecaudit.entry.obfuscator.ShowAllObfuscator; +import com.ericsson.bss.cassandra.ecaudit.logger.AuditLogger; +import com.ericsson.bss.cassandra.ecaudit.logger.Slf4jAuditLogger; +import com.ericsson.bss.cassandra.ecaudit.test.daemon.CassandraDaemonForAuditTest; +import net.jcip.annotations.NotThreadSafe; +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.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * This test class provides a functional integration test for custom column obfuscator log format with Cassandra itself. + *

+ * The format configuration is read when the plugin is started and has to be setup before the embedded Cassandra + * is started. Therefor this test class cannot be run in the same process as the other integration tests (having + * legacy log formatting). + *

+ * Use simple audit log format - with only OPERATION, to make assertions simpler: + * {@code operation='${OPERATION}'} + *
+ */ +@NotThreadSafe +@RunWith(MockitoJUnitRunner.class) +public class ITVerifyCustomColumnObfuscator +{ + private static final String CUSTOM_LOGGER_NAME = "ECAUDIT_CUSTOM"; + private static final String KEYSPACE = "CREATE KEYSPACE ks1 WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} AND DURABLE_WRITES = false"; + private static final String TABLE = "CREATE TABLE ks1.t1 (key1 text, key2 int, val1 text, val2 blob, val4 int, PRIMARY KEY((key1, key2), val1))"; + + private static Cluster cluster; + private static Session session; + private static AuditLogger customLogger; + + @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(); + + session.execute(new SimpleStatement(KEYSPACE)); + session.execute(new SimpleStatement(TABLE)); + + // Configure logger with custom format with only operation + customLogger = new Slf4jAuditLogger(Collections.singletonMap("log_format", "operation=${OPERATION}"), CUSTOM_LOGGER_NAME); + AuditAdapter.getInstance().getAuditor().addLogger(customLogger); + } + + @Before + public void before() + { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(CUSTOM_LOGGER_NAME).addAppender(mockAuditAppender); + } + + @After + public void after() + { + verifyNoMoreInteractions(mockAuditAppender); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(CUSTOM_LOGGER_NAME).detachAppender(mockAuditAppender); + } + + @AfterClass + public static void afterClass() + { + AuditAdapter.getInstance().getAuditor().removeLogger(customLogger); + session.close(); + cluster.close(); + } + + @Test + public void testShowAllObfuscator() + { + // Given + setColumnObfuscator(new ShowAllObfuscator()); + // When + executePreparedStatement(); + // Then + assertThat(getSingleLogEntry()).contains("operation=INSERT INTO ks1.t1 (key1, key2, val1, val2, val4) VALUES (?, ?, ?, ?, ?)" + + "['PartKey1', 42, 'ClusterKey', 0x00000001000000020000000300000004, 43]"); + } + + @Test + public void testHideBlobsObfuscator() + { + // Given + setColumnObfuscator(new HideBlobsObfuscator()); + // When + executePreparedStatement(); + // Then + assertThat(getSingleLogEntry()).contains("operation=INSERT INTO ks1.t1 (key1, key2, val1, val2, val4) VALUES (?, ?, ?, ?, ?)" + + "['PartKey1', 42, 'ClusterKey', , 43]"); + } + + private List getSingleLogEntry() + { + verify(mockAuditAppender, atLeastOnce()).doAppend(loggingEventCaptor.capture()); + return loggingEventCaptor.getAllValues().stream().map(ILoggingEvent::getFormattedMessage).collect(Collectors.toList()); + } + + private void executePreparedStatement() + { + String insert = "INSERT INTO ks1.t1 (key1, key2, val1, val2, val4) VALUES (?, ?, ?, ?, ?)"; + + PreparedStatement preparedInsert = session.prepare(insert); + session.execute(preparedInsert.bind("PartKey1", + 42, + "ClusterKey", + createBlob(16), + 43)); + } + + private Buffer createBlob(int capacityInBytes) + { + ByteBuffer buffer = ByteBuffer.allocate(capacityInBytes); + for (int i = 1; i <= capacityInBytes / 4; i++) + { + buffer.putInt(i); + } + return buffer.flip(); + } + + private void setColumnObfuscator(ColumnObfuscator obfuscator) + { + AuditAdapter.getInstance().setColumnObfuscator(obfuscator); + } +}