diff --git a/CHANGES.txt b/CHANGES.txt index 1cfb1fe875..693042563b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1 + * Implement Virtual Tables for Auth Caches (CASSANDRA-16914) * Actively update auth cache in the background (CASSANDRA-16957) * Add unix time conversion functions (CASSANDRA-17029) * JVMStabilityInspector.forceHeapSpaceOomMaybe should handle all non-heap OOMs rather than only supporting direct only (CASSANDRA-17128) diff --git a/NEWS.txt b/NEWS.txt index db91c27d55..9f5a9f1604 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -69,6 +69,9 @@ New features - Added a new feature to allow denylisting (i.e. blocking read, write, or range read configurable) access to partition keys in configured keyspaces and tables. See doc/operating/denylisting_partitions.rst for details on using this new feature. Also see CASSANDRA-12106. + - Information about pending hints is now available through `nodetool listpendinghints` and `pending_hints` virtual + table. + - Added ability to invalidate auth caches through corresponding `nodetool` commands and virtual tables. Upgrading --------- @@ -76,8 +79,6 @@ Upgrading confirm it is set to value lower than 31 otherwise Cassandra will fail to start. See CASSANDRA-9384 for further details. You also need to regenerate passwords for users for who the password was created while the above property was set to be more than 30 otherwise they will not be able to log in. - - Information about pending hints is now available through `nodetool listpendinghints` and `pending_hints` virtual - table. Deprecation ----------- diff --git a/doc/source/new/virtualtables.rst b/doc/source/new/virtualtables.rst index 0114543c23..50a37c2df5 100644 --- a/doc/source/new/virtualtables.rst +++ b/doc/source/new/virtualtables.rst @@ -17,8 +17,7 @@ Virtual Tables -------------- -Apache Cassandra 4.0 implements virtual tables (`CASSANDRA-7622 -`_). +Virtual tables are supported starting from version 4.0. Definition ^^^^^^^^^^ @@ -92,52 +91,69 @@ Virtual tables in a virtual keyspace may be listed with ``DESC TABLES``. The `` cqlsh> USE system_views; cqlsh:system_views> DESC TABLES; - coordinator_scans clients tombstones_scanned internode_inbound - disk_usage sstable_tasks live_scanned caches - local_writes max_partition_size local_reads - coordinator_writes internode_outbound thread_pools - local_scans coordinator_reads settings + caches internode_outbound roles_cache_keys + clients jmx_permissions_cache_keys rows_per_read + coordinator_read_latency local_read_latency settings + coordinator_scan_latency local_scan_latency sstable_tasks + coordinator_write_latency local_write_latency system_properties + credentials_cache_keys max_partition_size thread_pools + disk_usage network_permissions_cache_keys tombstones_per_read + internode_inbound permissions_cache_keys Some of the salient virtual tables in ``system_views`` virtual keyspace are described in Table 1. Table 1 : Virtual Tables in system_views -+------------------+---------------------------------------------------+ -|Virtual Table | Description | -+------------------+---------------------------------------------------+ -| clients |Lists information about all connected clients. | -+------------------+---------------------------------------------------+ -| disk_usage |Disk usage including disk_space, keyspace_name, | -| |and table_name by system keyspaces. | -+------------------+---------------------------------------------------+ -| local_writes |A table metric for local writes | -| |including count, keyspace_name, | -| |max, median, per_second, and | -| |table_name. | -+------------------+---------------------------------------------------+ -| caches |Displays the general cache information including | -| |cache name, capacity_bytes, entry_count, hit_count,| -| |hit_ratio double, recent_hit_rate_per_second, | -| |recent_request_rate_per_second, request_count, and | -| |size_bytes. | -+------------------+---------------------------------------------------+ -| local_reads |A table metric for local reads information. | -+------------------+---------------------------------------------------+ -| sstable_tasks |Lists currently running tasks such as compactions | -| |and upgrades on SSTables. | -+------------------+---------------------------------------------------+ -|internode_inbound |Lists information about the inbound | -| |internode messaging. | -+------------------+---------------------------------------------------+ -| thread_pools |Lists metrics for each thread pool. | -+------------------+---------------------------------------------------+ -| settings |Displays configuration settings in cassandra.yaml. | -+------------------+---------------------------------------------------+ -|max_partition_size|A table metric for maximum partition size. | -+------------------+---------------------------------------------------+ -|internode_outbound|Information about the outbound internode messaging.| -| | | -+------------------+---------------------------------------------------+ ++------------------------------+---------------------------------------------------+ +|Virtual Table | Description | ++------------------------------+---------------------------------------------------+ +| clients |Lists information about all connected clients. | ++------------------------------+---------------------------------------------------+ +| disk_usage |Disk usage including disk_space, keyspace_name, | +| |and table_name by system keyspaces. | ++------------------------------+---------------------------------------------------+ +| local_writes |A table metric for local writes | +| |including count, keyspace_name, | +| |max, median, per_second, and | +| |table_name. | ++------------------------------+---------------------------------------------------+ +| caches |Displays the general cache information including | +| |cache name, capacity_bytes, entry_count, hit_count,| +| |hit_ratio double, recent_hit_rate_per_second, | +| |recent_request_rate_per_second, request_count, and | +| |size_bytes. | ++------------------------------+---------------------------------------------------+ +| local_reads |A table metric for local reads information. | ++------------------------------+---------------------------------------------------+ +| sstable_tasks |Lists currently running tasks such as compactions | +| |and upgrades on SSTables. | ++------------------------------+---------------------------------------------------+ +| internode_inbound |Lists information about the inbound internode | +| |messaging. | ++------------------------------+---------------------------------------------------+ +| thread_pools |Lists metrics for each thread pool. | ++------------------------------+---------------------------------------------------+ +| settings |Displays configuration settings in cassandra.yaml. | ++------------------------------+---------------------------------------------------+ +| max_partition_size |A table metric for maximum partition size. | ++------------------------------+---------------------------------------------------+ +| internode_outbound |Information about the outbound internode messaging.| ++------------------------------+---------------------------------------------------+ +| credentials_cache_keys |Displays credentials cache keys. Supports DELETE | +| |and TRUNCATE operations. | ++------------------------------+---------------------------------------------------+ +| jmx_permissions_cache_keys |Displays JMX permissions cache keys. Supports | +| |DELETE and TRUNCATE operations. | ++------------------------------+---------------------------------------------------+ +|network_permissions_cache_keys|Displays netwrok permissions cache keys. Supports | +| |DELETE and TRUNCATE operations. | ++------------------------------+---------------------------------------------------+ +| permissions_cache_keys |Displays permissions cache keys. Supports DELETE | +| |and TRUNCATE operations. | ++------------------------------+---------------------------------------------------+ +| roles_cache_keys |Displays roles cache keys. Supports DELETE and | +| |TRUNCATE operations. | ++------------------------------+---------------------------------------------------+ We shall discuss some of the virtual tables in more detail next. @@ -187,7 +203,7 @@ The virtual tables may be described with ``DESCRIBE`` statement. The DDL listed Caches Virtual Table ******************** -The ``caches`` virtual table lists information about the caches. The four caches presently created are chunks, counters, keys and rows. A query on the ``caches`` virtual table returns the following details: +The ``caches`` virtual table lists information about the caches. The four caches presently created are chunks, counters, keys and rows. A query on the ``caches`` virtual table returns the following details: :: @@ -307,6 +323,57 @@ As another example, to find how much time is remaining for SSTable tasks, use th SELECT total - progress AS remaining FROM system_views.sstable_tasks; +Auth Caches Keys Virtual Tables +**************************** + +Every authentication cache has a separate virtual table associated. The virtual tables show the keys stored in caches +and additionally support DELETE and TRUNCATE operations. In fact these operations just invalidate data in caches, no +data is actually deleted from real tables. + +The tables show the following information: + +:: + + cqlsh:system_views> select * from credentials_cache_keys; + role + ------ + bob + (1 rows) + +:: + + cqlsh:system_views> select * from jmx_permissions_cache_keys; + role + ------ + bob + (1 rows) + +:: + + cqlsh:system_views> select * from network_permissions_cache_keys; + role + ------ + bob + (1 rows) + +:: + + cqlsh:system_views> select * from permissions_cache_keys; + role | resource + ------+------------- + bob | roles/alice + bob | data/ks1 + (2 rows) + +:: + + cqlsh:system_views> select * from roles_cache_keys; + role + ------ + bob + (1 rows) + + Other Virtual Tables ******************** diff --git a/src/java/org/apache/cassandra/auth/AuthCache.java b/src/java/org/apache/cassandra/auth/AuthCache.java index a70afbad77..04d37849e9 100644 --- a/src/java/org/apache/cassandra/auth/AuthCache.java +++ b/src/java/org/apache/cassandra/auth/AuthCache.java @@ -18,7 +18,9 @@ package org.apache.cassandra.auth; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -188,6 +190,17 @@ public class AuthCache implements AuthCacheMBean, Shutdownable return MBEAN_NAME_BASE + name; } + /** + * Retrive all cached entries. Will call {@link LoadingCache#asMap()} which does not trigger "load". + * @return a map of cached key-value pairs + */ + public Map getAll() + { + if (cache == null) + return Collections.emptyMap(); + + return Collections.unmodifiableMap(cache.asMap()); + } /** * Retrieve a value from the cache. Will call {@link LoadingCache#get(Object)} which will * "load" the value if it's not present, thus populating the key. @@ -213,7 +226,7 @@ public class AuthCache implements AuthCacheMBean, Shutdownable } /** - * Invalidate a key + * Invalidate a key. * @param k key to invalidate */ public void invalidate(K k) diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java index 678295c7b5..f0f927e7ea 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -38,8 +38,8 @@ public class AuthenticatedUser public static final AuthenticatedUser ANONYMOUS_USER = new AuthenticatedUser(ANONYMOUS_USERNAME); // User-level permissions cache. - private static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getAuthorizer()); - private static final NetworkPermissionsCache networkPermissionsCache = new NetworkPermissionsCache(DatabaseDescriptor.getNetworkAuthorizer()); + public static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getAuthorizer()); + public static final NetworkPermissionsCache networkPermissionsCache = new NetworkPermissionsCache(DatabaseDescriptor.getNetworkAuthorizer()); private final String name; // primary Role of the logged in user diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java index ac1dbb9f70..1ac0088396 100644 --- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java @@ -81,6 +81,11 @@ public class PasswordAuthenticator implements IAuthenticator return true; } + public CredentialsCache getCredentialsCache() + { + return cache; + } + protected static boolean checkpw(String password, String hash) { try @@ -271,7 +276,7 @@ public class PasswordAuthenticator implements IAuthenticator } } - private static class CredentialsCache extends AuthCache implements CredentialsCacheMBean + public static class CredentialsCache extends AuthCache implements CredentialsCacheMBean { private CredentialsCache(PasswordAuthenticator authenticator) { diff --git a/src/java/org/apache/cassandra/auth/PermissionsCache.java b/src/java/org/apache/cassandra/auth/PermissionsCache.java index fd1fce8c8d..9f200a0e93 100644 --- a/src/java/org/apache/cassandra/auth/PermissionsCache.java +++ b/src/java/org/apache/cassandra/auth/PermissionsCache.java @@ -45,8 +45,8 @@ public class PermissionsCache extends AuthCache memberOf; diff --git a/src/java/org/apache/cassandra/auth/Roles.java b/src/java/org/apache/cassandra/auth/Roles.java index 527451e703..401d208572 100644 --- a/src/java/org/apache/cassandra/auth/Roles.java +++ b/src/java/org/apache/cassandra/auth/Roles.java @@ -20,11 +20,8 @@ package org.apache.cassandra.auth; import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.function.BooleanSupplier; import java.util.stream.Collectors; -import com.google.common.annotations.VisibleForTesting; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,26 +35,8 @@ public class Roles private static final Role NO_ROLE = new Role("", false, false, Collections.emptyMap(), Collections.emptySet()); - private static RolesCache cache; - static - { - initRolesCache(DatabaseDescriptor.getRoleManager(), - () -> DatabaseDescriptor.getAuthenticator().requireAuthentication()); - } - - @VisibleForTesting - public static void initRolesCache(IRoleManager roleManager, BooleanSupplier enableCache) - { - if (cache != null) - cache.unregisterMBean(); - cache = new RolesCache(roleManager, enableCache); - } - - @VisibleForTesting - public static void clearCache() - { - cache.invalidate(); - } + public static final RolesCache cache = new RolesCache(DatabaseDescriptor.getRoleManager(), + () -> DatabaseDescriptor.getAuthenticator().requireAuthentication()); /** * Identify all roles granted to the supplied Role, including both directly granted diff --git a/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java b/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java index 77bd5c0a1d..674abe6a6d 100644 --- a/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java +++ b/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java @@ -104,7 +104,7 @@ public class AuthorizationProxy implements InvocationHandler "registerMBean", "unregisterMBean"); - private static final JmxPermissionsCache permissionsCache = new JmxPermissionsCache(); + public static final JmxPermissionsCache jmxPermissionsCache = new JmxPermissionsCache(); private MBeanServer mbs; /* @@ -118,7 +118,7 @@ public class AuthorizationProxy implements InvocationHandler the permissions from the local cache, which in turn loads them from the configured IAuthorizer but can be overridden for testing. */ - protected Function> getPermissions = permissionsCache::get; + protected Function> getPermissions = jmxPermissionsCache::get; /* Used to decide whether authorization is enabled or not, usually this depends on the configured @@ -477,7 +477,7 @@ public class AuthorizationProxy implements InvocationHandler .collect(Collectors.toSet()); } - private static final class JmxPermissionsCache extends AuthCache> + public static final class JmxPermissionsCache extends AuthCache> implements JmxPermissionsCacheMBean { protected JmxPermissionsCache() diff --git a/src/java/org/apache/cassandra/db/virtual/AbstractMutableVirtualTable.java b/src/java/org/apache/cassandra/db/virtual/AbstractMutableVirtualTable.java index c273e51ebc..7044312eac 100644 --- a/src/java/org/apache/cassandra/db/virtual/AbstractMutableVirtualTable.java +++ b/src/java/org/apache/cassandra/db/virtual/AbstractMutableVirtualTable.java @@ -26,7 +26,7 @@ import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.ClusteringBound; @@ -145,7 +145,7 @@ public abstract class AbstractMutableVirtualTable extends AbstractVirtualTable throw invalidRequest("Range deletion is not supported by table %s", metadata); } - protected void applyRowDeletion(ColumnValues partitionKey, ColumnValues clusteringColumnValues) + protected void applyRowDeletion(ColumnValues partitionKey, ColumnValues clusteringColumns) { throw invalidRequest("Row deletion is not supported by table %s", metadata); } diff --git a/src/java/org/apache/cassandra/db/virtual/CredentialsCacheKeysTable.java b/src/java/org/apache/cassandra/db/virtual/CredentialsCacheKeysTable.java new file mode 100644 index 0000000000..f5bc62c526 --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/CredentialsCacheKeysTable.java @@ -0,0 +1,78 @@ +/* + * 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.virtual; + +import java.util.Optional; + +import org.apache.cassandra.auth.IAuthenticator; +import org.apache.cassandra.auth.PasswordAuthenticator; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; + +final class CredentialsCacheKeysTable extends AbstractMutableVirtualTable +{ + private static final String ROLE = "role"; + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private final Optional passwordAuthenticatorOptional; + + CredentialsCacheKeysTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "credentials_cache_keys") + .comment("keys in the credentials cache") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(ROLE, UTF8Type.instance) + .build()); + + IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator(); + if (authenticator instanceof PasswordAuthenticator) + this.passwordAuthenticatorOptional = Optional.of((PasswordAuthenticator) authenticator); + else + this.passwordAuthenticatorOptional = Optional.empty(); + } + + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + passwordAuthenticatorOptional + .ifPresent(passwordAuthenticator -> passwordAuthenticator.getCredentialsCache().getAll() + .forEach((roleName, ignored) -> result.row(roleName))); + + return result; + } + + @Override + protected void applyPartitionDeletion(ColumnValues partitionKey) + { + String roleName = partitionKey.value(0); + + passwordAuthenticatorOptional + .ifPresent(passwordAuthenticator -> passwordAuthenticator.getCredentialsCache().invalidate(roleName)); + } + + @Override + public void truncate() + { + passwordAuthenticatorOptional + .ifPresent(passwordAuthenticator -> passwordAuthenticator.getCredentialsCache().invalidate()); + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTable.java b/src/java/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTable.java new file mode 100644 index 0000000000..b048b92a54 --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTable.java @@ -0,0 +1,63 @@ +/* + * 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.virtual; + +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.jmx.AuthorizationProxy; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; + +final class JmxPermissionsCacheKeysTable extends AbstractMutableVirtualTable +{ + private static final String ROLE = "role"; + + JmxPermissionsCacheKeysTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "jmx_permissions_cache_keys") + .comment("keys in the JMX permissions cache") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(ROLE, UTF8Type.instance) + .build()); + } + + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + AuthorizationProxy.jmxPermissionsCache.getAll() + .forEach((roleResource, ignored) -> result.row(roleResource.getRoleName())); + + return result; + } + + @Override + protected void applyPartitionDeletion(ColumnValues partitionKey) + { + RoleResource roleResource = RoleResource.role(partitionKey.value(0)); + + AuthorizationProxy.jmxPermissionsCache.invalidate(roleResource); + } + + @Override + public void truncate() + { + AuthorizationProxy.jmxPermissionsCache.invalidate(); + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTable.java b/src/java/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTable.java new file mode 100644 index 0000000000..0c0c62557e --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTable.java @@ -0,0 +1,63 @@ +/* + * 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.virtual; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; + +final class NetworkPermissionsCacheKeysTable extends AbstractMutableVirtualTable +{ + private static final String ROLE = "role"; + + NetworkPermissionsCacheKeysTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "network_permissions_cache_keys") + .comment("keys in the network permissions cache") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(ROLE, UTF8Type.instance) + .build()); + } + + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + AuthenticatedUser.networkPermissionsCache.getAll() + .forEach((roleResource, ignored) -> result.row(roleResource.getRoleName())); + + return result; + } + + @Override + protected void applyPartitionDeletion(ColumnValues partitionKey) + { + RoleResource roleResource = RoleResource.role(partitionKey.value(0)); + + AuthenticatedUser.networkPermissionsCache.invalidate(roleResource); + } + + @Override + public void truncate() + { + AuthenticatedUser.networkPermissionsCache.invalidate(); + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/PermissionsCacheKeysTable.java b/src/java/org/apache/cassandra/db/virtual/PermissionsCacheKeysTable.java new file mode 100644 index 0000000000..e2b4fd1ffe --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/PermissionsCacheKeysTable.java @@ -0,0 +1,84 @@ +/* + * 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.virtual; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.IResource; +import org.apache.cassandra.auth.Resources; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.utils.Pair; + +final class PermissionsCacheKeysTable extends AbstractMutableVirtualTable +{ + private static final String ROLE = "role"; + private static final String RESOURCE = "resource"; + + PermissionsCacheKeysTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "permissions_cache_keys") + .comment("keys in the permissions cache") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(ROLE, UTF8Type.instance) + .addPartitionKeyColumn(RESOURCE, UTF8Type.instance) + .build()); + } + + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + AuthenticatedUser.permissionsCache.getAll() + .forEach((userResoursePair, ignored) -> + result.row(userResoursePair.left.getName(), userResoursePair.right.getName())); + + return result; + } + + @Override + protected void applyPartitionDeletion(ColumnValues partitionKey) + { + AuthenticatedUser user = new AuthenticatedUser(partitionKey.value(0)); + IResource resource = resourceFromNameIfExists(partitionKey.value(1)); + // no need to delete invalid resource + if (resource == null) + return; + + AuthenticatedUser.permissionsCache.invalidate(Pair.create(user, resource)); + } + + @Override + public void truncate() + { + AuthenticatedUser.permissionsCache.invalidate(); + } + + private IResource resourceFromNameIfExists(String name) + { + try + { + return Resources.fromName(name); + } + catch (IllegalArgumentException e) + { + return null; + } + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/RolesCacheKeysTable.java b/src/java/org/apache/cassandra/db/virtual/RolesCacheKeysTable.java new file mode 100644 index 0000000000..58e0949b0d --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/RolesCacheKeysTable.java @@ -0,0 +1,63 @@ +/* + * 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.virtual; + +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.Roles; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; + +final class RolesCacheKeysTable extends AbstractMutableVirtualTable +{ + private static final String ROLE = "role"; + + RolesCacheKeysTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "roles_cache_keys") + .comment("keys in the roles cache") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(ROLE, UTF8Type.instance) + .build()); + } + + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + Roles.cache.getAll() + .forEach((roleResource, ignored) -> result.row(roleResource.getRoleName())); + + return result; + } + + @Override + protected void applyPartitionDeletion(ColumnValues partitionKey) + { + RoleResource roleResource = RoleResource.role(partitionKey.value(0)); + + Roles.cache.invalidate(roleResource); + } + + @Override + public void truncate() + { + Roles.cache.invalidate(); + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java index 77773a7bc1..6dfd8a95ff 100644 --- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java +++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java @@ -38,6 +38,11 @@ public final class SystemViewsKeyspace extends VirtualKeyspace .add(new InternodeInboundTable(VIRTUAL_VIEWS)) .add(new PendingHintsTable(VIRTUAL_VIEWS)) .addAll(TableMetricTables.getAll(VIRTUAL_VIEWS)) + .add(new CredentialsCacheKeysTable(VIRTUAL_VIEWS)) + .add(new JmxPermissionsCacheKeysTable(VIRTUAL_VIEWS)) + .add(new NetworkPermissionsCacheKeysTable(VIRTUAL_VIEWS)) + .add(new PermissionsCacheKeysTable(VIRTUAL_VIEWS)) + .add(new RolesCacheKeysTable(VIRTUAL_VIEWS)) .build()); } } diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index 46481b41b9..07a5804b61 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -544,9 +544,9 @@ public class NodeProbe implements AutoCloseable pcProxy.invalidate(); } - public void invalidatePermissionsCache(String userName, String resourceName) + public void invalidatePermissionsCache(String roleName, String resourceName) { - pcProxy.invalidatePermissions(userName, resourceName); + pcProxy.invalidatePermissions(roleName, resourceName); } public void invalidateRolesCache() diff --git a/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java index 049e91bd10..f879f276af 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java +++ b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java @@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument; @Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache") public class InvalidatePermissionsCache extends NodeToolCmd { - @Arguments(usage = "[]", description = "A specific user for whom permissions need to be invalidated") + @Arguments(usage = "[]", description = "A role for which permissions to specified resources need to be invalidated") private List args = new ArrayList<>(); // Data Resources @@ -52,6 +52,11 @@ public class InvalidatePermissionsCache extends NodeToolCmd description = "Keyspace to invalidate permissions for") private String keyspace; + @Option(title = "all-tables", + name = {"--all-tables"}, + description = "Invalidate permissions for 'ALL TABLES'") + private boolean allTables; + @Option(title = "table", name = {"--table"}, description = "Table to invalidate permissions for (you must specify --keyspace for using this option)") @@ -105,28 +110,34 @@ public class InvalidatePermissionsCache extends NodeToolCmd && !allRoles && StringUtils.isEmpty(role) && !allFunctions && StringUtils.isEmpty(functionsInKeyspace) && StringUtils.isEmpty(function) && !allMBeans && StringUtils.isEmpty(mBean), - "No options allowed without a being specified"); + "No resource options allowed without a being specified"); probe.invalidatePermissionsCache(); } else { checkArgument(args.size() == 1, - "A single is only supported / you have a typo in the options spelling"); + "A single is only supported / you have a typo in the resource options spelling"); List resourceNames = new ArrayList<>(); // Data Resources if (allKeyspaces) resourceNames.add(DataResource.root().getName()); + if (allTables) + if (StringUtils.isNotEmpty(keyspace)) + resourceNames.add(DataResource.allTables(keyspace).getName()); + else + throw new IllegalArgumentException("--all-tables option should be passed along with --keyspace option"); + if (StringUtils.isNotEmpty(table)) if (StringUtils.isNotEmpty(keyspace)) resourceNames.add(DataResource.table(keyspace, table).getName()); else throw new IllegalArgumentException("--table option should be passed along with --keyspace option"); - else - if (StringUtils.isNotEmpty(keyspace)) - resourceNames.add(DataResource.keyspace(keyspace).getName()); + + if (StringUtils.isNotEmpty(keyspace) && !allTables && StringUtils.isEmpty(table)) + resourceNames.add(DataResource.keyspace(keyspace).getName()); // Roles Resources if (allRoles) @@ -155,10 +166,13 @@ public class InvalidatePermissionsCache extends NodeToolCmd if (StringUtils.isNotEmpty(mBean)) resourceNames.add(JMXResource.mbean(mBean).getName()); - String userName = args.get(0); + String roleName = args.get(0); + + if (resourceNames.isEmpty()) + throw new IllegalArgumentException("No resource options specified"); for (String resourceName : resourceNames) - probe.invalidatePermissionsCache(userName, resourceName); + probe.invalidatePermissionsCache(roleName, resourceName); } } diff --git a/test/unit/org/apache/cassandra/auth/AuthCacheTest.java b/test/unit/org/apache/cassandra/auth/AuthCacheTest.java index 15e6b1fad6..bf3d7bb687 100644 --- a/test/unit/org/apache/cassandra/auth/AuthCacheTest.java +++ b/test/unit/org/apache/cassandra/auth/AuthCacheTest.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.auth; +import java.util.Map; import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.IntConsumer; @@ -27,6 +28,7 @@ import org.junit.Test; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.exceptions.UnavailableException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -224,6 +226,43 @@ public class AuthCacheTest } } + @Test + public void testCacheLoaderIsNotCalledOnGetAllWhenCacheIsDisabled() + { + isCacheEnabled = false; + TestCache authCache = new TestCache<>(this::countingLoader, this::setValidity, () -> validity, () -> isCacheEnabled); + authCache.get("10"); + Map result = authCache.getAll(); + + // even though the cache is disabled and nothing is cache we still use loadFunction on get operation, so + // its counter has been incremented + assertThat(result).isEmpty(); + assertEquals(1, loadCounter); + } + + @Test + public void testCacheLoaderIsNotCalledOnGetAllWhenCacheIsEmpty() + { + TestCache authCache = new TestCache<>(this::countingLoader, this::setValidity, () -> validity, () -> isCacheEnabled); + + Map result = authCache.getAll(); + + assertThat(result).isEmpty(); + assertEquals(0, loadCounter); + } + + @Test + public void testCacheLoaderIsNotCalledOnGetAllWhenCacheIsNotEmpty() + { + TestCache authCache = new TestCache<>(this::countingLoader, this::setValidity, () -> validity, () -> isCacheEnabled); + authCache.get("10"); + Map result = authCache.getAll(); + + assertThat(result).hasSize(1); + assertThat(result).containsEntry("10", 10); + assertEquals(1, loadCounter); + } + private void setValidity(int validity) { this.validity = validity; diff --git a/test/unit/org/apache/cassandra/auth/AuthTestUtils.java b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java index 83bdf7100f..18e95a41de 100644 --- a/test/unit/org/apache/cassandra/auth/AuthTestUtils.java +++ b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java @@ -20,6 +20,7 @@ package org.apache.cassandra.auth; import java.util.concurrent.Callable; +import org.apache.cassandra.auth.jmx.AuthorizationProxy; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; @@ -121,6 +122,15 @@ public class AuthTestUtils } } + public static class NoAuthSetupAuthorizationProxy extends AuthorizationProxy + { + public NoAuthSetupAuthorizationProxy() + { + super(); + this.isAuthSetupComplete = () -> true; + } + } + public static void grantRolesTo(IRoleManager roleManager, RoleResource grantee, RoleResource...granted) { for(RoleResource toGrant : granted) diff --git a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java index f14b25387b..bfa89ec7db 100644 --- a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java +++ b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java @@ -43,13 +43,12 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.service.ClientState; import static org.apache.cassandra.auth.AuthKeyspace.NETWORK_PERMISSIONS; -import static org.apache.cassandra.auth.AuthTestUtils.LocalCassandraRoleManager; import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount; import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME; public class CassandraNetworkAuthorizerTest { - public static void setupSuperUser() + private static void setupSuperUser() { QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) " + "VALUES ('%s', true, true, '%s')", @@ -63,13 +62,12 @@ public class CassandraNetworkAuthorizerTest public static void defineSchema() throws ConfigurationException { SchemaLoader.prepareServer(); - SchemaLoader.setupAuth(new LocalCassandraRoleManager(), - new PasswordAuthenticator(), + SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), + new AuthTestUtils.LocalPasswordAuthenticator(), new AuthTestUtils.LocalCassandraAuthorizer(), new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + setupSuperUser(); - // not strictly necessary to init the cache here, but better to be explicit - Roles.initRolesCache(DatabaseDescriptor.getRoleManager(), () -> true); } @Before @@ -122,7 +120,7 @@ public class CassandraNetworkAuthorizerTest AuthenticationStatement authStmt = (AuthenticationStatement) statement; // invalidate roles cache so that any changes to the underlying roles are picked up - Roles.clearCache(); + Roles.cache.invalidate(); authStmt.execute(getClientState()); } @@ -193,7 +191,7 @@ public class CassandraNetworkAuthorizerTest assertDcPermRow(username, "dc1"); // clear the roles cache to lose the (non-)superuser status for the user - Roles.clearCache(); + Roles.cache.invalidate(); auth("ALTER ROLE %s WITH superuser = true", username); Assert.assertEquals(DCPermissions.all(), dcPerms(username)); } @@ -207,7 +205,7 @@ public class CassandraNetworkAuthorizerTest } @Test - public void getLoginPrivilegeFromRolesCache() throws Exception + public void getLoginPrivilegeFromRolesCache() { String username = createName(); auth("CREATE ROLE %s", username); diff --git a/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java b/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java index ea1ff65266..1df57ead38 100644 --- a/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java +++ b/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java @@ -55,7 +55,7 @@ public class CassandraRoleManagerTest // collects all of the necessary info with a single query for each granted role. // This just tests that that is the case, i.e. we perform 1 read per role in the // transitive set of granted roles - IRoleManager roleManager = new LocalCassandraRoleManager(); + IRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); roleManager.setup(); for (RoleResource r : ALL_ROLES) roleManager.createRole(AuthenticatedUser.ANONYMOUS_USER, r, new RoleOptions()); diff --git a/test/unit/org/apache/cassandra/auth/ResourcesTest.java b/test/unit/org/apache/cassandra/auth/ResourcesTest.java index bb279d9203..ca4d3dc49e 100644 --- a/test/unit/org/apache/cassandra/auth/ResourcesTest.java +++ b/test/unit/org/apache/cassandra/auth/ResourcesTest.java @@ -50,6 +50,9 @@ public class ResourcesTest assertEquals(DataResource.keyspace("ks1"), Resources.fromName("data/ks1")); assertEquals("data/ks1", DataResource.keyspace("ks1").getName()); + assertEquals(DataResource.allTables("ks1"), Resources.fromName("data/ks1/*")); + assertEquals("data/ks1/*", DataResource.allTables("ks1").getName()); + assertEquals(DataResource.table("ks1", "t1"), Resources.fromName("data/ks1/t1")); assertEquals("data/ks1/t1", DataResource.table("ks1", "t1").getName()); } diff --git a/test/unit/org/apache/cassandra/auth/RolesTest.java b/test/unit/org/apache/cassandra/auth/RolesTest.java index 40b38c9eb6..bba861de24 100644 --- a/test/unit/org/apache/cassandra/auth/RolesTest.java +++ b/test/unit/org/apache/cassandra/auth/RolesTest.java @@ -28,11 +28,13 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.schema.TableMetadata; -import static org.apache.cassandra.auth.AuthTestUtils.*; +import static org.apache.cassandra.auth.AuthTestUtils.ALL_ROLES; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_C; +import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount; +import static org.apache.cassandra.auth.AuthTestUtils.grantRolesTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -43,14 +45,12 @@ public class RolesTest public static void setupClass() { SchemaLoader.prepareServer(); - // 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)); + IRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); + SchemaLoader.setupAuth(roleManager, + new AuthTestUtils.LocalPasswordAuthenticator(), + new AuthTestUtils.LocalCassandraAuthorizer(), + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); - IRoleManager roleManager = new LocalCassandraRoleManager(); - roleManager.setup(); - Roles.initRolesCache(roleManager, () -> true); for (RoleResource role : ALL_ROLES) roleManager.createRole(AuthenticatedUser.ANONYMOUS_USER, role, new RoleOptions()); grantRolesTo(roleManager, ROLE_A, ROLE_B, ROLE_C); diff --git a/test/unit/org/apache/cassandra/db/virtual/CredentialsCacheKeysTableTest.java b/test/unit/org/apache/cassandra/db/virtual/CredentialsCacheKeysTableTest.java new file mode 100644 index 0000000000..70395633fa --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/CredentialsCacheKeysTableTest.java @@ -0,0 +1,168 @@ +/* + * 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.virtual; + +import com.google.common.collect.ImmutableList; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.datastax.driver.core.EndPoint; +import com.datastax.driver.core.PlainTextAuthProvider; +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.IAuthenticator; +import org.apache.cassandra.auth.IRoleManager; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; + +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; + +public class CredentialsCacheKeysTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + private static AuthTestUtils.LocalPasswordAuthenticator passwordAuthenticator; + + @SuppressWarnings("FieldCanBeLocal") + private CredentialsCacheKeysTable table; + + @BeforeClass + public static void setUpClass() + { + // high value is used for convenient debugging + DatabaseDescriptor.setCredentialsValidity(20_000); + + CQLTester.setUpClass(); + CQLTester.requireAuthentication(); + passwordAuthenticator = (AuthTestUtils.LocalPasswordAuthenticator) DatabaseDescriptor.getAuthenticator(); + + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); + } + + @Before + public void config() + { + table = new CredentialsCacheKeysTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // ensure nothing keeps cached between tests + passwordAuthenticator.getCredentialsCache().invalidate(); + } + + @AfterClass + public static void tearDownClass() + { + DatabaseDescriptor.setCredentialsValidity(DatabaseDescriptor.getRawConfig().credentials_validity_in_ms); + } + + @Test + public void testSelectAllWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.credentials_cache_keys")); + } + + @Test + public void testSelectAllWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.credentials_cache_keys"), + row("role_a"), + row("role_b")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.credentials_cache_keys WHERE role='role_a'")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.credentials_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testDeletePartition() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("DELETE FROM vts.credentials_cache_keys WHERE role='role_a'"); + + assertRows(execute("SELECT * FROM vts.credentials_cache_keys"), + row("role_b")); + } + + @Test + public void testDeletePartitionWithInvalidValues() throws Throwable + { + cachePermissions(ROLE_A); + + execute("DELETE FROM vts.credentials_cache_keys WHERE role='invalid_role'"); + + assertRows(execute("SELECT * FROM vts.credentials_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testTruncateTable() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("TRUNCATE vts.credentials_cache_keys"); + + assertEmpty(execute("SELECT * FROM vts.credentials_cache_keys")); + } + + @Test + public void testUnsupportedOperations() throws Throwable + { + // range tombstone is not supported, however, this table has no clustering columns, so it is not covered by tests + + // column deletion is not supported, however, this table has no regular columns, so it is not covered by tests + + // insert is not supported + assertInvalidMessage("Column modification is not supported by table vts.credentials_cache_keys", + "INSERT INTO vts.credentials_cache_keys (role) VALUES ('role_e')"); + + // update is not supported, however, this table has no regular columns, so it is not covered by tests + } + + private void cachePermissions(RoleResource roleResource) + { + IAuthenticator.SaslNegotiator saslNegotiator = passwordAuthenticator.newSaslNegotiator(null); + saslNegotiator.evaluateResponse(new PlainTextAuthProvider(roleResource.getRoleName(), "ignored") + .newAuthenticator((EndPoint) null, null) + .initialResponse()); + saslNegotiator.getAuthenticatedUser(); + } +} diff --git a/test/unit/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTableTest.java b/test/unit/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTableTest.java new file mode 100644 index 0000000000..0f16df2f9e --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/JmxPermissionsCacheKeysTableTest.java @@ -0,0 +1,188 @@ +/* + * 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.virtual; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.security.auth.Subject; + +import com.google.common.collect.ImmutableList; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.CassandraPrincipal; +import org.apache.cassandra.auth.IAuthorizer; +import org.apache.cassandra.auth.IResource; +import org.apache.cassandra.auth.IRoleManager; +import org.apache.cassandra.auth.JMXResource; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.jmx.AuthorizationProxy; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; + +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; + +public class JmxPermissionsCacheKeysTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + private static final AuthorizationProxy authorizationProxy = new AuthTestUtils.NoAuthSetupAuthorizationProxy(); + + @SuppressWarnings("FieldCanBeLocal") + private JmxPermissionsCacheKeysTable table; + + // this method is intentionally not called "setUpClass" to let it throw exception brought by startJMXServer method + @BeforeClass + public static void setup() throws Exception { + // high value is used for convenient debugging + DatabaseDescriptor.setPermissionsValidity(20_000); + + CQLTester.setUpClass(); + CQLTester.requireAuthentication(); + + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); + + List resources = Arrays.asList( + JMXResource.root(), + JMXResource.mbean("org.apache.cassandra.db:type=Tables,*")); + + IAuthorizer authorizer = DatabaseDescriptor.getAuthorizer(); + for (IResource resource : resources) + { + Set permissions = resource.applicablePermissions(); + authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, resource, ROLE_A); + authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, resource, ROLE_B); + } + + startJMXServer(); + } + + @Before + public void config() + { + table = new JmxPermissionsCacheKeysTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // ensure nothing keeps cached between tests + AuthorizationProxy.jmxPermissionsCache.invalidate(); + } + + @AfterClass + public static void tearDownClass() + { + DatabaseDescriptor.setPermissionsValidity(DatabaseDescriptor.getRawConfig().permissions_validity_in_ms); + } + + @Test + public void testSelectAllWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.jmx_permissions_cache_keys")); + } + + @Test + public void testSelectAllWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.jmx_permissions_cache_keys"), + row("role_a"), + row("role_b")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.jmx_permissions_cache_keys WHERE role='role_a'")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.jmx_permissions_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testDeletePartition() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("DELETE FROM vts.jmx_permissions_cache_keys WHERE role='role_a'"); + + assertRows(execute("SELECT * FROM vts.jmx_permissions_cache_keys"), + row("role_b")); + } + + @Test + public void testDeletePartitionWithInvalidValues() throws Throwable + { + cachePermissions(ROLE_A); + + execute("DELETE FROM vts.jmx_permissions_cache_keys WHERE role='invalid_role'"); + + assertRows(execute("SELECT * FROM vts.jmx_permissions_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testTruncateTable() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("TRUNCATE vts.jmx_permissions_cache_keys"); + + assertEmpty(execute("SELECT * FROM vts.jmx_permissions_cache_keys")); + } + + @Test + public void testUnsupportedOperations() throws Throwable + { + // range tombstone is not supported, however, this table has no clustering columns, so it is not covered by tests + + // column deletion is not supported, however, this table has no regular columns, so it is not covered by tests + + // insert is not supported + assertInvalidMessage("Column modification is not supported by table vts.jmx_permissions_cache_keys", + "INSERT INTO vts.jmx_permissions_cache_keys (role) VALUES ('role_e')"); + + // update is not supported, however, this table has no regular columns, so it is not covered by tests + } + + private void cachePermissions(RoleResource roleResource) + { + Subject userSubject = new Subject(); + userSubject.getPrincipals().add(new CassandraPrincipal(roleResource.getRoleName())); + + authorizationProxy.authorize(userSubject, "queryNames", null); + } +} diff --git a/test/unit/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTableTest.java b/test/unit/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTableTest.java new file mode 100644 index 0000000000..b8e76172f6 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/NetworkPermissionsCacheKeysTableTest.java @@ -0,0 +1,165 @@ +/* + * 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.virtual; + +import com.google.common.collect.ImmutableList; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.DCPermissions; +import org.apache.cassandra.auth.INetworkAuthorizer; +import org.apache.cassandra.auth.IRoleManager; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; + +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; + +public class NetworkPermissionsCacheKeysTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + + @SuppressWarnings("FieldCanBeLocal") + private NetworkPermissionsCacheKeysTable table; + + @BeforeClass + public static void setUpClass() + { + // high value is used for convenient debugging + DatabaseDescriptor.setPermissionsValidity(20_000); + + CQLTester.setUpClass(); + CQLTester.requireAuthentication(); + + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); + + INetworkAuthorizer networkAuthorizer = DatabaseDescriptor.getNetworkAuthorizer(); + networkAuthorizer.setRoleDatacenters(ROLE_A, DCPermissions.all()); + networkAuthorizer.setRoleDatacenters(ROLE_B, DCPermissions.subset(DATA_CENTER, DATA_CENTER_REMOTE)); + } + + @Before + public void config() + { + table = new NetworkPermissionsCacheKeysTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // ensure nothing keeps cached between tests + AuthenticatedUser.networkPermissionsCache.invalidate(); + } + + @AfterClass + public static void tearDownClass() + { + DatabaseDescriptor.setPermissionsValidity(DatabaseDescriptor.getRawConfig().permissions_validity_in_ms); + } + + @Test + public void testSelectAllWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.network_permissions_cache_keys")); + } + + @Test + public void testSelectAllWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.network_permissions_cache_keys"), + row("role_a"), + row("role_b")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.network_permissions_cache_keys WHERE role='role_a'")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.network_permissions_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testDeletePartition() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("DELETE FROM vts.network_permissions_cache_keys WHERE role='role_a'"); + + assertRows(execute("SELECT * FROM vts.network_permissions_cache_keys"), + row("role_b")); + } + + @Test + public void testDeletePartitionWithInvalidValues() throws Throwable + { + cachePermissions(ROLE_A); + + execute("DELETE FROM vts.network_permissions_cache_keys WHERE role='invalid_role'"); + + assertRows(execute("SELECT * FROM vts.network_permissions_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testTruncateTable() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("TRUNCATE vts.network_permissions_cache_keys"); + + assertEmpty(execute("SELECT * FROM vts.network_permissions_cache_keys")); + } + + @Test + public void testUnsupportedOperations() throws Throwable + { + // range tombstone is not supported, however, this table has no clustering columns, so it is not covered by tests + + // column deletion is not supported, however, this table has no regular columns, so it is not covered by tests + + // insert is not supported + assertInvalidMessage("Column modification is not supported by table vts.network_permissions_cache_keys", + "INSERT INTO vts.network_permissions_cache_keys (role) VALUES ('role_e')"); + + // update is not supported, however, this table has no regular columns, so it is not covered by tests + } + + private void cachePermissions(RoleResource roleResource) + { + AuthenticatedUser.networkPermissionsCache.get(roleResource); + } +} diff --git a/test/unit/org/apache/cassandra/db/virtual/PermissionsCacheKeysTableTest.java b/test/unit/org/apache/cassandra/db/virtual/PermissionsCacheKeysTableTest.java new file mode 100644 index 0000000000..699dd66472 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/PermissionsCacheKeysTableTest.java @@ -0,0 +1,185 @@ +/* + * 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.virtual; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.DataResource; +import org.apache.cassandra.auth.IAuthorizer; +import org.apache.cassandra.auth.IResource; +import org.apache.cassandra.auth.IRoleManager; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; + +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; + +public class PermissionsCacheKeysTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + + @SuppressWarnings("FieldCanBeLocal") + private PermissionsCacheKeysTable table; + + @BeforeClass + public static void setUpClass() + { + // high value is used for convenient debugging + DatabaseDescriptor.setPermissionsValidity(20_000); + + CQLTester.setUpClass(); + CQLTester.requireAuthentication(); + + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); + + List resources = Arrays.asList( + DataResource.root(), + DataResource.keyspace(KEYSPACE), + DataResource.table(KEYSPACE, "t1")); + + IAuthorizer authorizer = DatabaseDescriptor.getAuthorizer(); + for (IResource resource : resources) + { + Set permissions = resource.applicablePermissions(); + authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, resource, ROLE_A); + authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, resource, ROLE_B); + } + } + + @Before + public void config() + { + table = new PermissionsCacheKeysTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // ensure nothing keeps cached between tests + AuthenticatedUser.permissionsCache.invalidate(); + } + + @AfterClass + public static void tearDownClass() + { + DatabaseDescriptor.setPermissionsValidity(DatabaseDescriptor.getRawConfig().permissions_validity_in_ms); + } + + @Test + public void testSelectAllWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.permissions_cache_keys")); + } + + @Test + public void testSelectAllWhenPermissionsAreCached() throws Throwable + { + cachePermissionsForResource(ROLE_A, DataResource.root()); + cachePermissionsForResource(ROLE_A, DataResource.keyspace(KEYSPACE)); + cachePermissionsForResource(ROLE_B, DataResource.table(KEYSPACE, "t1")); + + assertRows(execute("SELECT * FROM vts.permissions_cache_keys"), + row("role_a", "data"), + row("role_a", "data/cql_test_keyspace"), + row("role_b", "data/cql_test_keyspace/t1")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.permissions_cache_keys WHERE role='role_a' AND resource='data'")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreCached() throws Throwable + { + cachePermissionsForResource(ROLE_A, DataResource.root()); + cachePermissionsForResource(ROLE_A, DataResource.keyspace(KEYSPACE)); + cachePermissionsForResource(ROLE_B, DataResource.table(KEYSPACE, "t1")); + + assertRows(execute("SELECT * FROM vts.permissions_cache_keys WHERE role='role_a' AND resource='data'"), + row("role_a", "data")); + } + + @Test + public void testDeletePartition() throws Throwable + { + cachePermissionsForResource(ROLE_A, DataResource.root()); + cachePermissionsForResource(ROLE_A, DataResource.keyspace(KEYSPACE)); + + execute("DELETE FROM vts.permissions_cache_keys WHERE role='role_a' AND resource='data'"); + + assertRows(execute("SELECT * FROM vts.permissions_cache_keys"), + row("role_a", "data/cql_test_keyspace")); + } + + @Test + public void testDeletePartitionWithInvalidValues() throws Throwable + { + cachePermissionsForResource(ROLE_A, DataResource.root()); + + execute("DELETE FROM vts.permissions_cache_keys WHERE role='invalid_role' AND resource='data'"); + execute("DELETE FROM vts.permissions_cache_keys WHERE role='role_a' AND resource='invalid_resource'"); + + assertRows(execute("SELECT * FROM vts.permissions_cache_keys WHERE role='role_a' AND resource='data'"), + row("role_a", "data")); + } + + @Test + public void testTruncateTable() throws Throwable + { + cachePermissionsForResource(ROLE_A, DataResource.root()); + cachePermissionsForResource(ROLE_B, DataResource.table(KEYSPACE, "t1")); + + execute("TRUNCATE vts.permissions_cache_keys"); + + assertEmpty(execute("SELECT * FROM vts.permissions_cache_keys")); + } + + @Test + public void testUnsupportedOperations() throws Throwable + { + // range tombstone is not supported, however, this table has no clustering columns, so it is not covered by tests + + // column deletion is not supported, however, this table has no regular columns, so it is not covered by tests + + // insert is not supported + assertInvalidMessage("Column modification is not supported by table vts.permissions_cache_keys", + "INSERT INTO vts.permissions_cache_keys (role, resource) VALUES ('role_e', 'data')"); + + // update is not supported, however, this table has no regular columns, so it is not covered by tests + } + + private void cachePermissionsForResource(RoleResource roleResource, IResource resource) + { + AuthenticatedUser role = new AuthenticatedUser(roleResource.getRoleName()); + role.getPermissions(resource); + } +} diff --git a/test/unit/org/apache/cassandra/db/virtual/RolesCacheKeysTableTest.java b/test/unit/org/apache/cassandra/db/virtual/RolesCacheKeysTableTest.java new file mode 100644 index 0000000000..5a4ccf2570 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/RolesCacheKeysTableTest.java @@ -0,0 +1,165 @@ +/* + * 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.virtual; + +import com.google.common.collect.ImmutableList; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.IRoleManager; +import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.Roles; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; + +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; +import static org.apache.cassandra.auth.AuthTestUtils.ROLE_C; + +public class RolesCacheKeysTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + + @SuppressWarnings("FieldCanBeLocal") + private RolesCacheKeysTable table; + + @BeforeClass + public static void setUpClass() + { + // high value is used for convenient debugging + DatabaseDescriptor.setRolesValidity(20_000); + + CQLTester.setUpClass(); + CQLTester.requireAuthentication(); + + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); + roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_C, AuthTestUtils.getLoginRoleOptions()); + + AuthTestUtils.grantRolesTo(roleManager, ROLE_A, ROLE_C); + AuthTestUtils.grantRolesTo(roleManager, ROLE_B, ROLE_C); + } + + @Before + public void config() + { + table = new RolesCacheKeysTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // ensure nothing keeps cached between tests + Roles.cache.invalidate(); + } + + @AfterClass + public static void tearDownClass() + { + DatabaseDescriptor.setRolesValidity(DatabaseDescriptor.getRawConfig().roles_validity_in_ms); + } + + @Test + public void testSelectAllWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.roles_cache_keys")); + } + + @Test + public void testSelectAllWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.roles_cache_keys"), + row("role_a"), + row("role_b")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreNotCached() throws Throwable + { + assertEmpty(execute("SELECT * FROM vts.roles_cache_keys WHERE role='role_a'")); + } + + @Test + public void testSelectPartitionWhenPermissionsAreCached() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + assertRows(execute("SELECT * FROM vts.roles_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testDeletePartition() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("DELETE FROM vts.roles_cache_keys WHERE role='role_a'"); + + assertRows(execute("SELECT * FROM vts.roles_cache_keys"), + row("role_b")); + } + + @Test + public void testDeletePartitionWithInvalidValues() throws Throwable + { + cachePermissions(ROLE_A); + + execute("DELETE FROM vts.roles_cache_keys WHERE role='invalid_role'"); + + assertRows(execute("SELECT * FROM vts.roles_cache_keys WHERE role='role_a'"), + row("role_a")); + } + + @Test + public void testTruncateTable() throws Throwable + { + cachePermissions(ROLE_A); + cachePermissions(ROLE_B); + + execute("TRUNCATE vts.roles_cache_keys"); + + assertEmpty(execute("SELECT * FROM vts.roles_cache_keys")); + } + + @Test + public void testUnsupportedOperations() throws Throwable + { + // range tombstone is not supported, however, this table has no clustering columns, so it is not covered by tests + + // column deletion is not supported, however, this table has no regular columns, so it is not covered by tests + + // insert is not supported + assertInvalidMessage("Column modification is not supported by table vts.roles_cache_keys", + "INSERT INTO vts.roles_cache_keys (role) VALUES ('role_e')"); + + // update is not supported, however, this table has no regular columns, so it is not covered by tests + } + + private void cachePermissions(RoleResource roleResource) + { + Roles.getRoleDetails(roleResource); + } +} diff --git a/test/unit/org/apache/cassandra/service/ClientStateTest.java b/test/unit/org/apache/cassandra/service/ClientStateTest.java index 62f538ce56..014bc2df36 100644 --- a/test/unit/org/apache/cassandra/service/ClientStateTest.java +++ b/test/unit/org/apache/cassandra/service/ClientStateTest.java @@ -30,10 +30,8 @@ import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.auth.AuthTestUtils; import org.apache.cassandra.auth.AuthenticatedUser; -import org.apache.cassandra.auth.CassandraAuthorizer; import org.apache.cassandra.auth.DataResource; import org.apache.cassandra.auth.IResource; -import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.auth.Roles; import org.apache.cassandra.config.DatabaseDescriptor; @@ -64,7 +62,8 @@ public class ClientStateTest } @Test - public void permissionsCheckStartsAtHeadOfResourceChain() throws Exception { + 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 @@ -94,12 +93,10 @@ public class ClientStateTest public boolean canLogin() { return true; } }; - IRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); - roleManager.setup(); - Roles.initRolesCache(roleManager, () -> true); + Roles.cache.invalidate(); // finally, need to configure CassandraAuthorizer so we don't shortcircuit out of the authz process - DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer()); + 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 diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java index 2a4839da98..a6c5d28405 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java @@ -46,20 +46,20 @@ public class InvalidateCredentialsCacheTest extends CQLTester { SchemaLoader.prepareServer(); AuthTestUtils.LocalCassandraRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); - PasswordAuthenticator authenticator = new AuthTestUtils.LocalPasswordAuthenticator(); + PasswordAuthenticator passwordAuthenticator = new AuthTestUtils.LocalPasswordAuthenticator(); SchemaLoader.setupAuth(roleManager, - authenticator, - new AuthTestUtils.LocalCassandraAuthorizer(), - new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + passwordAuthenticator, + new AuthTestUtils.LocalCassandraAuthorizer(), + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); - roleANegotiator = authenticator.newSaslNegotiator(null); + roleANegotiator = passwordAuthenticator.newSaslNegotiator(null); roleANegotiator.evaluateResponse(new PlainTextAuthProvider(ROLE_A.getRoleName(), "ignored") .newAuthenticator((EndPoint) null, null) .initialResponse()); - roleBNegotiator = authenticator.newSaslNegotiator(null); + roleBNegotiator = passwordAuthenticator.newSaslNegotiator(null); roleBNegotiator.evaluateResponse(new PlainTextAuthProvider(ROLE_B.getRoleName(), "ignored") .newAuthenticator((EndPoint) null, null) .initialResponse()); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java index 3b1f488f81..484f5e78d1 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java @@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class InvalidateJmxPermissionsCacheTest extends CQLTester { - private static final AuthorizationProxy authorizationProxy = new NoAuthSetupAuthorizationProxy(); + private static final AuthorizationProxy authorizationProxy = new AuthTestUtils.NoAuthSetupAuthorizationProxy(); @BeforeClass public static void setup() throws Exception @@ -50,9 +50,9 @@ public class InvalidateJmxPermissionsCacheTest extends CQLTester AuthTestUtils.LocalCassandraRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); AuthTestUtils.LocalCassandraAuthorizer authorizer = new AuthTestUtils.LocalCassandraAuthorizer(); SchemaLoader.setupAuth(roleManager, - new AuthTestUtils.LocalPasswordAuthenticator(), - authorizer, - new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + new AuthTestUtils.LocalPasswordAuthenticator(), + authorizer, + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); JMXResource rootJmxResource = JMXResource.root(); Set jmxPermissions = rootJmxResource.applicablePermissions(); @@ -178,12 +178,4 @@ public class InvalidateJmxPermissionsCacheTest extends CQLTester return subject; } - private static class NoAuthSetupAuthorizationProxy extends AuthorizationProxy - { - public NoAuthSetupAuthorizationProxy() - { - super(); - this.isAuthSetupComplete = () -> true; - } - } } diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java index aa8fa7c285..a693eca6de 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java @@ -40,9 +40,9 @@ public class InvalidateNetworkPermissionsCacheTest extends CQLTester SchemaLoader.prepareServer(); AuthTestUtils.LocalCassandraRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); SchemaLoader.setupAuth(roleManager, - new AuthTestUtils.LocalPasswordAuthenticator(), - new AuthTestUtils.LocalCassandraAuthorizer(), - new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + new AuthTestUtils.LocalPasswordAuthenticator(), + new AuthTestUtils.LocalCassandraAuthorizer(), + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java index 994fcbb8a6..91d36abfb9 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java @@ -56,9 +56,9 @@ public class InvalidatePermissionsCacheTest extends CQLTester AuthTestUtils.LocalCassandraRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); AuthTestUtils.LocalCassandraAuthorizer authorizer = new AuthTestUtils.LocalCassandraAuthorizer(); SchemaLoader.setupAuth(roleManager, - new AuthTestUtils.LocalPasswordAuthenticator(), - authorizer, - new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + new AuthTestUtils.LocalPasswordAuthenticator(), + authorizer, + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions()); @@ -66,6 +66,7 @@ public class InvalidatePermissionsCacheTest extends CQLTester List resources = Arrays.asList( DataResource.root(), DataResource.keyspace(KEYSPACE), + DataResource.allTables(KEYSPACE), DataResource.table(KEYSPACE, "t1"), RoleResource.root(), RoleResource.role("role_x"), @@ -104,10 +105,10 @@ public class InvalidatePermissionsCacheTest extends CQLTester " [(-pwf | --password-file )]\n" + " [(-u | --username )] invalidatepermissionscache\n" + " [--all-functions] [--all-keyspaces] [--all-mbeans] [--all-roles]\n" + - " [--function ]\n" + + " [--all-tables] [--function ]\n" + " [--functions-in-keyspace ]\n" + " [--keyspace ] [--mbean ] [--role ]\n" + - " [--table ] [--] []\n" + + " [--table
] [--] []\n" + "\n" + "OPTIONS\n" + " --all-functions\n" + @@ -122,6 +123,9 @@ public class InvalidatePermissionsCacheTest extends CQLTester " --all-roles\n" + " Invalidate permissions for 'ALL ROLES'\n" + "\n" + + " --all-tables\n" + + " Invalidate permissions for 'ALL TABLES'\n" + + "\n" + " --function \n" + " Function to invalidate permissions for (you must specify\n" + " --functions-in-keyspace for using this option; function format:\n" + @@ -166,8 +170,9 @@ public class InvalidatePermissionsCacheTest extends CQLTester " list of argument, (useful when arguments might be mistaken for\n" + " command-line options\n" + "\n" + - " []\n" + - " A specific user for whom permissions need to be invalidated\n" + + " []\n" + + " A role for which permissions to specified resources need to be\n" + + " invalidated\n" + "\n" + "\n"; assertThat(tool.getStdout()).isEqualTo(help); @@ -179,28 +184,40 @@ public class InvalidatePermissionsCacheTest extends CQLTester ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "--all-keyspaces"); assertThat(tool.getExitCode()).isEqualTo(1); assertThat(tool.getStdout()) - .isEqualTo(wrapByDefaultNodetoolMessage("No options allowed without a being specified")); + .isEqualTo(wrapByDefaultNodetoolMessage("No resource options allowed without a being specified")); assertThat(tool.getStderr()).isEmpty(); - tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "user1", "--invalid-option"); + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1"); assertThat(tool.getExitCode()).isEqualTo(1); assertThat(tool.getStdout()) - .isEqualTo(wrapByDefaultNodetoolMessage("A single is only supported / you have a typo in the options spelling")); + .isEqualTo(wrapByDefaultNodetoolMessage("No resource options specified")); assertThat(tool.getStderr()).isEmpty(); - tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "user1", "--table", "t1"); + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1", "--invalid-option"); + assertThat(tool.getExitCode()).isEqualTo(1); + assertThat(tool.getStdout()) + .isEqualTo(wrapByDefaultNodetoolMessage("A single is only supported / you have a typo in the resource options spelling")); + assertThat(tool.getStderr()).isEmpty(); + + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1", "--all-tables"); + assertThat(tool.getExitCode()).isEqualTo(1); + assertThat(tool.getStdout()) + .isEqualTo(wrapByDefaultNodetoolMessage("--all-tables option should be passed along with --keyspace option")); + assertThat(tool.getStderr()).isEmpty(); + + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1", "--table", "t1"); assertThat(tool.getExitCode()).isEqualTo(1); assertThat(tool.getStdout()) .isEqualTo(wrapByDefaultNodetoolMessage("--table option should be passed along with --keyspace option")); assertThat(tool.getStderr()).isEmpty(); - tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "user1", "--function", "f[Int32Type]"); + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1", "--function", "f[Int32Type]"); assertThat(tool.getExitCode()).isEqualTo(1); assertThat(tool.getStdout()) .isEqualTo(wrapByDefaultNodetoolMessage("--function option should be passed along with --functions-in-keyspace option")); assertThat(tool.getStderr()).isEmpty(); - tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "user1", "--functions-in-keyspace", + tool = ToolRunner.invokeNodetool("invalidatepermissionscache", "role1", "--functions-in-keyspace", KEYSPACE, "--function", "f[x]"); assertThat(tool.getExitCode()).isEqualTo(1); assertThat(tool.getStdout()) @@ -213,6 +230,7 @@ public class InvalidatePermissionsCacheTest extends CQLTester { assertInvalidation(DataResource.root(), Collections.singletonList("--all-keyspaces")); assertInvalidation(DataResource.keyspace(KEYSPACE), Arrays.asList("--keyspace", KEYSPACE)); + assertInvalidation(DataResource.allTables(KEYSPACE), Arrays.asList("--keyspace", KEYSPACE, "--all-tables")); assertInvalidation(DataResource.table(KEYSPACE, "t1"), Arrays.asList("--keyspace", KEYSPACE, "--table", "t1")); assertInvalidation(RoleResource.root(), Collections.singletonList("--all-roles")); @@ -273,7 +291,7 @@ public class InvalidatePermissionsCacheTest extends CQLTester } @Test - public void testInvalidatePermissionsForAllUsers() + public void testInvalidatePermissionsForAllRoles() { DataResource rootDataResource = DataResource.root(); Set dataPermissions = rootDataResource.applicablePermissions(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java index c79af9af5b..266768db99 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java @@ -40,9 +40,9 @@ public class InvalidateRolesCacheTest extends CQLTester SchemaLoader.prepareServer(); AuthTestUtils.LocalCassandraRoleManager roleManager = new AuthTestUtils.LocalCassandraRoleManager(); SchemaLoader.setupAuth(roleManager, - new AuthTestUtils.LocalPasswordAuthenticator(), - new AuthTestUtils.LocalCassandraAuthorizer(), - new AuthTestUtils.LocalCassandraNetworkAuthorizer()); + new AuthTestUtils.LocalPasswordAuthenticator(), + new AuthTestUtils.LocalCassandraAuthorizer(), + new AuthTestUtils.LocalCassandraNetworkAuthorizer()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions()); roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions());