diff --git a/CHANGES.txt b/CHANGES.txt index 0f46880950..302ac8b936 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,6 +6,7 @@ * Fix COPY ... TO STDOUT behavior in cqlsh (CASSANDRA-18353) * Remove six and Py2SaferScanner merge cruft (CASSANDRA-18354) Merged from 4.0: + * Improve nodetool enable{audit,fullquery}log (CASSANDRA-18550) * Report network cache info in nodetool (CASSANDRa-18400) * Partial compaction can resurrect deleted data (CASSANDRA-18507) * Allow internal address to change with reconnecting snitches (CASSANDRA-16718) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index a15de953c0..4b2711cfb7 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1588,6 +1588,8 @@ audit_logging_options: # max_log_size: 17179869184 # 16 GiB ## archive command is "/path/to/script.sh %path" where %path is replaced with the file being rolled: # archive_command: + ## note that enabling this allows anyone with JMX/nodetool access to run local shell commands as the user running cassandra + # allow_nodetool_archive_command: false # max_archive_retries: 10 # validate tombstones on reads and compaction diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index d2c529c3d4..a04e85c1bc 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -3706,6 +3706,11 @@ public class DatabaseDescriptor return conf.full_query_logging_options; } + public static void setFullQueryLogOptions(FullQueryLoggerOptions options) + { + conf.full_query_logging_options = options; + } + public static boolean getBlockForPeersInRemoteDatacenters() { return conf.block_for_peers_in_remote_dcs; diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 87cfbac046..ed10f04773 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -6398,7 +6398,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE String includedUsers, String excludedUsers, Integer maxArchiveRetries, Boolean block, String rollCycle, Long maxLogSize, Integer maxQueueWeight, String archiveCommand) throws IllegalStateException { - final AuditLogOptions options = new AuditLogOptions.Builder(DatabaseDescriptor.getAuditLoggingOptions()) + AuditLogOptions auditOptions = DatabaseDescriptor.getAuditLoggingOptions(); + if (archiveCommand != null && !auditOptions.allow_nodetool_archive_command) + throw new ConfigurationException("Can't enable audit log archiving via nodetool unless audit_logging_options.allow_nodetool_archive_command is set to true"); + + final AuditLogOptions options = new AuditLogOptions.Builder(auditOptions) .withEnabled(true) .withLogger(loggerName, parameters) .withIncludedKeyspaces(includedKeyspaces) @@ -6501,6 +6505,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE blocking = blocking != null ? blocking : fqlOptions.block; maxQueueWeight = maxQueueWeight != Integer.MIN_VALUE ? maxQueueWeight : fqlOptions.max_queue_weight; maxLogSize = maxLogSize != Long.MIN_VALUE ? maxLogSize : fqlOptions.max_log_size; + if (archiveCommand != null && !fqlOptions.allow_nodetool_archive_command) + throw new ConfigurationException("Can't enable full query log archiving via nodetool unless full_query_logging_options.allow_nodetool_archive_command is set to true"); archiveCommand = archiveCommand != null ? archiveCommand : fqlOptions.archive_command; maxArchiveRetries = maxArchiveRetries != Integer.MIN_VALUE ? maxArchiveRetries : fqlOptions.max_archive_retries; diff --git a/src/java/org/apache/cassandra/tools/nodetool/EnableAuditLog.java b/src/java/org/apache/cassandra/tools/nodetool/EnableAuditLog.java index ae0bb42d3d..cf9d05ee64 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/EnableAuditLog.java +++ b/src/java/org/apache/cassandra/tools/nodetool/EnableAuditLog.java @@ -62,7 +62,9 @@ public class EnableAuditLog extends NodeToolCmd private long maxLogSize = Long.MIN_VALUE; @Option(title = "archive_command", name = {"--archive-command"}, description = "Command that will handle archiving rolled audit log files." + - " Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive") + " Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive" + + " Enable this by setting the audit_logging_options.allow_nodetool_archive_command: true in the config.") + private String archiveCommand = null; @Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.") diff --git a/src/java/org/apache/cassandra/tools/nodetool/EnableFullQueryLog.java b/src/java/org/apache/cassandra/tools/nodetool/EnableFullQueryLog.java index 50848946e0..511730d269 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/EnableFullQueryLog.java +++ b/src/java/org/apache/cassandra/tools/nodetool/EnableFullQueryLog.java @@ -42,7 +42,8 @@ public class EnableFullQueryLog extends NodeToolCmd private String path = null; @Option(title = "archive_command", name = {"--archive-command"}, description = "Command that will handle archiving rolled full query log files." + - " Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive") + " Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive" + + " Enable this by setting the full_query_logging_options.allow_nodetool_archive_command: true in the config.") private String archiveCommand = null; @Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.") diff --git a/src/java/org/apache/cassandra/utils/binlog/BinLogOptions.java b/src/java/org/apache/cassandra/utils/binlog/BinLogOptions.java index 8005ca38ef..614d19031c 100644 --- a/src/java/org/apache/cassandra/utils/binlog/BinLogOptions.java +++ b/src/java/org/apache/cassandra/utils/binlog/BinLogOptions.java @@ -23,6 +23,13 @@ import org.apache.commons.lang3.StringUtils; public class BinLogOptions { public String archive_command = StringUtils.EMPTY; + + /** + * enable if a user should be able to set the archive command via nodetool/jmx + * + * do not make this a hotprop. + */ + public boolean allow_nodetool_archive_command = false; /** * How often to roll BinLog segments so they can potentially be reclaimed. Available options are: * MINUTELY, HOURLY, DAILY, LARGE_DAILY, XLARGE_DAILY, HUGE_DAILY. diff --git a/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java b/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java index b4ae02b6da..1ee66f7340 100644 --- a/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java +++ b/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java @@ -21,6 +21,7 @@ import org.junit.After; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import org.junit.Assert; import org.junit.Before; @@ -716,6 +717,34 @@ public class AuditLoggerTest extends CQLTester assertEquals(0, AuthEvents.instance.listenerCount()); } + @Test + public void testJMXArchiveCommand() + { + disableAuditLogOptions(); + AuditLogOptions options = new AuditLogOptions(); + + try + { + StorageService.instance.enableAuditLog("BinAuditLogger", Collections.emptyMap(), "", "", "", "", + "", "", 10, true, options.roll_cycle, + 1000L, 1000, "/xyz/not/null"); + fail("not allowed"); + } + catch (ConfigurationException e) + { + assertTrue(e.getMessage().contains("Can't enable audit log archiving via nodetool")); + } + + options.archive_command = "/xyz/not/null"; + options.audit_logs_dir = "/tmp/abc"; + DatabaseDescriptor.setAuditLoggingOptions(options); + StorageService.instance.enableAuditLog("BinAuditLogger", Collections.emptyMap(), "", "", "", "", + "", "", 10, true, options.roll_cycle, + 1000L, 1000, null); + assertTrue(AuditLogManager.instance.isEnabled()); + assertEquals("/xyz/not/null", AuditLogManager.instance.getAuditLogOptions().archive_command); + } + /** * Helper methods for Audit Log CQL Testing */ diff --git a/test/unit/org/apache/cassandra/fql/FullQueryLoggerTest.java b/test/unit/org/apache/cassandra/fql/FullQueryLoggerTest.java index 0b9230bfd1..c874a65e62 100644 --- a/test/unit/org/apache/cassandra/fql/FullQueryLoggerTest.java +++ b/test/unit/org/apache/cassandra/fql/FullQueryLoggerTest.java @@ -43,15 +43,18 @@ import net.openhft.chronicle.queue.RollCycles; import net.openhft.chronicle.wire.ValueIn; import net.openhft.chronicle.wire.WireOut; import org.apache.cassandra.Util; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.statements.BatchStatement; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.fql.FullQueryLogger.Query; import org.apache.cassandra.fql.FullQueryLogger.Batch; import org.apache.cassandra.cql3.statements.BatchStatement.Type; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ObjectSizes; import org.apache.cassandra.utils.binlog.BinLogTest; @@ -73,6 +76,7 @@ import static org.apache.cassandra.fql.FullQueryLogger.SINGLE_QUERY; import static org.apache.cassandra.fql.FullQueryLogger.TYPE; import static org.apache.cassandra.fql.FullQueryLogger.VALUES; import static org.apache.cassandra.fql.FullQueryLogger.VERSION; +import static org.junit.Assert.fail; public class FullQueryLoggerTest extends CQLTester { @@ -669,6 +673,29 @@ public class FullQueryLoggerTest extends CQLTester logQuery("", QueryOptions.DEFAULT, queryState(), -1); } + @Test + public void testJMXArchiveCommand() + { + FullQueryLoggerOptions options = new FullQueryLoggerOptions(); + + try + { + StorageService.instance.enableFullQueryLogger(options.log_dir, options.roll_cycle, false, 1000, 1000, "/xyz/not/null", 0); + fail("not allowed"); + } + catch (ConfigurationException e) + { + assertTrue(e.getMessage().contains("Can't enable full query log archiving via nodetool")); + } + + options.archive_command = "/xyz/not/null"; + options.log_dir = "/tmp/abc"; + DatabaseDescriptor.setFullQueryLogOptions(options); + StorageService.instance.enableFullQueryLogger(options.log_dir, options.roll_cycle, false, 1000, 1000, null, 0); + assertTrue(FullQueryLogger.instance.isEnabled()); + assertEquals("/xyz/not/null", FullQueryLogger.instance.getFullQueryLoggerOptions().archive_command); + } + private static void compareQueryOptions(QueryOptions a, QueryOptions b) { assertEquals(a.getClass(), b.getClass());