Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CASSANDRA-19556 Guardrail to block DDL/DCL queries (4.1) #3248

Open
wants to merge 2 commits into
base: cassandra-4.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
4.1.5
* Guardrail to block DDL/DCL queries
* Fix hints delivery for a node going down repeatedly (CASSANDRA-19495)
* Do not go to disk for reading hints file sizes (CASSANDRA-19477)
* Fix system_views.settings to handle array types (CASSANDRA-19475)
Expand Down
5 changes: 5 additions & 0 deletions conf/cassandra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,11 @@ drop_compact_storage_enabled: false
# group_by_enabled: true
# Guardrail to allow/disallow TRUNCATE and DROP TABLE statements
# drop_truncate_table_enabled: true
#
# Guardrail to allow/disallow DDL/DCL statements
# ddl_enabled: true
# dcl_enabled: true
#
# Guardrail to warn or fail when using a page size greater than threshold.
# The two thresholds default to -1 to disable.
# page_size_warn_threshold: -1
Expand Down
2 changes: 2 additions & 0 deletions src/java/org/apache/cassandra/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,8 @@ public static void setClientMode(boolean clientMode)
public volatile Set<ConsistencyLevel> write_consistency_levels_disallowed = Collections.emptySet();
public volatile boolean user_timestamps_enabled = true;
public volatile boolean group_by_enabled = true;
public volatile boolean ddl_enabled = true;
public volatile boolean dcl_enabled = true;
public volatile boolean drop_truncate_table_enabled = true;
public volatile boolean secondary_indexes_enabled = true;
public volatile boolean uncompressed_tables_enabled = true;
Expand Down
28 changes: 28 additions & 0 deletions src/java/org/apache/cassandra/config/GuardrailsOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,34 @@ public void setDropTruncateTableEnabled(boolean enabled)
x -> config.drop_truncate_table_enabled = x);
}

@Override
public boolean getDDLEnabled()
{
return config.ddl_enabled;
}

public void setDDLEnabled(boolean enabled)
{
updatePropertyWithLogging("ddl_enabled",
enabled,
() -> config.ddl_enabled,
x -> config.ddl_enabled = x);
}

@Override
public boolean getDCLEnabled()
{
return config.dcl_enabled;
}

public void setDCLEnabled(boolean enabled)
{
updatePropertyWithLogging("dcl_enabled",
enabled,
() -> config.dcl_enabled,
x -> config.dcl_enabled = x);
}

@Override
public boolean getSecondaryIndexesEnabled()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.PasswordObfuscator;
import org.apache.cassandra.cql3.RoleName;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.messages.ResultMessage;
Expand Down Expand Up @@ -69,6 +70,8 @@ public void validate(ClientState state) throws RequestValidationException
{
checkTrue(ifExists, "Role %s doesn't exist", role.getRoleName());
}

Guardrails.dclEnabled.ensureEnabled(state);
}

public void authorize(ClientState state) throws UnauthorizedException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.PasswordObfuscator;
import org.apache.cassandra.cql3.RoleName;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.messages.ResultMessage;
Expand Down Expand Up @@ -71,6 +72,8 @@ public void validate(ClientState state) throws RequestValidationException

if (!ifNotExists && DatabaseDescriptor.getRoleManager().isExistingRole(role))
throw new InvalidRequestException(String.format("%s already exists", role.getRoleName()));

Guardrails.dclEnabled.ensureEnabled(state);
}

public ResultMessage execute(ClientState state) throws RequestExecutionException, RequestValidationException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.cassandra.auth.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.RoleName;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.messages.ResultMessage;
Expand Down Expand Up @@ -62,6 +63,8 @@ public void validate(ClientState state) throws RequestValidationException
AuthenticatedUser user = state.getUser();
if (user != null && user.getName().equals(role.getRoleName()))
throw new InvalidRequestException("Cannot DROP primary role for current login");

Guardrails.dclEnabled.ensureEnabled(state);
}

public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.apache.cassandra.auth.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.cql3.RoleName;
import org.apache.cassandra.exceptions.InvalidRequestException;
Expand Down Expand Up @@ -64,6 +65,8 @@ public void validate(ClientState state) throws RequestValidationException

if (!resource.exists())
throw new InvalidRequestException(String.format("Resource %s doesn't exist", resource));

Guardrails.dclEnabled.ensureEnabled(state);
}

public void authorize(ClientState state) throws UnauthorizedException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
Expand Down Expand Up @@ -73,6 +74,8 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
return schema;
}
// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

KeyspaceMetadata newKeyspace = keyspace.withSwapped(attrs.asAlteredKeyspaceParams(keyspace.params));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ public void validate(ClientState state)

