From 070784428fe2b0f1be0cc89a2859099fe15b798c Mon Sep 17 00:00:00 2001 From: Yuqi Yan Date: Mon, 15 Apr 2024 11:58:38 -0700 Subject: [PATCH 1/3] Guardrail to block DDL/DCL queries --- CHANGES.txt | 1 + conf/cassandra.yaml | 4 + .../org/apache/cassandra/config/Config.java | 2 + .../cassandra/config/GuardrailsOptions.java | 28 ++ .../cql3/statements/AlterRoleStatement.java | 3 + .../cql3/statements/CreateRoleStatement.java | 3 + .../cql3/statements/DropRoleStatement.java | 3 + .../PermissionsManagementStatement.java | 3 + .../schema/AlterKeyspaceStatement.java | 2 + .../schema/AlterTableStatement.java | 15 + .../statements/schema/AlterTypeStatement.java | 5 + .../statements/schema/AlterViewStatement.java | 3 + .../schema/CreateAggregateStatement.java | 4 + .../schema/CreateFunctionStatement.java | 4 + .../schema/CreateIndexStatement.java | 3 + .../schema/CreateKeyspaceStatement.java | 3 + .../schema/CreateTableStatement.java | 3 + .../schema/CreateTriggerStatement.java | 4 + .../schema/CreateTypeStatement.java | 3 + .../schema/CreateViewStatement.java | 3 + .../schema/DropAggregateStatement.java | 4 + .../schema/DropFunctionStatement.java | 4 + .../statements/schema/DropIndexStatement.java | 4 + .../schema/DropKeyspaceStatement.java | 4 + .../statements/schema/DropTableStatement.java | 3 + .../schema/DropTriggerStatement.java | 4 + .../statements/schema/DropTypeStatement.java | 4 + .../statements/schema/DropViewStatement.java | 4 + .../cassandra/db/guardrails/Guardrails.java | 42 ++ .../db/guardrails/GuardrailsConfig.java | 14 + .../db/guardrails/GuardrailsMBean.java | 24 + .../guardrails/GuardrailDCLEnabledTest.java | 117 +++++ .../guardrails/GuardrailDDLEnabledTest.java | 420 ++++++++++++++++++ 33 files changed, 747 insertions(+) create mode 100644 test/unit/org/apache/cassandra/db/guardrails/GuardrailDCLEnabledTest.java create mode 100644 test/unit/org/apache/cassandra/db/guardrails/GuardrailDDLEnabledTest.java diff --git a/CHANGES.txt b/CHANGES.txt index ed314c60d324..acb2c35c6fd3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Guardrail to block DDL/DCL queries (CASSANDRA-19556) * Improve handling of transient replicas during range movements (CASSANDRA-19344) * Enable debounced internode log requests to be cancelled at shutdown (CASSANDRA-19514) * Correctly set last modified epoch when combining multistep operations into a single step (CASSANDRA-19538) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 9a4a41fbc5a8..8839df47a084 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -2013,6 +2013,10 @@ drop_compact_storage_enabled: false # Guardrail to allow/disallow bulk load of SSTables # bulk_load_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 diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index db1d07c5428d..a54cbe1ede86 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -871,6 +871,8 @@ public static void setClientMode(boolean clientMode) public volatile boolean alter_table_enabled = true; public volatile boolean group_by_enabled = true; public volatile boolean bulk_load_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 drop_keyspace_enabled = true; public volatile boolean secondary_indexes_enabled = true; diff --git a/src/java/org/apache/cassandra/config/GuardrailsOptions.java b/src/java/org/apache/cassandra/config/GuardrailsOptions.java index 047f7c0037ea..a6a0bea62fe1 100644 --- a/src/java/org/apache/cassandra/config/GuardrailsOptions.java +++ b/src/java/org/apache/cassandra/config/GuardrailsOptions.java @@ -382,6 +382,34 @@ public void setBulkLoadEnabled(boolean enabled) x -> config.bulk_load_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() { diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java index aadf6c788db2..8387e1c426a7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java @@ -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; @@ -78,6 +79,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 diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java index d6e0a1298cde..38ee838c500c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java @@ -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; @@ -79,6 +80,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 diff --git a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java index 13ba54a52d9d..2196240ecbc6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java @@ -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; @@ -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 diff --git a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java index e809a27a45e9..23bad04207df 100644 --- a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java @@ -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.cql3.RoleName; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; @@ -56,6 +57,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 diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java index 14bdef0f68bc..31e48b27255d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java @@ -81,6 +81,8 @@ public Keyspaces apply(ClusterMetadata metadata) 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)); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java index 15ed785ca674..3e07015eb055 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java @@ -317,6 +317,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"); @@ -437,6 +440,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); @@ -509,6 +515,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); @@ -562,6 +571,9 @@ public void validate(ClientState state) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata) { + // 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); @@ -606,6 +618,9 @@ private DropCompactStorage(String keyspaceName, String tableName, boolean ifTabl public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata) { + // 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."); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java index fefe70e1c6e2..8f7bf920f118 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java @@ -142,6 +142,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)) @@ -216,6 +218,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) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java index 831e46b0ca2d..1e3759b82194 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java @@ -71,6 +71,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java index 6428d85073ef..f766bf6fdf8e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java @@ -38,6 +38,7 @@ import org.apache.cassandra.cql3.functions.UserFunction; import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.schema.UserFunctions.FunctionsDiff; import org.apache.cassandra.schema.KeyspaceMetadata; @@ -230,6 +231,9 @@ public Keyspaces apply(ClusterMetadata metadata) } } + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.userFunctions.withAddedOrUpdated(aggregate))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java index 20923687ff03..0187935afac9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java @@ -33,6 +33,7 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UserFunction; +import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.schema.UserFunctions.FunctionsDiff; import org.apache.cassandra.schema.KeyspaceMetadata; @@ -154,6 +155,9 @@ public Keyspaces apply(ClusterMetadata metadata) // 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.userFunctions.withAddedOrUpdated(function))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java index bf00c581c25c..85a7ba0bea9d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java @@ -192,6 +192,9 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire(INDEX_DUPLICATE_OF_EXISTING, 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(); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java index 16a117c890e5..89c4c2c7cfdd 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java @@ -76,6 +76,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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)) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java index 264877db7ccf..7af57cc48be0 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java @@ -127,6 +127,9 @@ public Keyspaces apply(ClusterMetadata metadata) if (!table.params.compression.isEnabled()) Guardrails.uncompressedTablesEnabled.ensureEnabled(state); + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.with(table))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java index 8555430c1f27..3d88d87d06b8 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java @@ -22,6 +22,7 @@ import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QualifiedName; import org.apache.cassandra.exceptions.InvalidRequestException; +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; @@ -71,6 +72,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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.loadTriggerClass(triggerClass); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java index bd63310f18fa..fd5d56420b1f 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java @@ -92,6 +92,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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 usedNames = new HashSet<>(); for (FieldIdentifier name : fieldNames) if (!usedNames.add(name)) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java index a4a8ddcfbefe..75d847b1d230 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java @@ -144,6 +144,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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 */ diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java index e61332ee1203..5a53caa1b6e9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java @@ -31,6 +31,7 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.functions.UserFunction; +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; @@ -112,6 +113,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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.userFunctions.without(aggregate))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java index 0353964683b5..09135624e453 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java @@ -31,6 +31,7 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UserFunction; +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; @@ -114,6 +115,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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.userFunctions .aggregatesUsingFunction(function) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java index 06cbc4be9961..1d142b9c3c95 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java @@ -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; @@ -61,6 +62,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java index e074da54a33b..c16fda0ef7d3 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java @@ -46,7 +46,11 @@ public Keyspaces apply(ClusterMetadata metadata) Keyspaces schema = metadata.schema.getKeyspaces(); if (schema.containsKeyspace(keyspaceName)) + { + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); return schema.without(keyspaceName); + } if (ifExists) return schema; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java index 56848a8c2275..f4892d9563d3 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java @@ -67,6 +67,9 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); } + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + if (table.isView()) throw ire("Cannot use DROP TABLE on a materialized view. Please use DROP MATERIALIZED VIEW instead."); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java index 15007c3b1e41..dfe0ecc56416 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java @@ -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; @@ -65,6 +66,9 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Trigger '%s' on '%s.%s' doesn't exist", triggerName, keyspaceName, tableName); } + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + TableMetadata newTable = table.withSwapped(table.triggers.without(triggerName)); return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.withSwapped(newTable))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java index 105c8f5db8cf..00e1583b435b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java @@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.UTName; import org.apache.cassandra.cql3.functions.UserFunction; +import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; @@ -76,6 +77,9 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName); } + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + /* * We don't want to drop a type unless it's not used anymore (mainly because * if someone drops a type and recreates one with the same name but different diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java index 121575503cf8..2212acfc6ebe 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java @@ -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.Keyspaces.KeyspacesDiff; import org.apache.cassandra.service.ClientState; @@ -60,6 +61,9 @@ public Keyspaces apply(ClusterMetadata metadata) 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); + return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.without(viewName))); } diff --git a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java index 31532abb4745..29a85a19fe9f 100644 --- a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java +++ b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java @@ -193,6 +193,24 @@ public final class Guardrails implements GuardrailsMBean state -> CONFIG_PROVIDER.getOrCreate(state).getBulkLoadEnabled(), "Bulk loading of SSTables").throwOnNullClientState(true); + /** + * Guardrail disabling DDL statements + */ + public static final EnableFlag ddlEnabled = + new EnableFlag("ddl_enabled", + null, + state -> CONFIG_PROVIDER.getOrCreate(state).getDDLEnabled(), + "DDL statement"); + + /** + * Guardrail disabling DCL statements + */ + public static final EnableFlag dclEnabled = + new EnableFlag("dcl_enabled", + null, + state -> CONFIG_PROVIDER.getOrCreate(state).getDCLEnabled(), + "DCL statement"); + /** * Guardrail disabling user's ability to turn off compression */ @@ -866,6 +884,30 @@ public void setBulkLoadEnabled(boolean enabled) DEFAULT_CONFIG.setBulkLoadEnabled(enabled); } + @Override + public boolean getDDLEnabled() + { + return DEFAULT_CONFIG.getDDLEnabled(); + } + + @Override + public void setDDLEnabled(boolean enabled) + { + DEFAULT_CONFIG.setDDLEnabled(enabled); + } + + @Override + public boolean getDCLEnabled() + { + return DEFAULT_CONFIG.getDCLEnabled(); + } + + @Override + public void setDCLEnabled(boolean enabled) + { + DEFAULT_CONFIG.setDCLEnabled(enabled); + } + @Override public int getPageSizeWarnThreshold() { diff --git a/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java b/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java index 8309971c5baf..f1356f72a477 100644 --- a/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java +++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java @@ -182,6 +182,20 @@ public interface GuardrailsConfig */ boolean getBulkLoadEnabled(); + /** + * Returns whether DDL statement is allowed + * + * @return {@code true} if allowed, {@code false} otherwise. + */ + boolean getDDLEnabled(); + + /** + * Returns whether DCL statement is allowed + * + * @return {@code true} if allowed, {@code false} otherwise. + */ + boolean getDCLEnabled(); + /** * @return The threshold to warn when page size exceeds given size. */ diff --git a/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java b/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java index 68dd9e1e3cbc..22d526e3cdb4 100644 --- a/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java +++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java @@ -328,6 +328,30 @@ public interface GuardrailsMBean */ void setBulkLoadEnabled(boolean enabled); + /** + * Returns whether DDL statement is allowed + * + * @return {@code true} if allowed, {@code false} otherwise. + */ + boolean getDDLEnabled(); + + /** + * Sets whether DDL statement is allowed + */ + void setDDLEnabled(boolean enabled); + + /** + * Returns whether DCL statement is allowed + * + * @return {@code true} if allowed, {@code false} otherwise. + */ + boolean getDCLEnabled(); + + /** + * Sets whether DCL statement is allowed + */ + void setDCLEnabled(boolean enabled); + /** * @return The threshold to warn when requested page size greater than threshold. * -1 means disabled. diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailDCLEnabledTest.java b/test/unit/org/apache/cassandra/db/guardrails/GuardrailDCLEnabledTest.java new file mode 100644 index 000000000000..e0f0dc6dc523 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailDCLEnabledTest.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.db.guardrails; + +import java.net.InetSocketAddress; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.service.ClientState; + +import static java.lang.String.format; + +public class GuardrailDCLEnabledTest extends GuardrailTester +{ + private static final String TEST_USER = "testuser"; + private static final String TEST_PW = "testpassword"; + private static final String TEST_USER1 = "testuser1"; + private static final String TEST_PW1 = "testpassword1"; + private static final String TEST_KS = "dclks"; + private static final String TEST_TABLE = "dcltbl"; + private static final String DCL_ERROR_MSG = "DCL statement is not allowed"; + private ClientState loginUserClientState; + + private void setGuardrail(boolean enabled) + { + Guardrails.instance.setDCLEnabled(enabled); + } + + @Before + public void beforeGuardrailTest() throws Throwable + { + super.beforeGuardrailTest(); + // create user in login state + useSuperUser(); + executeNet(getCreateRoleCQL(TEST_USER, true, false, TEST_PW)); + executeNet(format("GRANT ALL ON KEYSPACE %s TO %s", KEYSPACE, TEST_USER)); + useUser(TEST_USER, TEST_PW); + + loginUserClientState = ClientState.forExternalCalls(InetSocketAddress.createUnresolved("127.0.0.1", 1234)); + loginUserClientState.login(new AuthenticatedUser(TEST_USER)); + execute(loginUserClientState, "USE " + keyspace()); + + execute(superClientState, String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", TEST_KS)); + execute(superClientState, String.format("CREATE TABLE IF NOT EXISTS %s.%s (key text PRIMARY KEY, col1 int, col2 int)", TEST_KS, TEST_TABLE)); + } + + @After + public void afterTest() + { + setGuardrail(true); + } + + @Test + public void testCannotCreateRoleWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + assertFails(() -> execute(loginUserClientState, + getCreateRoleCQL(TEST_USER1, true, false, TEST_PW1)), + DCL_ERROR_MSG); + // no role is created + assertEmpty(execute(String.format("SELECT * FROM system_auth.roles WHERE role='%s'", TEST_USER1))); + } + + @Test + public void testCannotGrantPermissionWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + assertFails(() -> execute(loginUserClientState, + getGrantPermissionCQL(TEST_USER, TEST_KS, TEST_TABLE)), + DCL_ERROR_MSG); + // TEST_USER don't get permission on TEST_KS.TEST_TABLE + assertEmpty(execute(String.format("SELECT * FROM system_auth.role_permissions WHERE role='%s' AND resource='data/%s/%s'", + TEST_USER, TEST_KS, TEST_TABLE))); + } + + @Test + public void testCannotRevokePermissionWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + assertFails(() -> execute(loginUserClientState, + String.format("REVOKE ALL ON KEYSPACE %s FROM %s", KEYSPACE, TEST_USER)), + DCL_ERROR_MSG); + // TEST_USER permission wasn't revoked on KEYSPACE + assertRowCount(execute(String.format("SELECT * FROM system_auth.role_permissions WHERE role='%s' AND resource='data/%s'", TEST_USER, KEYSPACE)), + 1); + } + + private static String getCreateRoleCQL(String role, boolean login, boolean superUser, String password) + { + return String.format("CREATE ROLE IF NOT EXISTS %s WITH LOGIN = %s AND SUPERUSER = %s AND PASSWORD = '%s'", + role, login, superUser, password); + } + + private static String getGrantPermissionCQL(String role, String ks, String tbl) + { + return String.format("GRANT ALL PERMISSIONS ON %s.%s TO %s;", ks, tbl, role); + } +} diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailDDLEnabledTest.java b/test/unit/org/apache/cassandra/db/guardrails/GuardrailDDLEnabledTest.java new file mode 100644 index 000000000000..40f22d3d52dc --- /dev/null +++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailDDLEnabledTest.java @@ -0,0 +1,420 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.db.guardrails; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.schema.SchemaKeyspaceTables; + +import static org.junit.Assert.fail; + +public class GuardrailDDLEnabledTest extends GuardrailTester +{ + private static final String TEST_KS = "ddlks"; + private static final String TEST_TABLE = "ddltbl"; + private static final String TEST_VIEW = "ddlview"; + private static final String TEST_KS_NEW = "ddlks2"; + private static final String TEST_TABLE_NEW = "ddltbl2"; + private static final String TEST_VIEW_NEW = "ddlview2"; + private static final String DDL_ERROR_MSG = "DDL statement is not allowed"; + + private void setGuardrail(boolean enabled) + { + Guardrails.instance.setDDLEnabled(enabled); + } + + @Before + public void beforeGuardrailTest() throws Throwable + { + super.beforeGuardrailTest(); + // create keyspace ddlks and table ddltbl + execute(superClientState, getCreateKeyspaceCQL(TEST_KS, true)); + execute(superClientState, getCreateTableCQL(TEST_KS, TEST_TABLE, true)); + execute(superClientState, getCreateViewCQL(TEST_KS, TEST_VIEW, true)); + assertRowCount(execute(getSystemSchemaKeyspaceCQL(TEST_KS)), 1); + assertRowCount(execute(getSystemSchemaTableCQL(TEST_KS, TEST_TABLE)), 1); + assertRowCount(execute(getSystemSchemaViewCQL(TEST_KS, TEST_VIEW)), 1); + } + + @After + public void afterTest() + { + setGuardrail(true); + } + + @Test + public void testCannotCreateKeyspaceWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // CREATE should not fail if: keyspace exists and IF NOT EXISTS specified (no-op) + execute(userClientState, getCreateKeyspaceCQL(TEST_KS, true)); + + // CREATE will fail with guardrail exception if user tries to create a keyspace + assertFails(() -> execute(userClientState, + getCreateKeyspaceCQL(TEST_KS_NEW, false)), + DDL_ERROR_MSG); + + // CREATE will also fail if user doesn't specify IF NOT EXISTS but create an already existing keyspace + try + { + execute(userClientState, getCreateKeyspaceCQL(TEST_KS, false)); + } + catch (AlreadyExistsException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // No new keyspace should be created + assertEmpty(execute(getSystemSchemaKeyspaceCQL(TEST_KS_NEW))); + } + + @Test + public void testCannotCreateTableWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // CREATE should not fail if: table exists and IF NOT EXISTS specified (no-op) + execute(userClientState, getCreateTableCQL(TEST_KS, TEST_TABLE, true)); + + // CREATE will fail with guardrail exception if user tries to create a table + assertFails(() -> execute(userClientState, + getCreateTableCQL(TEST_KS, TEST_TABLE_NEW, false)), + DDL_ERROR_MSG); + + // CREATE will also fail if user doesn't specify IF NOT EXISTS but create an already existing table + try + { + execute(userClientState, getCreateTableCQL(TEST_KS, TEST_TABLE, false)); + } + catch (AlreadyExistsException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // No new table should be created + assertEmpty(execute(getSystemSchemaTableCQL(TEST_KS, TEST_TABLE_NEW))); + } + + @Test + public void testCannotCreateViewWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // CREATE should not fail if: view exists and IF NOT EXISTS specified (no-op) + execute(userClientState, getCreateViewCQL(TEST_KS, TEST_VIEW, true)); + + // CREATE will fail with guardrail exception if user tries to create a view + assertFails(() -> execute(userClientState, + getCreateViewCQL(TEST_KS, TEST_VIEW_NEW, false)), + DDL_ERROR_MSG); + + // CREATE will also fail if user doesn't specify IF NOT EXISTS but create an already existing view. + try + { + execute(userClientState, getCreateViewCQL(TEST_KS, TEST_VIEW, false)); + } + catch (AlreadyExistsException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // No new view should be created + assertEmpty(execute(getSystemSchemaViewCQL(TEST_KS, TEST_TABLE_NEW))); + } + + @Test + public void testCannotDropKeyspaceWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // DROP should not fail if: keyspace not exists and IF EXISTS specified (no-op) + execute(userClientState, getDropKeyspaceCQL(TEST_KS_NEW, true)); + + // DROP will fail with guardrail exception if user tries to drop an existing keyspace + assertFails(() -> execute(userClientState, + getDropKeyspaceCQL(TEST_KS, false)), + DDL_ERROR_MSG); + + // DROP will also fail if user doesn't specify IF EXISTS but keyspace doesn't exist + try + { + execute(userClientState, getDropKeyspaceCQL(TEST_KS_NEW, false)); + } + catch (InvalidRequestException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // TEST_KS should not be dropped + assertRowCount(execute(getSystemSchemaKeyspaceCQL(TEST_KS)), 1); + } + + @Test + public void testCannotDropTableWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // DROP should not fail if: table not exists and IF EXISTS specified (no-op) + execute(userClientState, getDropTableCQL(TEST_KS, TEST_TABLE_NEW, true)); + + // DROP will fail with guardrail exception if user tries to drop an existing table + assertFails(() -> execute(userClientState, + getDropTableCQL(TEST_KS, TEST_TABLE, false)), + DDL_ERROR_MSG); + + // DROP will also fail if user doesn't specify IF EXISTS but table doesn't exist + try + { + execute(userClientState, getDropTableCQL(TEST_KS, TEST_TABLE_NEW, false)); + } + catch (InvalidRequestException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // TEST_TABLE should not be dropped + assertRowCount(execute(getSystemSchemaTableCQL(TEST_KS, TEST_TABLE)), 1); + } + + @Test + public void testCannotDropViewWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // DROP should not fail if: view not exists and IF EXISTS specified (no-op) + execute(userClientState, getDropViewCQL(TEST_KS, TEST_VIEW_NEW, true)); + + // DROP will fail with guardrail exception if user tries to drop an existing view + assertFails(() -> execute(userClientState, + getDropViewCQL(TEST_KS, TEST_VIEW, false)), + DDL_ERROR_MSG); + + // DROP will also fail if user doesn't specify IF EXISTS but view doesn't exist + try + { + execute(userClientState, getDropViewCQL(TEST_KS, TEST_VIEW_NEW, false)); + } + catch (InvalidRequestException e) + { + // expected + } + catch (Exception e) + { + fail(String.format("failed with unexpected error: %s", e.getMessage())); + } + + // TEST_VIEW should not be dropped + assertRowCount(execute(getSystemSchemaViewCQL(TEST_KS, TEST_VIEW)), 1); + } + + @Test + public void testCannotDropColumnWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // table have column col1 + assertRowCount(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND column_name='col1'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.COLUMNS, + TEST_KS, TEST_TABLE)), + 1); + // ALTER TABLE drop column should not fail if: table exists and the column doesn't exist and user specify IF EXISTS (no-op) + execute(userClientState, String.format("ALTER TABLE %s.%s DROP IF EXISTS col3", TEST_KS, TEST_TABLE)); + + // ALTER TABLE will fail with guardrail excepetion if user tries to add new column to this table + assertFails(() -> execute(userClientState, + String.format("ALTER TABLE %s.%s DROP col1", TEST_KS, TEST_TABLE)), + DDL_ERROR_MSG); + + // column col1 should not be dropped + assertRowCount(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND column_name='col1'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.COLUMNS, + TEST_KS, TEST_TABLE)), + 1); + } + + @Test + public void testCannotAlterKeyspaceWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // keyspace should have durable_write=true by default + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND durable_writes=false ALLOW FILTERING", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.KEYSPACES, + TEST_KS))); + // ALTER KEYSPACE should not fail if: keyspace not exists and IF EXISTS specified (no-op) + execute(userClientState, String.format("ALTER KEYSPACE IF EXISTS %s WITH durable_writes=false", TEST_KS_NEW)); + // ALTER TABLE will fail with guardrail excepetion if user tries to alter anything related to this table + assertFails(() -> execute(userClientState, + String.format("ALTER KEYSPACE %s WITH durable_writes=false", TEST_KS)), + DDL_ERROR_MSG); + + // keyspace should still have durable_write=true + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND durable_writes=false ALLOW FILTERING", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.KEYSPACES, + TEST_KS))); + } + + @Test + public void testCannotAlterTableWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // table doesn't have comment + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND comment='test' ALLOW FILTERING", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.TABLES, + TEST_KS, TEST_TABLE))); + // ALTER TABLE should not fail if: table not exists and IF EXISTS specified (no-op) + execute(userClientState, String.format("ALTER TABLE IF EXISTS %s.%s WITH comment='test'", TEST_KS, TEST_TABLE_NEW)); + // ALTER TABLE will fail with guardrail excepetion if user tries to alter anything related to this table + assertFails(() -> execute(userClientState, + String.format("ALTER TABLE %s.%s WITH comment='test'", TEST_KS, TEST_TABLE)), + DDL_ERROR_MSG); + + // table should not have comment + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND comment='test' ALLOW FILTERING", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.TABLES, + TEST_KS, TEST_TABLE))); + } + + @Test + public void testCannotAddColumnWhileFeatureDisabled() throws Throwable + { + setGuardrail(false); + // table doesn't have new column col3 + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND column_name='col3'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.COLUMNS, + TEST_KS, TEST_TABLE))); + // ALTER TABLE add column should not fail if: table not exists and IF EXISTS specified (no-op) + execute(userClientState, String.format("ALTER TABLE IF EXISTS %s.%s ADD col3 text", TEST_KS, TEST_TABLE_NEW)); + // ALTER TABLE will fail with guardrail excepetion if user tries to add new column to this table + assertFails(() -> execute(userClientState, + String.format("ALTER TABLE %s.%s ADD col3 text", TEST_KS, TEST_TABLE)), + DDL_ERROR_MSG); + + // table should not have new column col3 + assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s' AND column_name='col3'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.COLUMNS, + TEST_KS, TEST_TABLE))); + } + + private String getCreateKeyspaceCQL(String ks, boolean ifNotExists) + { + if (ifNotExists) + { + return String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", ks); + } + return String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", ks); + } + + private String getDropKeyspaceCQL(String ks, boolean ifExists) + { + if (ifExists) + { + return String.format("DROP KEYSPACE IF EXISTS %s", ks); + } + return String.format("DROP KEYSPACE %s", ks); + } + + private String getCreateTableCQL(String ks, String table, boolean ifNotExists) + { + if (ifNotExists) + { + return String.format("CREATE TABLE IF NOT EXISTS %s.%s (key text PRIMARY KEY, col1 text, col2 text)", ks, table); + } + return String.format("CREATE TABLE %s.%s (key text PRIMARY KEY, col1 text, col2 text)", ks, table); + } + + private String getDropTableCQL(String ks, String table, boolean ifExists) + { + if (ifExists) + { + return String.format("DROP TABLE IF EXISTS %s.%s", ks, table); + } + return String.format("DROP TABLE %s.%s", ks, table); + } + + private String getCreateViewCQL(String ks, String table, boolean ifNotExists) + { + if (ifNotExists) + { + return String.format("CREATE MATERIALIZED VIEW IF NOT EXISTS %s.%s AS SELECT key FROM %s.%s WHERE key IS NOT NULL PRIMARY KEY (key)", + ks, table, TEST_KS, TEST_TABLE); + } + return String.format("CREATE MATERIALIZED VIEW %s.%s AS SELECT key FROM %s.%s WHERE key IS NOT NULL PRIMARY KEY (key)", + ks, table, TEST_KS, TEST_TABLE); + } + + private String getDropViewCQL(String ks, String view, boolean ifExists) + { + if (ifExists) + { + return String.format("DROP MATERIALIZED VIEW IF EXISTS %s.%s", ks, view); + } + return String.format("DROP MATERIALIZED VIEW %s.%s", ks, view); + } + + private String getSystemSchemaKeyspaceCQL(String ks) + { + return String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.KEYSPACES, + ks); + } + + private String getSystemSchemaTableCQL(String ks, String table) + { + return String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND table_name='%s'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.TABLES, + ks, table); + } + + private String getSystemSchemaViewCQL(String ks, String view) + { + return String.format("SELECT * FROM %s.%s WHERE keyspace_name='%s' AND view_name='%s'", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.VIEWS, + ks, view); + } +} From 9940a0522fe4214d6d66149de0526fa14434a946 Mon Sep 17 00:00:00 2001 From: Yuqi Yan Date: Mon, 29 Apr 2024 17:28:20 -0700 Subject: [PATCH 2/3] add gr for mask column --- .../cassandra/cql3/statements/schema/AlterTableStatement.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java index 3e07015eb055..5883c0d30924 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java @@ -223,6 +223,9 @@ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetad if (Objects.equals(oldMask, newMask)) return keyspace; + // if apply is not no-op then we check guardrail for this ddl op + Guardrails.ddlEnabled.ensureEnabled(state); + TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch); tableBuilder.alterColumnMask(columnName, newMask); TableMetadata newTable = tableBuilder.build(); From 35a62ecc479864a661f4288edec08ae548592961 Mon Sep 17 00:00:00 2001 From: Stefan Miklosovic Date: Tue, 30 Apr 2024 10:54:08 +0200 Subject: [PATCH 3/3] rework --- .../apache/cassandra/cql3/CQLStatement.java | 10 ++++++++++ .../apache/cassandra/cql3/QueryProcessor.java | 11 +++++++++++ .../cql3/statements/AlterRoleStatement.java | 3 --- .../statements/AuthenticationStatement.java | 6 ++++++ .../statements/AuthorizationStatement.java | 6 ++++++ .../cql3/statements/CreateRoleStatement.java | 3 --- .../cql3/statements/DropRoleStatement.java | 3 --- .../PermissionsManagementStatement.java | 3 --- .../schema/AlterKeyspaceStatement.java | 2 -- .../schema/AlterSchemaStatement.java | 6 ++++++ .../statements/schema/AlterTableStatement.java | 18 ------------------ .../statements/schema/AlterTypeStatement.java | 5 ----- .../statements/schema/AlterViewStatement.java | 3 --- .../schema/CreateAggregateStatement.java | 4 ---- .../schema/CreateFunctionStatement.java | 4 ---- .../schema/CreateIndexStatement.java | 3 --- .../schema/CreateKeyspaceStatement.java | 3 --- .../schema/CreateTableStatement.java | 3 --- .../schema/CreateTriggerStatement.java | 4 ---- .../statements/schema/CreateTypeStatement.java | 3 --- .../statements/schema/CreateViewStatement.java | 3 --- .../schema/DropAggregateStatement.java | 4 ---- .../schema/DropFunctionStatement.java | 4 ---- .../statements/schema/DropIndexStatement.java | 4 ---- .../schema/DropKeyspaceStatement.java | 4 ---- .../statements/schema/DropTableStatement.java | 3 --- .../schema/DropTriggerStatement.java | 4 ---- .../statements/schema/DropTypeStatement.java | 4 ---- .../statements/schema/DropViewStatement.java | 4 ---- 29 files changed, 39 insertions(+), 98 deletions(-) diff --git a/src/java/org/apache/cassandra/cql3/CQLStatement.java b/src/java/org/apache/cassandra/cql3/CQLStatement.java index e78f7332c322..f118645bfd25 100644 --- a/src/java/org/apache/cassandra/cql3/CQLStatement.java +++ b/src/java/org/apache/cassandra/cql3/CQLStatement.java @@ -99,6 +99,16 @@ default public boolean hasConditions() return false; } + default boolean isDDLStatement() + { + return false; + } + + default boolean isDCLStatement() + { + return false; + } + public static abstract class Raw { protected VariableSpecifications bindVariables; diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 3cbad02362f6..aead1b685959 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -61,6 +61,7 @@ import org.apache.cassandra.db.ReadResponse; import org.apache.cassandra.db.SinglePartitionReadQuery; import org.apache.cassandra.db.SystemKeyspace; +import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.db.partitions.PartitionIterators; @@ -278,6 +279,11 @@ public ResultMessage processStatement(CQLStatement statement, QueryState querySt statement.authorize(clientState); statement.validate(clientState); + if (statement.isDDLStatement()) + Guardrails.ddlEnabled.ensureEnabled(clientState); + else if (statement.isDCLStatement()) + Guardrails.dclEnabled.ensureEnabled(clientState); + ResultMessage result = options.getConsistency() == ConsistencyLevel.NODE_LOCAL ? processNodeLocalStatement(statement, queryState, options) : statement.execute(queryState, options, queryStartNanoTime); @@ -459,6 +465,11 @@ public static Prepared parseAndPrepare(String query, ClientState clientState, bo CQLStatement statement = raw.prepare(clientState); statement.validate(clientState); + if (statement.isDDLStatement()) + Guardrails.ddlEnabled.ensureEnabled(clientState); + else if (statement.isDCLStatement()) + Guardrails.dclEnabled.ensureEnabled(clientState); + // Set CQL string for AlterSchemaStatement as this is used to serialize the transformation // in the cluster metadata log if (statement instanceof AlterSchemaStatement) diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java index 8387e1c426a7..aadf6c788db2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java @@ -24,7 +24,6 @@ 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; @@ -79,8 +78,6 @@ 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java index db3aa99273d9..04b7faad5ae7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java @@ -68,5 +68,11 @@ public String obfuscatePassword(String query) { return query; } + + @Override + public boolean isDCLStatement() + { + return true; + } } diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java index 46285c6730c2..c57995093cfa 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java @@ -67,4 +67,10 @@ public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } + + @Override + public boolean isDCLStatement() + { + return true; + } } diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java index 38ee838c500c..d6e0a1298cde 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java @@ -23,7 +23,6 @@ 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; @@ -80,8 +79,6 @@ 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java index 2196240ecbc6..13ba54a52d9d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java @@ -22,7 +22,6 @@ 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; @@ -63,8 +62,6 @@ 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java index 23bad04207df..e809a27a45e9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java @@ -21,7 +21,6 @@ import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.cql3.RoleName; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; @@ -57,8 +56,6 @@ 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java index 31e48b27255d..14bdef0f68bc 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java @@ -81,8 +81,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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)); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java index 8907a93f8f0a..c70e719fb9c4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java @@ -51,6 +51,12 @@ protected AlterSchemaStatement(String keyspaceName) this.keyspaceName = keyspaceName; } + @Override + public boolean isDDLStatement() + { + return true; + } + public void setCql(String cql) { this.cql = cql; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java index 5883c0d30924..15ed785ca674 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java @@ -223,9 +223,6 @@ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetad if (Objects.equals(oldMask, newMask)) return keyspace; - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch); tableBuilder.alterColumnMask(columnName, newMask); TableMetadata newTable = tableBuilder.build(); @@ -320,9 +317,6 @@ 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"); @@ -443,9 +437,6 @@ 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); @@ -518,9 +509,6 @@ 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); @@ -574,9 +562,6 @@ public void validate(ClientState state) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata) { - // 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); @@ -621,9 +606,6 @@ private DropCompactStorage(String keyspaceName, String tableName, boolean ifTabl public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata) { - // 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."); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java index 8f7bf920f118..fefe70e1c6e2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java @@ -142,8 +142,6 @@ 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)) @@ -218,9 +216,6 @@ 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) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java index 1e3759b82194..831e46b0ca2d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java @@ -71,9 +71,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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 diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java index f766bf6fdf8e..6428d85073ef 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java @@ -38,7 +38,6 @@ import org.apache.cassandra.cql3.functions.UserFunction; import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.terms.Term; -import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.schema.UserFunctions.FunctionsDiff; import org.apache.cassandra.schema.KeyspaceMetadata; @@ -231,9 +230,6 @@ public Keyspaces apply(ClusterMetadata metadata) } } - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.userFunctions.withAddedOrUpdated(aggregate))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java index 0187935afac9..20923687ff03 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java @@ -33,7 +33,6 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UserFunction; -import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.schema.UserFunctions.FunctionsDiff; import org.apache.cassandra.schema.KeyspaceMetadata; @@ -155,9 +154,6 @@ public Keyspaces apply(ClusterMetadata metadata) // 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.userFunctions.withAddedOrUpdated(function))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java index 85a7ba0bea9d..bf00c581c25c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java @@ -192,9 +192,6 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire(INDEX_DUPLICATE_OF_EXISTING, 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(); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java index 89c4c2c7cfdd..16a117c890e5 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java @@ -76,9 +76,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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)) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java index 7af57cc48be0..264877db7ccf 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java @@ -127,9 +127,6 @@ public Keyspaces apply(ClusterMetadata metadata) if (!table.params.compression.isEnabled()) Guardrails.uncompressedTablesEnabled.ensureEnabled(state); - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.with(table))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java index 3d88d87d06b8..8555430c1f27 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java @@ -22,7 +22,6 @@ import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QualifiedName; import org.apache.cassandra.exceptions.InvalidRequestException; -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; @@ -72,9 +71,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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.loadTriggerClass(triggerClass); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java index fd5d56420b1f..bd63310f18fa 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java @@ -92,9 +92,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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 usedNames = new HashSet<>(); for (FieldIdentifier name : fieldNames) if (!usedNames.add(name)) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java index 75d847b1d230..a4a8ddcfbefe 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java @@ -144,9 +144,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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 */ diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java index 5a53caa1b6e9..e61332ee1203 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java @@ -31,7 +31,6 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.functions.UserFunction; -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; @@ -113,9 +112,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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.userFunctions.without(aggregate))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java index 09135624e453..0353964683b5 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java @@ -31,7 +31,6 @@ import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UserFunction; -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; @@ -115,9 +114,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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.userFunctions .aggregatesUsingFunction(function) diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java index 1d142b9c3c95..06cbc4be9961 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java @@ -22,7 +22,6 @@ 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; @@ -62,9 +61,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java index c16fda0ef7d3..e074da54a33b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java @@ -46,11 +46,7 @@ public Keyspaces apply(ClusterMetadata metadata) Keyspaces schema = metadata.schema.getKeyspaces(); if (schema.containsKeyspace(keyspaceName)) - { - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); return schema.without(keyspaceName); - } if (ifExists) return schema; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java index f4892d9563d3..56848a8c2275 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java @@ -67,9 +67,6 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); } - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - if (table.isView()) throw ire("Cannot use DROP TABLE on a materialized view. Please use DROP MATERIALIZED VIEW instead."); diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java index dfe0ecc56416..15007c3b1e41 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java @@ -21,7 +21,6 @@ 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; @@ -66,9 +65,6 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Trigger '%s' on '%s.%s' doesn't exist", triggerName, keyspaceName, tableName); } - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - TableMetadata newTable = table.withSwapped(table.triggers.without(triggerName)); return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.withSwapped(newTable))); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java index 00e1583b435b..105c8f5db8cf 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java @@ -25,7 +25,6 @@ import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.UTName; import org.apache.cassandra.cql3.functions.UserFunction; -import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; @@ -77,9 +76,6 @@ public Keyspaces apply(ClusterMetadata metadata) throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName); } - // if apply is not no-op then we check guardrail for this ddl op - Guardrails.ddlEnabled.ensureEnabled(state); - /* * We don't want to drop a type unless it's not used anymore (mainly because * if someone drops a type and recreates one with the same name but different diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java index 2212acfc6ebe..121575503cf8 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java @@ -22,7 +22,6 @@ 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.Keyspaces.KeyspacesDiff; import org.apache.cassandra.service.ClientState; @@ -61,9 +60,6 @@ public Keyspaces apply(ClusterMetadata metadata) 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); - return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.without(viewName))); }