Runtime-configurable YAML option to prohibit USE statements

patch by Caleb Rackliffe; reviewed by David Capwell for CASSANDRA-17318
This commit is contained in:
Caleb Rackliffe 2022-02-03 18:11:32 -06:00
parent df16b3750d
commit 28eea6e8cd
10 changed files with 104 additions and 16 deletions

View File

@ -1,4 +1,5 @@
4.1
* Runtime-configurable YAML option to prohibit USE statements (CASSANDRA-17318)
* When streaming sees a ClosedChannelException this triggers the disk failure policy (CASSANDRA-17116)
* Add a virtual table for exposing prepared statements metrics (CASSANDRA-17224)
* Remove python 2.x support from cqlsh (CASSANDRA-17242)

View File

@ -1550,6 +1550,9 @@ enable_transient_replication: false
# 'ALTER ... DROP COMPACT STORAGE' is considered experimental and is not recommended for production use.
enable_drop_compact_storage: false
# Whether or not USE <keyspace> is allowed. This is enabled by default to avoid failure on upgrade.
#use_statements_enabled: true
# When the client triggers a protocol exception or unknown issue (Cassandra bug) we increment
# a client metric showing this; this logic will exclude specific subnets from updating these
# metrics

View File

@ -411,6 +411,8 @@ public class Config
public volatile boolean enable_drop_compact_storage = false;
public volatile boolean use_statements_enabled = true;
/**
* Optionally disable asynchronous UDF execution.
* Disabling asynchronous UDF execution also implicitly disables the security-manager!

View File

@ -70,7 +70,6 @@ import org.apache.cassandra.security.EncryptionContext;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.service.CacheService.CacheType;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.commons.lang3.ArrayUtils;
@ -3810,4 +3809,18 @@ public class DatabaseDescriptor
conf.minimum_keyspace_rf = value;
}
public static boolean getUseStatementsEnabled()
{
return conf.use_statements_enabled;
}
public static void setUseStatementsEnabled(boolean enabled)
{
if (enabled != conf.use_statements_enabled)
{
logger.info("Setting use_statements_enabled to {}", enabled);
conf.use_statements_enabled = enabled;
}
}
}

View File

@ -19,8 +19,10 @@ package org.apache.cassandra.cql3.statements;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.transport.messages.ResultMessage;
@ -29,6 +31,7 @@ import org.apache.cassandra.service.QueryState;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class UseStatement extends CQLStatement.Raw implements CQLStatement
@ -50,12 +53,15 @@ public class UseStatement extends CQLStatement.Raw implements CQLStatement
state.validateLogin();
}
@Override
public void validate(ClientState state) throws InvalidRequestException
{
checkTrue(DatabaseDescriptor.getUseStatementsEnabled(), "USE statements prohibited. (see use_statements_enabled in cassandra.yaml)");
}
public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) throws InvalidRequestException
{
QueryProcessor.metrics.useStatementsExecuted.inc();
state.getClientState().setKeyspace(keyspace);
return new ResultMessage.SetKeyspace(keyspace);
}

View File

@ -32,6 +32,8 @@ public class CQLMetrics
public final Counter preparedStatementsExecuted;
public final Counter preparedStatementsEvicted;
public final Counter useStatementsExecuted;
public final Gauge<Integer> preparedStatementsCount;
public final Gauge<Double> preparedStatementsRatio;
@ -41,13 +43,9 @@ public class CQLMetrics
preparedStatementsExecuted = Metrics.counter(factory.createMetricName("PreparedStatementsExecuted"));
preparedStatementsEvicted = Metrics.counter(factory.createMetricName("PreparedStatementsEvicted"));
preparedStatementsCount = Metrics.register(factory.createMetricName("PreparedStatementsCount"), new Gauge<Integer>()
{
public Integer getValue()
{
return QueryProcessor.preparedStatementsCount();
}
});
useStatementsExecuted = Metrics.counter(factory.createMetricName("UseStatementsExecuted"));
preparedStatementsCount = Metrics.register(factory.createMetricName("PreparedStatementsCount"), QueryProcessor::preparedStatementsCount);
preparedStatementsRatio = Metrics.register(factory.createMetricName("PreparedStatementsRatio"), new RatioGauge()
{
public Ratio getRatio()

View File

@ -416,7 +416,6 @@ public class StorageProxy implements StorageProxyMBean
* {@link ConsistencyLevel#LOCAL_SERIAL}).
* @param consistencyForReplayCommits the consistency for the commit phase of "replayed" in-progress operations.
* @param consistencyForCommit the consistency for the commit phase of _this_ operation update.
* @param state the client state.
* @param queryStartNanoTime the nano time for the start of the query this is part of. This is the base time for
* timeouts.
* @param casMetrics the metrics to update for this operation.
@ -2981,4 +2980,16 @@ public class StorageProxy implements StorageProxyMBean
{
return Paxos.getPaxosVariant().toString();
}
@Override
public boolean getUseStatementsEnabled()
{
return DatabaseDescriptor.getUseStatementsEnabled();
}
@Override
public void setUseStatementsEnabled(boolean enabled)
{
DatabaseDescriptor.setUseStatementsEnabled(enabled);
}
}

View File

@ -121,4 +121,7 @@ public interface StorageProxyMBean
void setPaxosVariant(String variant);
String getPaxosVariant();
boolean getUseStatementsEnabled();
void setUseStatementsEnabled(boolean enabled);
}

View File

@ -17,15 +17,41 @@
*/
package org.apache.cassandra.cql3.validation.operations;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.exceptions.InvalidRequestException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class UseTest extends CQLTester
{
@Test
public void testUseStatementWithBindVariable() throws Throwable
{
DatabaseDescriptor.setUseStatementsEnabled(true);
assertInvalidSyntaxMessage("Bind variables cannot be used for keyspace names", "USE ?");
}
@Test
public void shouldRejectUseStatementWhenProhibited() throws Throwable
{
long useCountBefore = QueryProcessor.metrics.useStatementsExecuted.getCount();
try
{
DatabaseDescriptor.setUseStatementsEnabled(false);
execute("USE cql_test_keyspace");
fail("expected USE statement to fail with use_statements_enabled = false");
}
catch (InvalidRequestException e)
{
assertEquals(useCountBefore, QueryProcessor.metrics.useStatementsExecuted.getCount());
Assertions.assertThat(e).hasMessageContaining("USE statements prohibited");
}
}
}

