This is an automated email from the ASF dual-hosted git repository. samt pushed a commit to branch cassandra-4.1 in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit d14d16926e52078eb86fb06bbc13715670c7c650 Merge: 18c777f641 1848efaf53 Author: Sam Tunnicliffe <[email protected]> AuthorDate: Fri Jan 17 09:07:33 2025 +0000 Merge branch 'cassandra-4.0' into cassandra-4.1 CHANGES.txt | 3 + src/java/org/apache/cassandra/auth/Permission.java | 7 ++ src/java/org/apache/cassandra/auth/Resources.java | 28 ++++- .../cql3/statements/GrantPermissionsStatement.java | 21 ++++ .../apache/cassandra/schema/SchemaConstants.java | 10 ++ .../org/apache/cassandra/service/ClientState.java | 60 ++++++++-- .../apache/cassandra/auth/GrantAndRevokeTest.java | 127 ++++++++++++++++++++- test/unit/org/apache/cassandra/cql3/CQLTester.java | 8 ++ .../apache/cassandra/service/ClientStateTest.java | 4 +- 9 files changed, 248 insertions(+), 20 deletions(-) diff --cc CHANGES.txt index 48ba5a2bae,1ef6a902e4..17f31338ae --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -19,10 -16,9 +19,13 @@@ Merged from 4.0 * Fix text containing "/*" being interpreted as multiline comment in cqlsh (CASSANDRA-17667) * Fix indexing of a frozen collection that is the clustering key and reversed (CASSANDRA-19889) * Emit error when altering a table with non-frozen UDTs with nested non-frozen collections the same way as done upon table creation (CASSANDRA-19925) ++Merged from 3.0 ++ * Tighten up permissions on system keyspaces (CASSANDRA-20090) ++ * Fix incorrect column identifier bytes problem when renaming a column (CASSANDRA-18956) -4.0.14 +4.1.7 +Merged from 4.0: * Safer handling of out-of-range tokens (CASSANDRA-13704) * Fix memory leak in BTree.FastBuilder (CASSANDRA-19785) * Fix millisecond and microsecond precision for commit log replay (CASSANDRA-19448) diff --cc src/java/org/apache/cassandra/auth/Permission.java index d552280e64,d552280e64..11c7aeb05b --- a/src/java/org/apache/cassandra/auth/Permission.java +++ b/src/java/org/apache/cassandra/auth/Permission.java @@@ -66,4 -66,4 +66,11 @@@ public enum Permissio public static final Set<Permission> ALL = Sets.immutableEnumSet(EnumSet.range(Permission.CREATE, Permission.EXECUTE)); public static final Set<Permission> NONE = ImmutableSet.of(); ++ ++ /** ++ * Set of Permissions which may never be granted on any system keyspace, or table in a system keyspace, to any role. ++ * (Only SELECT, DESCRIBE and ALTER may ever be granted). ++ */ ++ public static final Set<Permission> INVALID_FOR_SYSTEM_KEYSPACES = ++ Sets.immutableEnumSet(EnumSet.complementOf(EnumSet.of(Permission.SELECT, Permission.DESCRIBE, Permission.ALTER))); } diff --cc src/java/org/apache/cassandra/auth/Resources.java index 653cd46e32,653cd46e32..2863710632 --- a/src/java/org/apache/cassandra/auth/Resources.java +++ b/src/java/org/apache/cassandra/auth/Resources.java @@@ -19,6 -19,6 +19,7 @@@ package org.apache.cassandra.auth import java.util.ArrayList; import java.util.List; ++import java.util.function.Predicate; import org.apache.cassandra.utils.Hex; @@@ -27,18 -27,18 +28,33 @@@ public final class Resource /** * Construct a chain of resource parents starting with the resource and ending with the root. * -- * @param resource The staring point. ++ * @param resource The starting point. * @return list of resource in the chain form start to the root. */ public static List<? extends IResource> chain(IResource resource) { -- List<IResource> chain = new ArrayList<IResource>(); ++ return chain(resource, (r) -> true); ++ } ++ ++ /** ++ * Construct a chain of resource parents starting with the resource and ending with the root. Only resources which ++ * satisfy the supplied predicate will be included. ++ * ++ * @param resource The starting point. ++ * @param filter can be used to omit specific resources from the chain ++ * @return list of resource in the chain form start to the root. ++ */ ++ public static List<? extends IResource> chain(IResource resource, Predicate<IResource> filter) ++ { ++ ++ List<IResource> chain = new ArrayList<>(4); while (true) { -- chain.add(resource); -- if (!resource.hasParent()) -- break; -- resource = resource.getParent(); ++ if (filter.test(resource)) ++ chain.add(resource); ++ if (!resource.hasParent()) ++ break; ++ resource = resource.getParent(); } return chain; } diff --cc src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java index 824c4856d5,3db20e3841..5e165f5841 --- a/src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java @@@ -17,20 -17,17 +17,24 @@@ */ package org.apache.cassandra.cql3.statements; ++import java.util.Collections; import java.util.Set; +import java.util.stream.Collectors; import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.audit.AuditLogEntryType; ++import org.apache.cassandra.auth.DataResource; +import org.apache.cassandra.auth.IAuthorizer; import org.apache.cassandra.auth.IResource; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.RoleName; import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; ++import org.apache.cassandra.exceptions.UnauthorizedException; ++import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.transport.messages.ResultMessage; public class GrantPermissionsStatement extends PermissionsManagementStatement @@@ -40,27 -37,9 +44,44 @@@ super(permissions, resource, grantee); } ++ public void validate(ClientState state) throws RequestValidationException ++ { ++ super.validate(state); ++ if (resource instanceof DataResource) ++ { ++ DataResource data = (DataResource) resource; ++ // Only a subset of permissions can be granted on non-virtual system keyspaces ++ if (!data.isRootLevel() ++ && SchemaConstants.isNonVirtualSystemKeyspace(data.getKeyspace()) ++ && !Collections.disjoint(permissions, Permission.INVALID_FOR_SYSTEM_KEYSPACES)) ++ { ++ throw new UnauthorizedException("Granting permissions on system keyspaces is strictly limited, " + ++ "this operation is not permitted"); ++ } ++ } ++ } ++ public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException { - DatabaseDescriptor.getAuthorizer().grant(state.getUser(), permissions, resource, grantee); + IAuthorizer authorizer = DatabaseDescriptor.getAuthorizer(); + Set<Permission> granted = authorizer.grant(state.getUser(), permissions, resource, grantee); + + // We want to warn the client if all the specified permissions have not been granted and the client did + // not specify ALL in the query. + if (!granted.equals(permissions) && !permissions.equals(Permission.ALL)) + { + String permissionsStr = permissions.stream() + .filter(permission -> !granted.contains(permission)) + .sorted(Permission::compareTo) // guarantee the order for testing + .map(Permission::name) + .collect(Collectors.joining(", ")); + + ClientWarn.instance.warn(String.format("Role '%s' was already granted %s on %s", + grantee.getRoleName(), + permissionsStr, + resource)); + } + return null; } diff --cc src/java/org/apache/cassandra/schema/SchemaConstants.java index 888ea4a31f,7b6b7de490..d4cf888a9f --- a/src/java/org/apache/cassandra/schema/SchemaConstants.java +++ b/src/java/org/apache/cassandra/schema/SchemaConstants.java @@@ -112,16 -104,8 +112,26 @@@ public final class SchemaConstant */ public static boolean isSystemKeyspace(String keyspaceName) { - return isLocalSystemKeyspace(keyspaceName) - || isReplicatedSystemKeyspace(keyspaceName) - || isVirtualSystemKeyspace(keyspaceName); + return isLocalSystemKeyspace(keyspaceName) // this includes vtables + || isReplicatedSystemKeyspace(keyspaceName); + } + ++ /** ++ * @return whether or not the keyspace is a non-virtual, system keyspace ++ */ ++ public static boolean isNonVirtualSystemKeyspace(String keyspaceName) ++ { ++ final String lowercaseKeyspaceName = keyspaceName.toLowerCase(); ++ return LOCAL_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName) ++ || REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName); ++ } ++ + /** + * Returns the set of all system keyspaces + * @return all system keyspaces + */ + public static Set<String> getSystemKeyspaces() + { + return Sets.union(Sets.union(LOCAL_SYSTEM_KEYSPACE_NAMES, REPLICATED_SYSTEM_KEYSPACE_NAMES), VIRTUAL_SYSTEM_KEYSPACE_NAMES); } } diff --cc src/java/org/apache/cassandra/service/ClientState.java index 9e35c7f4b0,f76e7e3a4f..21243cd68c --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@@ -21,18 -21,11 +21,18 @@@ import java.net.InetAddress import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; --import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@@ -54,6 -47,6 +54,7 @@@ import org.apache.cassandra.dht.Datacen import org.apache.cassandra.exceptions.AuthenticationException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; ++import org.apache.cassandra.tracing.TraceKeyspace; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.MD5Digest; @@@ -67,29 -58,29 +68,39 @@@ public class ClientStat { private static final Logger logger = LoggerFactory.getLogger(ClientState.class); -- private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new HashSet<>(); -- private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new HashSet<>(); ++ public static final ImmutableSet<IResource> READABLE_SYSTEM_RESOURCES; ++ public static final ImmutableSet<IResource> PROTECTED_AUTH_RESOURCES; static { // We want these system cfs to be always readable to authenticated users since many tools rely on them // (nodetool, cqlsh, bulkloader, etc.) -- for (String cf : Arrays.asList(SystemKeyspace.LOCAL, SystemKeyspace.LEGACY_PEERS, SystemKeyspace.PEERS_V2)) -- READABLE_SYSTEM_RESOURCES.add(DataResource.table(SchemaConstants.SYSTEM_KEYSPACE_NAME, cf)); ++ ImmutableSet.Builder<IResource> readableBuilder = ImmutableSet.builder(); ++ for (String cf : Arrays.asList(SystemKeyspace.LOCAL, SystemKeyspace.LEGACY_PEERS, SystemKeyspace.PEERS_V2, ++ SystemKeyspace.LEGACY_SIZE_ESTIMATES, SystemKeyspace.TABLE_ESTIMATES)) ++ readableBuilder.add(DataResource.table(SchemaConstants.SYSTEM_KEYSPACE_NAME, cf)); // make all schema tables readable by default (required by the drivers) -- SchemaKeyspaceTables.ALL.forEach(table -> READABLE_SYSTEM_RESOURCES.add(DataResource.table(SchemaConstants.SCHEMA_KEYSPACE_NAME, table))); ++ SchemaKeyspaceTables.ALL.forEach(table -> readableBuilder.add(DataResource.table(SchemaConstants.SCHEMA_KEYSPACE_NAME, table))); ++ ++ // make system_traces readable by all or else tracing will require explicit grants ++ readableBuilder.add(DataResource.table(SchemaConstants.TRACE_KEYSPACE_NAME, TraceKeyspace.EVENTS)); ++ readableBuilder.add(DataResource.table(SchemaConstants.TRACE_KEYSPACE_NAME, TraceKeyspace.SESSIONS)); // make all virtual schema tables readable by default as well -- VirtualSchemaKeyspace.instance.tables().forEach(t -> READABLE_SYSTEM_RESOURCES.add(t.metadata().resource)); ++ VirtualSchemaKeyspace.instance.tables().forEach(t -> readableBuilder.add(t.metadata().resource)); ++ READABLE_SYSTEM_RESOURCES = readableBuilder.build(); ++ ImmutableSet.Builder<IResource> protectedBuilder = ImmutableSet.builder(); // neither clients nor tools need authentication/authorization if (DatabaseDescriptor.isDaemonInitialized()) { -- PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthenticator().protectedResources()); -- PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthorizer().protectedResources()); -- PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getRoleManager().protectedResources()); ++ protectedBuilder.addAll(DatabaseDescriptor.getAuthenticator().protectedResources()); ++ protectedBuilder.addAll(DatabaseDescriptor.getAuthorizer().protectedResources()); ++ protectedBuilder.addAll(DatabaseDescriptor.getRoleManager().protectedResources()); } ++ ++ PROTECTED_AUTH_RESOURCES = protectedBuilder.build(); } // Current user for the session @@@ -425,12 -384,12 +436,16 @@@ preventSystemKSSchemaModification(keyspace, resource, perm); ++ // Some system data is always readable if ((perm == Permission.SELECT) && READABLE_SYSTEM_RESOURCES.contains(resource)) return; ++ // Modifications to any resource upon which the authenticator, authorizer or role manager depend should not be ++ // be performed by users if (PROTECTED_AUTH_RESOURCES.contains(resource)) if ((perm == Permission.CREATE) || (perm == Permission.ALTER) || (perm == Permission.DROP)) throw new UnauthorizedException(String.format("%s schema is protected", resource)); ++ ensurePermission(perm, resource); } @@@ -444,6 -403,6 +459,24 @@@ if (((FunctionResource)resource).getKeyspace().equals(SchemaConstants.SYSTEM_KEYSPACE_NAME)) return; ++ if (resource instanceof DataResource && isOrdinaryUser()) ++ { ++ DataResource dataResource = (DataResource)resource; ++ if (!dataResource.isRootLevel()) ++ { ++ String keyspace = dataResource.getKeyspace(); ++ // A user may have permissions granted on ALL KEYSPACES, but this should exclude system keyspaces. Any ++ // permission on those keyspaces or their tables must be granted to the user either explicitly or ++ // transitively. The set of grantable permissions for non-virtual system keyspaces is further limited, ++ // see the Permission enum for details. ++ if (SchemaConstants.isSystemKeyspace(keyspace)) ++ { ++ ensurePermissionOnResourceChain(perm, Resources.chain(dataResource, IResource::hasParent)); ++ return; ++ } ++ } ++ } ++ ensurePermissionOnResourceChain(perm, resource); } @@@ -466,11 -425,7 +499,16 @@@ private void ensurePermissionOnResourceChain(Permission perm, IResource resource) { - List<? extends IResource> resources = Resources.chain(resource); - for (IResource r : Resources.chain(resource)) ++ ensurePermissionOnResourceChain(perm, Resources.chain(resource)); ++ } ++ ++ private void ensurePermissionOnResourceChain(Permission perm, List<? extends IResource> resources) ++ { ++ IResource resource = resources.get(0); + if (DatabaseDescriptor.getAuthFromRoot()) + resources = Lists.reverse(resources); + + for (IResource r : resources) if (authorize(r).contains(perm)) return; diff --cc test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java index 5c1c2a0298,0000000000..73923ea9d4 mode 100644,000000..100644 --- a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java +++ b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java @@@ -1,388 -1,0 +1,511 @@@ +/* + * 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.auth; + ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++import com.google.common.collect.Iterables; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.datastax.driver.core.ResultSet; +import org.apache.cassandra.Util; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; - ++import org.apache.cassandra.db.ConsistencyLevel; ++import org.apache.cassandra.db.SystemKeyspace; ++import org.apache.cassandra.schema.Schema; ++import org.apache.cassandra.schema.SchemaConstants; ++import org.apache.cassandra.schema.TableMetadata; ++import org.apache.cassandra.transport.ProtocolVersion; ++ ++import static java.lang.String.format; ++import static org.apache.cassandra.schema.SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES; ++import static org.apache.cassandra.schema.SchemaConstants.REPLICATED_SYSTEM_KEYSPACE_NAMES; ++import static org.apache.cassandra.schema.SchemaConstants.SCHEMA_KEYSPACE_NAME; ++import static org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME; ++import static org.apache.cassandra.schema.SchemaConstants.TRACE_KEYSPACE_NAME; +import static org.junit.Assert.assertTrue; + +public class GrantAndRevokeTest extends CQLTester +{ + private static final String user = "user"; + private static final String pass = "12345"; + + @BeforeClass + public static void setUpClass() + { + DatabaseDescriptor.setPermissionsValidity(0); + CQLTester.setUpClass(); + requireAuthentication(); + requireNetwork(); + } + + @After + public void tearDown() throws Throwable + { + useSuperUser(); + executeNet("DROP ROLE " + user); + } + + @Test + public void testGrantedKeyspace() throws Throwable + { + useSuperUser(); + + executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass)); + executeNet("GRANT CREATE ON KEYSPACE " + KEYSPACE_PER_TEST + " TO " + user); + String table = KEYSPACE_PER_TEST + '.' + createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"); + String index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)"); + String type = KEYSPACE_PER_TEST + '.' + createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (a int, b text)"); + String mv = KEYSPACE_PER_TEST + ".ks_mv_01"; + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + + useUser(user, pass); + + // ALTER and DROP tables created by somebody else + // Spin assert for effective auth changes. + final String spinAssertTable = table; + Util.spinAssertEquals(false, () -> { + try + { + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + spinAssertTable + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + assertUnauthorizedQuery("User user has no DROP permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + assertUnauthorizedQuery("User user has no ALTER permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "ALTER TYPE " + type + " ADD c bigint"); + assertUnauthorizedQuery("User user has no DROP permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "DROP TYPE " + type); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP MATERIALIZED VIEW " + mv); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP INDEX " + index); + + + useSuperUser(); + + executeNet("GRANT ALTER, DROP, SELECT, MODIFY ON KEYSPACE " + KEYSPACE_PER_TEST + " TO " + user); + + useUser(user, pass); + + // Spin assert for effective auth changes. + Util.spinAssertEquals(false, () -> { + try + { + executeNet("ALTER KEYSPACE " + KEYSPACE_PER_TEST + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + + executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1")); + assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1"), row(1, 1, 1, "1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + executeNet("DROP MATERIALIZED VIEW " + mv); + executeNet("DROP INDEX " + index); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + executeNet("ALTER TYPE " + type + " ADD c bigint"); + executeNet("DROP TYPE " + type); + + // calling creatTableName to create a new table name that will be used by the formatQuery + table = createTableName(); + type = KEYSPACE_PER_TEST + "." + createTypeName(); + mv = KEYSPACE_PER_TEST + ".ks_mv_02"; + executeNet("CREATE TYPE " + type + " (a int, b text)"); + executeNet(formatQuery(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))")); + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1")); + assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1"), row(1, 1, 1, "1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + executeNet("DROP MATERIALIZED VIEW " + mv); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + executeNet("ALTER TYPE " + type + " ADD c bigint"); + executeNet("DROP TYPE " + type); + + useSuperUser(); + + executeNet("REVOKE ALTER, DROP, MODIFY, SELECT ON KEYSPACE " + KEYSPACE_PER_TEST + " FROM " + user); + + table = KEYSPACE_PER_TEST + "." + createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"); + type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (a int, b text)"); + index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)"); + mv = KEYSPACE_PER_TEST + ".ks_mv_03"; + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + + useUser(user, pass); + + // Spin assert for effective auth changes. + final String spinAssertTable2 = table; + Util.spinAssertEquals(false, () -> { + try + { + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + spinAssertTable2 + "> or any of its parents", + "INSERT INTO " + spinAssertTable2 + " (pk, ck, val, val_2) VALUES (1, 1, 1, '1')"); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "UPDATE " + table + " SET val = 1 WHERE pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "DELETE FROM " + table + " WHERE pk = 1 AND ck = 2"); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + table + " WHERE pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "TRUNCATE TABLE " + table); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE " + table + " ADD val_3 int")); + assertUnauthorizedQuery("User user has no DROP permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DROP TABLE " + table)); + assertUnauthorizedQuery("User user has no ALTER permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "ALTER TYPE " + type + " ADD c bigint"); + assertUnauthorizedQuery("User user has no DROP permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "DROP TYPE " + type); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP MATERIALIZED VIEW " + mv); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP INDEX " + index); - + } + + @Test + public void testGrantedAllTables() throws Throwable + { + useSuperUser(); + + executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass)); + executeNet("GRANT CREATE ON ALL TABLES IN KEYSPACE " + KEYSPACE_PER_TEST + " TO " + user); + String table = KEYSPACE_PER_TEST + "." + createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"); + String index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)"); + String type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (a int, b text)"); + String mv = KEYSPACE_PER_TEST + ".alltables_mv_01"; + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + + useUser(user, pass); + + // ALTER and DROP tables created by somebody else + // Spin assert for effective auth changes. + final String spinAssertTable = table; + Util.spinAssertEquals(false, () -> { + try + { + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + spinAssertTable + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + assertUnauthorizedQuery("User user has no DROP permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + assertUnauthorizedQuery("User user has no ALTER permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "ALTER TYPE " + type + " ADD c bigint"); + assertUnauthorizedQuery("User user has no DROP permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "DROP TYPE " + type); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP MATERIALIZED VIEW " + mv); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP INDEX " + index); + + useSuperUser(); + + executeNet("GRANT ALTER, DROP, SELECT, MODIFY ON ALL TABLES IN KEYSPACE " + KEYSPACE_PER_TEST + " TO " + user); + + useUser(user, pass); + + // Spin assert for effective auth changes. + Util.spinAssertEquals(false, () -> { + try + { + assertUnauthorizedQuery("User user has no ALTER permission on <keyspace " + KEYSPACE_PER_TEST + "> or any of its parents", + "ALTER KEYSPACE " + KEYSPACE_PER_TEST + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1")); + assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1"), row(1, 1, 1, "1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + executeNet("DROP MATERIALIZED VIEW " + mv); + executeNet("DROP INDEX " + index); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + executeNet("ALTER TYPE " + type + " ADD c bigint"); + executeNet("DROP TYPE " + type); + + // calling creatTableName to create a new table name that will be used by the formatQuery + table = createTableName(); + type = KEYSPACE_PER_TEST + "." + createTypeName(); + mv = KEYSPACE_PER_TEST + ".alltables_mv_02"; + executeNet("CREATE TYPE " + type + " (a int, b text)"); + executeNet(formatQuery(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))")); + index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)"); + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 WHERE pk = 1 AND ck = 1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 1 AND ck = 2")); + assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1")); + assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1"), row(1, 1, 1, "1")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s")); + executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 int")); + executeNet("DROP MATERIALIZED VIEW " + mv); + executeNet("DROP INDEX " + index); + executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s")); + executeNet("ALTER TYPE " + type + " ADD c bigint"); + executeNet("DROP TYPE " + type); + + useSuperUser(); + + executeNet("REVOKE ALTER, DROP, SELECT, MODIFY ON ALL TABLES IN KEYSPACE " + KEYSPACE_PER_TEST + " FROM " + user); + + table = KEYSPACE_PER_TEST + "." + createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"); + index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)"); + type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (a int, b text)"); + mv = KEYSPACE_PER_TEST + ".alltables_mv_03"; + executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (val, pk, ck)"); + + useUser(user, pass); + + // Spin assert for effective auth changes. + final String spinAssertTable2 = table; + Util.spinAssertEquals(false, () -> { + try + { + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + spinAssertTable2 + "> or any of its parents", + "INSERT INTO " + spinAssertTable2 + " (pk, ck, val, val_2) VALUES (1, 1, 1, '1')"); + } + catch(Throwable e) + { + return true; + } + return false; + }, 10); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "UPDATE " + table + " SET val = 1 WHERE pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "DELETE FROM " + table + " WHERE pk = 1 AND ck = 2"); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + table + " WHERE pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no SELECT permission on <table " + table + "> or any of its parents", + "SELECT * FROM " + mv + " WHERE val = 1 AND pk = 1 AND ck = 1"); + assertUnauthorizedQuery("User user has no MODIFY permission on <table " + table + "> or any of its parents", + "TRUNCATE TABLE " + table); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE " + table + " ADD val_3 int")); + assertUnauthorizedQuery("User user has no DROP permission on <table " + table + "> or any of its parents", + formatQuery(KEYSPACE_PER_TEST, "DROP TABLE " + table)); + assertUnauthorizedQuery("User user has no ALTER permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "ALTER TYPE " + type + " ADD c bigint"); + assertUnauthorizedQuery("User user has no DROP permission on <all tables in " + KEYSPACE_PER_TEST + "> or any of its parents", + "DROP TYPE " + type); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP MATERIALIZED VIEW " + mv); + assertUnauthorizedQuery("User user has no ALTER permission on <table " + table + "> or any of its parents", + "DROP INDEX " + index); + } + + @Test + public void testWarnings() throws Throwable + { + useSuperUser(); + + executeNet("CREATE KEYSPACE revoke_yeah WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); + executeNet("CREATE TABLE revoke_yeah.t1 (id int PRIMARY KEY, val text)"); + executeNet("CREATE USER '" + user + "' WITH PASSWORD '" + pass + "'"); + + ResultSet res = executeNet("REVOKE CREATE ON KEYSPACE revoke_yeah FROM " + user); + assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted CREATE on <keyspace revoke_yeah>"); + + res = executeNet("GRANT SELECT ON KEYSPACE revoke_yeah TO " + user); + assertTrue(res.getExecutionInfo().getWarnings().isEmpty()); + + res = executeNet("GRANT SELECT ON KEYSPACE revoke_yeah TO " + user); + assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was already granted SELECT on <keyspace revoke_yeah>"); + + res = executeNet("REVOKE SELECT ON TABLE revoke_yeah.t1 FROM " + user); + assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted SELECT on <table revoke_yeah.t1>"); + + res = executeNet("REVOKE SELECT, MODIFY ON KEYSPACE revoke_yeah FROM " + user); + assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" + user + "' was not granted MODIFY on <keyspace revoke_yeah>"); + } ++ ++ @Test ++ public void testSpecificGrantsOnSystemKeyspaces() throws Throwable ++ { ++ // Granting specific permissions on system keyspaces should not be allowed if those permissions include any from ++ // the denylist Permission.INVALID_FOR_SYSTEM_KEYSPACES. By this definition, GRANT ALL on any system keyspace, ++ // or a table within one, should be rejected. ++ useSuperUser(); ++ executeNet("CREATE ROLE '" + user + "'"); ++ String responseMsg = "Granting permissions on system keyspaces is strictly limited, this operation is not permitted"; ++ for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, REPLICATED_SYSTEM_KEYSPACE_NAMES)) ++ { ++ assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON KEYSPACE %s TO %s", keyspace, user)); ++ DataResource keyspaceResource = DataResource.keyspace(keyspace); ++ for (Permission p : keyspaceResource.applicablePermissions()) ++ maybeRejectGrant(p, responseMsg, format("GRANT %s ON KEYSPACE %s TO %s", p.name(), keyspace, user)); ++ ++ assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON ALL TABLES IN KEYSPACE %s TO %s", keyspace, user)); ++ for (TableMetadata table : Schema.instance.getKeyspaceMetadata(keyspace).tables) ++ { ++ DataResource tableResource = DataResource.table(keyspace, table.name); ++ assertUnauthorizedQuery(responseMsg, format("GRANT ALL PERMISSIONS ON %s TO %s", table, user)); ++ for (Permission p : tableResource.applicablePermissions()) ++ maybeRejectGrant(p, responseMsg, format("GRANT %s ON %s TO %s", p.name(), table, user)); ++ } ++ } ++ } ++ ++ @Test ++ public void testGrantOnAllKeyspaces() throws Throwable ++ { ++ // Granting either specific or ALL permissions on ALL KEYSPACES is allowed, however these permissions are ++ // effective for non-system keyspaces only. If for any reason it is necessary to modify permissions on ++ // on a system keyspace, it must be done using keyspace specific grant statements. ++ useSuperUser(); ++ executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass)); ++ executeNet(String.format("ALTER KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", SchemaConstants.TRACE_KEYSPACE_NAME)); ++ executeNet("CREATE KEYSPACE user_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); ++ executeNet("CREATE TABLE user_keyspace.t1 (k int PRIMARY KEY)"); ++ useUser(user, pass); ++ ++ assertUnauthorizedQuery("User user has no MODIFY permission on <table user_keyspace.t1> or any of its parents", ++ "INSERT INTO user_keyspace.t1 (k) VALUES (0)"); ++ assertUnauthorizedQuery("User user has no MODIFY permission on <table system.local> or any of its parents", ++ "INSERT INTO system.local(key) VALUES ('invalid')"); ++ ++ useSuperUser(); ++ executeNet(ProtocolVersion.CURRENT, format("GRANT MODIFY ON ALL KEYSPACES TO %s", user)); ++ ++ useUser(user, pass); ++ // User now has write permission on non-system keyspaces only ++ executeNet(ProtocolVersion.CURRENT, "INSERT INTO user_keyspace.t1 (k) VALUES (0)"); ++ assertUnauthorizedQuery("User user has no MODIFY permission on <table system.local> or any of its parents", ++ "INSERT INTO system.local(key) VALUES ('invalid')"); ++ ++ // A non-superuser only has read access to a pre-defined set of system tables and all system_schema/traces ++ // tables and granting ALL permissions on ALL keyspaces also does not affect this. ++ maybeReadSystemTables(false); ++ useSuperUser(); ++ executeNet(ProtocolVersion.CURRENT, format("GRANT ALL PERMISSIONS ON ALL KEYSPACES TO %s", user)); ++ maybeReadSystemTables(false); ++ ++ // A superuser can still read system tables ++ useSuperUser(); ++ maybeReadSystemTables(true); ++ // and also write to them, though this is still strongly discouraged ++ executeNet(ProtocolVersion.CURRENT, "INSERT INTO system.peers_v2(peer, peer_port, data_center) VALUES ('127.0.100.100', 7012, 'invalid_dc')"); ++ } ++ ++ private void maybeReadSystemTables(boolean superuser) throws Throwable ++ { ++ if (superuser) ++ useSuperUser(); ++ else ++ useUser(user, pass); ++ ++ Set<String> readableKeyspaces = new HashSet<>(Arrays.asList(SCHEMA_KEYSPACE_NAME, TRACE_KEYSPACE_NAME)); ++ Set<String> readableSystemTables = new HashSet<>(Arrays.asList(SystemKeyspace.LOCAL, ++ SystemKeyspace.PEERS_V2, ++ SystemKeyspace.LEGACY_PEERS, ++ SystemKeyspace.LEGACY_SIZE_ESTIMATES, ++ SystemKeyspace.TABLE_ESTIMATES)); ++ ++ for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, REPLICATED_SYSTEM_KEYSPACE_NAMES)) ++ { ++ for (TableMetadata table : Schema.instance.getKeyspaceMetadata(keyspace).tables) ++ { ++ if (superuser || (readableKeyspaces.contains(keyspace) || (keyspace.equals(SYSTEM_KEYSPACE_NAME) && readableSystemTables.contains(table.name)))) ++ { ++ executeNet(ProtocolVersion.CURRENT, ConsistencyLevel.ONE, format("SELECT * FROM %s LIMIT 1", table)); ++ } ++ else ++ { ++ assertUnauthorizedQuery(format("User %s has no SELECT permission on %s or any of its parents", user, table.resource), ++ format("SELECT * FROM %s LIMIT 1", table)); ++ } ++ } ++ } ++ } ++ ++ private void maybeRejectGrant(Permission p, String errorResponse, String grant) throws Throwable ++ { ++ if (Permission.INVALID_FOR_SYSTEM_KEYSPACES.contains(p)) ++ assertUnauthorizedQuery(errorResponse, grant); ++ else ++ executeNet(ProtocolVersion.CURRENT, grant); ++ } +} diff --cc test/unit/org/apache/cassandra/cql3/CQLTester.java index 61bb4839b8,48fddec68a..c20a0cea2a --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@@ -61,24 -59,13 +61,25 @@@ import com.datastax.driver.core.excepti import com.datastax.shaded.netty.channel.EventLoopGroup; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.ServerTestUtils; +import org.apache.cassandra.Util; +import org.apache.cassandra.auth.AuthCacheService; +import org.apache.cassandra.auth.AuthKeyspace; +import org.apache.cassandra.auth.AuthSchemaChangeListener; +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.concurrent.Stage; +import org.apache.cassandra.config.DataStorageSpec; +import org.apache.cassandra.config.EncryptionOptions; ++import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry; import org.apache.cassandra.db.virtual.VirtualSchemaKeyspace; +import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.index.SecondaryIndexManager; -import org.apache.cassandra.config.EncryptionOptions; +import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.TokenMetadata; +import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.schema.*; import org.apache.cassandra.config.DatabaseDescriptor; @@@ -1258,6 -1014,6 +1259,13 @@@ public abstract class CQLTeste return Schema.instance.getTableMetadata(KEYSPACE, currentTable()); } ++ protected com.datastax.driver.core.ResultSet executeNet(ProtocolVersion protocolVersion, ConsistencyLevel consistency, String query) throws Throwable ++ { ++ Statement statement = new SimpleStatement(formatQuery(query)); ++ statement = statement.setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel.valueOf(consistency.name())); ++ return sessionNet(protocolVersion).execute(statement); ++ } ++ protected com.datastax.driver.core.ResultSet executeNet(ProtocolVersion protocolVersion, String query, Object... values) throws Throwable { return sessionNet(protocolVersion).execute(formatQuery(query), values); diff --cc test/unit/org/apache/cassandra/service/ClientStateTest.java index 56d0893356,0000000000..05d1f78036 mode 100644,000000..100644 --- a/test/unit/org/apache/cassandra/service/ClientStateTest.java +++ b/test/unit/org/apache/cassandra/service/ClientStateTest.java @@@ -1,112 -1,0 +1,114 @@@ +/* + * 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.service; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.collect.Iterables; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.auth.AuthCacheService; +import org.apache.cassandra.auth.AuthKeyspace; +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.DataResource; +import org.apache.cassandra.auth.IResource; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.Roles; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.schema.TableMetadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class ClientStateTest +{ + @BeforeClass + public static void beforeClass() + { + System.setProperty("org.apache.cassandra.disable_mbean_registration", "true"); + SchemaLoader.prepareServer(); + DatabaseDescriptor.setAuthFromRoot(true); + // create the system_auth keyspace so the IRoleManager can function as normal + SchemaLoader.createKeyspace(SchemaConstants.AUTH_KEYSPACE_NAME, + KeyspaceParams.simple(1), + Iterables.toArray(AuthKeyspace.metadata().tables, TableMetadata.class)); - ++ DatabaseDescriptor.setRoleManager(new AuthTestUtils.LocalCassandraRoleManager()); ++ DatabaseDescriptor.getRoleManager().setup(); ++ Roles.init(); + AuthCacheService.initializeAndRegisterCaches(); + } + + @AfterClass + public static void afterClass() + { + System.clearProperty("org.apache.cassandra.disable_mbean_registration"); + } + + @Test + public void permissionsCheckStartsAtHeadOfResourceChain() + { + // verify that when performing a permissions check, we start from the + // root IResource in the applicable hierarchy and proceed to the more + // granular resources until we find the required permission (or until + // we reach the end of the resource chain). This is because our typical + // usage is to grant blanket permissions on the root resources to users + // and so we save lookups, cache misses and cache space by traversing in + // this order. e.g. for DataResources, we typically grant perms on the + // 'data' resource, so when looking up a users perms on a specific table + // it makes sense to follow: data -> keyspace -> table + + final AtomicInteger getPermissionsRequestCount = new AtomicInteger(0); + final IResource rootResource = DataResource.root(); + final IResource tableResource = DataResource.table("test_ks", "test_table"); + final AuthenticatedUser testUser = new AuthenticatedUser("test_user") + { + public Set<Permission> getPermissions(IResource resource) + { + getPermissionsRequestCount.incrementAndGet(); + if (resource.equals(rootResource)) + return Permission.ALL; + + fail(String.format("Permissions requested for unexpected resource %s", resource)); + // need a return to make the compiler happy + return null; + } + + public boolean canLogin() { return true; } + }; + + Roles.cache.invalidate(); + + // finally, need to configure CassandraAuthorizer so we don't shortcircuit out of the authz process + DatabaseDescriptor.setAuthorizer(new AuthTestUtils.LocalCassandraAuthorizer()); + + // check permissions on the table, which should check for the root resource first + // & return successfully without needing to proceed further + ClientState state = ClientState.forInternalCalls(); + state.login(testUser); + state.ensurePermission(Permission.SELECT, tableResource); + assertEquals(1, getPermissionsRequestCount.get()); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