public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
{
// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

TableMetadata.Builder tableBuilder = table.unbuild();
Views.Builder viewsBuilder = keyspace.views.unbuild();
newColumns.forEach(c -> addColumn(keyspace, table, c, ifColumnNotExists, tableBuilder, viewsBuilder));
Expand Down Expand Up @@ -216,6 +219,9 @@ private void addColumn(KeyspaceMetadata keyspace,
return;
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

if (table.isCompactTable())
throw ire("Cannot add new column to a COMPACT STORAGE table");

Expand Down Expand Up @@ -302,6 +308,9 @@ private void dropColumn(KeyspaceMetadata keyspace, TableMetadata table, ColumnId
return;
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

if (currentColumn.isPrimaryKeyColumn())
throw ire("Cannot drop PRIMARY KEY column %s", column);

Expand Down Expand Up @@ -379,6 +388,9 @@ private void renameColumn(KeyspaceMetadata keyspace,
return;
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

if (!column.isPrimaryKeyColumn())
throw ire("Cannot rename non PRIMARY KEY column %s", oldName);

Expand Down Expand Up @@ -434,6 +446,9 @@ public void validate(ClientState state)

public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
{
// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

attrs.validate();

TableParams params = attrs.asAlteredTableParams(table.params);
Expand Down Expand Up @@ -478,6 +493,9 @@ private DropCompactStorage(String keyspaceName, String tableName, boolean ifTabl

public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
{
// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

if (!DatabaseDescriptor.enableDropCompactStorage())
throw new InvalidRequestException("DROP COMPACT STORAGE is disabled. Enable in cassandra.yaml to use.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ UserType apply(KeyspaceMetadata keyspace, UserType userType)
throw ire("Cannot add field %s to type %s: a field with name %s already exists", fieldName, userType.getCqlTypeName(), fieldName);
return userType;
}
// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

AbstractType<?> fieldType = type.prepare(keyspaceName, keyspace.types).getType();
if (fieldType.referencesUserType(userType.name))
Expand Down Expand Up @@ -212,6 +214,9 @@ UserType apply(KeyspaceMetadata keyspace, UserType userType)
fieldNames.set(idx, newName);
});

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

fieldNames.forEach(name ->
{
if (fieldNames.stream().filter(isEqual(name)).count() > 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Materialized view '%s.%s' doesn't exist", keyspaceName, viewName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

attrs.validate();

// Guardrails on table properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.*;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.Functions.FunctionsDiff;
import org.apache.cassandra.schema.KeyspaceMetadata;
Expand Down Expand Up @@ -220,6 +221,9 @@ public Keyspaces apply(Keyspaces schema)
}
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.functions.withAddedOrUpdated(aggregate)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.UDFunction;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.Functions.FunctionsDiff;
import org.apache.cassandra.schema.KeyspaceMetadata;
Expand Down Expand Up @@ -152,6 +153,9 @@ public Keyspaces apply(Keyspaces schema)
// TODO: update dependent aggregates
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.functions.withAddedOrUpdated(function)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Index %s is a duplicate of existing index %s", index.name, equalIndex.name);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

TableMetadata newTable = table.withSwapped(table.indexes.with(index));
newTable.validate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public Keyspaces apply(Keyspaces schema)
throw new AlreadyExistsException(keyspaceName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

KeyspaceMetadata keyspace = KeyspaceMetadata.create(keyspaceName, attrs.asNewKeyspaceParams());

if (keyspace.params.replication.klass.equals(LocalStrategy.class))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ public Keyspaces apply(Keyspaces schema)
throw new AlreadyExistsException(keyspaceName, tableName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

TableMetadata table = builder(keyspace.types).build();
table.validate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
Expand Down Expand Up @@ -67,6 +68,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Trigger '%s' already exists", triggerName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

try
{
TriggerExecutor.instance.loadTriggerInstance(triggerClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("A user type with name '%s' already exists", typeName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

Set<FieldIdentifier> usedNames = new HashSet<>();
for (FieldIdentifier name : fieldNames)
if (!usedNames.add(name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ public Keyspaces apply(Keyspaces schema)
throw new AlreadyExistsException(keyspaceName, viewName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

/*
* Base table validation
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.UDAggregate;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
Expand Down Expand Up @@ -110,6 +111,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Aggregate '%s' doesn't exist", name);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.functions.without(aggregate)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.functions.*;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
Expand Down Expand Up @@ -109,6 +110,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Function '%s' doesn't exist", name);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

String dependentAggregates =
keyspace.functions
.aggregatesUsingFunction(function)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
Expand Down Expand Up @@ -58,6 +59,9 @@ public Keyspaces apply(Keyspaces schema)
throw ire("Index '%s.%s' doesn't exist'", keyspaceName, indexName);
}

// if apply is not no-op then we check guardrail for this ddl op
Guardrails.ddlEnabled.ensureEnabled(state);

TableMetadata newTable = table.withSwapped(table.indexes.without(indexName));
return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.withSwapped(newTable)));
}
Expand Down