View File

@ -20,12 +20,14 @@ package org.apache.cassandra.metrics;
import java.io.IOException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.Schema;
@ -33,12 +35,12 @@ import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class CQLMetricsTest extends SchemaLoader
{
private static EmbeddedCassandraService cassandra;
private static Cluster cluster;
private static Session session;
@ -47,7 +49,7 @@ public class CQLMetricsTest extends SchemaLoader
{
Schema.instance.clear();
cassandra = new EmbeddedCassandraService();
EmbeddedCassandraService cassandra = new EmbeddedCassandraService();
cassandra.start();
cluster = Cluster.builder().addContactPoint("127.0.0.1").withPort(DatabaseDescriptor.getNativeTransportPort()).build();
@ -57,11 +59,34 @@ public class CQLMetricsTest extends SchemaLoader
session.execute("CREATE TABLE IF NOT EXISTS junit.metricstest (id int PRIMARY KEY, val text);");
}
@Test
public void testConnectionWithUseDisabled()
{
long useCountBefore = QueryProcessor.metrics.useStatementsExecuted.getCount();
DatabaseDescriptor.setUseStatementsEnabled(false);
try (Session ignored = cluster.connect("junit"))
{
fail("expected USE statement to fail with use_statements_enabled = false");
}
catch (InvalidQueryException e)
{
Assert.assertEquals(useCountBefore, QueryProcessor.metrics.useStatementsExecuted.getCount());
assertTrue(e.getMessage().contains("USE statements prohibited"));
}
finally
{
DatabaseDescriptor.setUseStatementsEnabled(true);
}
}
@Test
public void testPreparedStatementsCount()
{
int n = QueryProcessor.metrics.preparedStatementsCount.getValue();
long useCountBefore = QueryProcessor.metrics.useStatementsExecuted.getCount();
session.execute("use junit");
Assert.assertEquals(useCountBefore + 1, QueryProcessor.metrics.useStatementsExecuted.getCount());
session.prepare("SELECT * FROM junit.metricstest WHERE id = ?");
assertEquals(n+2, (int) QueryProcessor.metrics.preparedStatementsCount.getValue());
}
@ -104,15 +129,15 @@ public class CQLMetricsTest extends SchemaLoader
clearMetrics();
PreparedStatement metricsStatement = session.prepare("INSERT INTO junit.metricstest (id, val) VALUES (?, ?)");
assertEquals(Double.NaN, QueryProcessor.metrics.preparedStatementsRatio.getValue());
assertEquals(Double.NaN, QueryProcessor.metrics.preparedStatementsRatio.getValue(), 0.0);
for (int i = 0; i < 10; i++)
session.execute(metricsStatement.bind(i, "val" + i));
assertEquals(1.0, QueryProcessor.metrics.preparedStatementsRatio.getValue());
assertEquals(1.0, QueryProcessor.metrics.preparedStatementsRatio.getValue(), 0.0);
for (int i = 0; i < 10; i++)
session.execute(String.format("INSERT INTO junit.metricstest (id, val) VALUES (%d, '%s')", i, "val" + i));
assertEquals(0.5, QueryProcessor.metrics.preparedStatementsRatio.getValue());
assertEquals(0.5, QueryProcessor.metrics.preparedStatementsRatio.getValue(), 0.0);
}
private void clearMetrics()