Refactor auth caches and add credentials cache

Patch by Sam Tunnicliffe; reviewed by Mike Adamson for CASSANDRA-7715
This commit is contained in:
Sam Tunnicliffe 2016-02-17 13:41:18 +00:00
parent 7871c36e36
commit 186777aac5
13 changed files with 496 additions and 288 deletions

View File

@ -1,4 +1,5 @@
3.4
* Add caching of encrypted credentials in PasswordAuthenticator (CASSANDRA-7715)
* fix SASI memtable switching on flush (CASSANDRA-11159)
* Remove duplicate offline compaction tracking (CASSANDRA-11148)
* fix EQ semantics of analyzed SASI indexes (CASSANDRA-11130)

View File

@ -13,6 +13,30 @@ restore snapshots created with the previous major version using the
'sstableloader' tool. You can upgrade the file format of your snapshots
using the provided 'sstableupgrade' tool.
3.4
===
New features
------------
- Internal authentication now supports caching of encrypted credentials.
Reference cassandra.yaml:credentials_validity_in_ms
- Remote configuration of auth caches via JMX can be disabled using the
the system property cassandra.disable_auth_caches_remote_configuration
Upgrading
---------
- Nothing specific to 3.4 but please see previous versions upgrading section,
especially if you are upgrading from 2.2.
Deprecation
-----------
- The mbean interfaces org.apache.cassandra.auth.PermissionsCacheMBean and
org.apache.cassandra.auth.RolesCacheMBean are deprecated in favor of
org.apache.cassandra.auth.AuthCacheMBean. This generalized interface is
common across all caches in the auth subsystem. The specific mbean interfaces
for each individual cache will be removed in a subsequent major version.
3.2
===

View File

@ -120,11 +120,11 @@ authorizer: AllowAllAuthorizer
# increase system_auth keyspace replication factor if you use this role manager.
role_manager: CassandraRoleManager
# Validity period for roles cache (fetching permissions can be an
# expensive operation depending on the authorizer). Granted roles are cached for
# authenticated sessions in AuthenticatedUser and after the period specified
# here, become eligible for (async) reload.
# Defaults to 2000, set to 0 to disable.
# Validity period for roles cache (fetching granted roles can be an expensive
# operation depending on the role manager, CassandraRoleManager is one example)
# Granted roles are cached for authenticated sessions in AuthenticatedUser and
# after the period specified here, become eligible for (async) reload.
# Defaults to 2000, set to 0 to disable caching entirely.
# Will be disabled automatically for AllowAllAuthenticator.
roles_validity_in_ms: 2000
@ -134,7 +134,7 @@ roles_validity_in_ms: 2000
# completes. If roles_validity_in_ms is non-zero, then this must be
# also.
# Defaults to the same value as roles_validity_in_ms.
# roles_update_interval_in_ms: 1000
# roles_update_interval_in_ms: 2000
# Validity period for permissions cache (fetching permissions can be an
# expensive operation depending on the authorizer, CassandraAuthorizer is
@ -148,7 +148,26 @@ permissions_validity_in_ms: 2000
# completes. If permissions_validity_in_ms is non-zero, then this must be
# also.
# Defaults to the same value as permissions_validity_in_ms.
# permissions_update_interval_in_ms: 1000
# permissions_update_interval_in_ms: 2000
# Validity period for credentials cache. This cache is tightly coupled to
# the provided PasswordAuthenticator implementation of IAuthenticator. If
# another IAuthenticator implementation is configured, this cache will not
# be automatically used and so the following settings will have no effect.
# Please note, credentials are cached in their encrypted form, so while
# activating this cache may reduce the number of queries made to the
# underlying table, it may not bring a significant reduction in the
# latency of individual authentication attempts.
# Defaults to 2000, set to 0 to disable credentials caching.
credentials_validity_in_ms: 2000
# Refresh interval for credentials cache (if enabled).
# After this interval, cache entries become eligible for refresh. Upon next
# access, an async reload is scheduled and the old value returned until it
# completes. If credentials_validity_in_ms is non-zero, then this must be
# also.
# Defaults to the same value as credentials_validity_in_ms.
# credentials_update_interval_in_ms: 2000
# The partitioner is responsible for distributing groups of rows (by
# partition key) across nodes in the cluster. You should leave this

View File

@ -75,6 +75,11 @@
# strategies and benchmark write performance differences without affecting the production workload.
#-Dcassandra.write_survey=true
# To disable configuration via JMX of auth caches (such as those for credentials, permissions and
# roles). This will mean those config options can only be set (persistently) in cassandra.yaml
# and will require a restart for new values to take effect.
#-Dcassandra.disable_auth_caches_remote_configuration=true
########################
# GENERAL JVM SETTINGS #
########################

View File

@ -0,0 +1,207 @@
/*
* 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.lang.management.ManagementFactory;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
public class AuthCache<K, V> implements AuthCacheMBean
{
private static final Logger logger = LoggerFactory.getLogger(AuthCache.class);
private static final String MBEAN_NAME_BASE = "org.apache.cassandra.auth:type=";
private volatile LoadingCache<K, V> cache;
private ThreadPoolExecutor cacheRefreshExecutor;
private final String name;
private final Consumer<Integer> setValidityDelegate;
private final Supplier<Integer> getValidityDelegate;
private final Consumer<Integer> setUpdateIntervalDelegate;
private final Supplier<Integer> getUpdateIntervalDelegate;
private final Consumer<Integer> setMaxEntriesDelegate;
private final Supplier<Integer> getMaxEntriesDelegate;
private final Function<K, V> loadFunction;
private final Supplier<Boolean> enableCache;
protected AuthCache(String name,
Consumer<Integer> setValidityDelegate,
Supplier<Integer> getValidityDelegate,
Consumer<Integer> setUpdateIntervalDelegate,
Supplier<Integer> getUpdateIntervalDelegate,
Consumer<Integer> setMaxEntriesDelegate,
Supplier<Integer> getMaxEntriesDelegate,
Function<K, V> loadFunction,
Supplier<Boolean> enableCache)
{
this.name = name;
this.setValidityDelegate = setValidityDelegate;
this.getValidityDelegate = getValidityDelegate;
this.setUpdateIntervalDelegate = setUpdateIntervalDelegate;
this.getUpdateIntervalDelegate = getUpdateIntervalDelegate;
this.setMaxEntriesDelegate = setMaxEntriesDelegate;
this.getMaxEntriesDelegate = getMaxEntriesDelegate;
this.loadFunction = loadFunction;
this.enableCache = enableCache;
init();
}
protected void init()
{
this.cacheRefreshExecutor = new DebuggableThreadPoolExecutor(name + "Refresh", Thread.NORM_PRIORITY);
this.cache = initCache(null);
try
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(this, getObjectName());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
protected ObjectName getObjectName() throws MalformedObjectNameException
{
return new ObjectName(MBEAN_NAME_BASE + name);
}
public V get(K k) throws ExecutionException
{
if (cache == null)
return loadFunction.apply(k);
return cache.get(k);
}
public void invalidate()
{
cache = initCache(null);
}
public void invalidate(K k)
{
if (cache != null)
cache.invalidate(k);
}
public void setValidity(int validityPeriod)
{
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
throw new UnsupportedOperationException("Remote configuration of auth caches is disabled");
setValidityDelegate.accept(validityPeriod);
cache = initCache(cache);
}
public int getValidity()
{
return getValidityDelegate.get();
}
public void setUpdateInterval(int updateInterval)
{
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
throw new UnsupportedOperationException("Remote configuration of auth caches is disabled");
setUpdateIntervalDelegate.accept(updateInterval);
cache = initCache(cache);
}
public int getUpdateInterval()
{
return getUpdateIntervalDelegate.get();
}
public void setMaxEntries(int maxEntries)
{
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
throw new UnsupportedOperationException("Remote configuration of auth caches is disabled");
setMaxEntriesDelegate.accept(maxEntries);
cache = initCache(cache);
}
public int getMaxEntries()
{
return getMaxEntriesDelegate.get();
}
private LoadingCache<K, V> initCache(LoadingCache<K, V> existing)
{
if (!enableCache.get())
return null;
if (getValidity() <= 0)
return null;
logger.info("(Re)initializing {} (validity period/update interval/max entries) ({}/{}/{})",
name, getValidity(), getUpdateInterval(), getMaxEntries());
LoadingCache<K, V> newcache = CacheBuilder.newBuilder()
.refreshAfterWrite(getUpdateInterval(), TimeUnit.MILLISECONDS)
.expireAfterWrite(getValidity(), TimeUnit.MILLISECONDS)
.maximumSize(getMaxEntries())
.build(new CacheLoader<K, V>()
{
public V load(K k) throws Exception
{
return loadFunction.apply(k);
}
public ListenableFuture<V> reload(final K k, final V oldV)
{
ListenableFutureTask<V> task = ListenableFutureTask.create(() -> {
try
{
return loadFunction.apply(k);
}
catch (Exception e)
{
logger.trace("Error performing async refresh of auth data in {}", name, e);
throw e;
}
});
cacheRefreshExecutor.execute(task);
return task;
}
});
if (existing != null)
newcache.putAll(existing.asMap());
return newcache;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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;
public interface AuthCacheMBean
{
public void invalidate();
public void setValidity(int validityPeriod);
public int getValidity();
public void setUpdateInterval(int updateInterval);
public int getUpdateInterval();
public void setMaxEntries(int maxEntries);
public int getMaxEntries();
}

View File

@ -22,18 +22,23 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.exceptions.AuthenticationException;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.transport.messages.ResultMessage;
@ -68,6 +73,8 @@ public class PasswordAuthenticator implements IAuthenticator
public static final String LEGACY_CREDENTIALS_TABLE = "credentials";
private SelectStatement legacyAuthenticateStatement;
private CredentialsCache cache;
// No anonymous access.
public boolean requireAuthentication()
{
@ -75,6 +82,33 @@ public class PasswordAuthenticator implements IAuthenticator
}
private AuthenticatedUser authenticate(String username, String password) throws AuthenticationException
{
try
{
String hash = cache.get(username);
if (!BCrypt.checkpw(password, hash))
throw new AuthenticationException("Username and/or password are incorrect");
return new AuthenticatedUser(username);
}
catch (ExecutionException | UncheckedExecutionException e)
{
// the credentials were somehow invalid - either a non-existent role, or one without a defined password
if (e.getCause() instanceof NoSuchCredentialsException)
throw new AuthenticationException("Username and/or password are incorrect");
// an unanticipated exception occured whilst querying the credentials table
if (e.getCause() instanceof RequestExecutionException)
{
logger.trace("Error performing internal authentication", e);
throw new AuthenticationException(e.getMessage());
}
throw new RuntimeException(e);
}
}
private String queryHashedPassword(String username) throws NoSuchCredentialsException
{
try
{
@ -83,12 +117,28 @@ public class PasswordAuthenticator implements IAuthenticator
SelectStatement authenticationStatement = Schema.instance.getCFMetaData(AuthKeyspace.NAME, LEGACY_CREDENTIALS_TABLE) == null
? authenticateStatement
: legacyAuthenticateStatement;
return doAuthenticate(username, password, authenticationStatement);
ResultMessage.Rows rows =
authenticationStatement.execute(QueryState.forInternalCalls(),
QueryOptions.forInternalCalls(consistencyForRole(username),
Lists.newArrayList(ByteBufferUtil.bytes(username))));
// If either a non-existent role name was supplied, or no credentials
// were found for that role we don't want to cache the result so we throw
// a specific, but unchecked, exception to keep LoadingCache happy.
if (rows.result.isEmpty())
throw new NoSuchCredentialsException();
UntypedResultSet result = UntypedResultSet.create(rows.result);
if (!result.one().has(SALTED_HASH))
throw new NoSuchCredentialsException();
return result.one().getString(SALTED_HASH);
}
catch (RequestExecutionException e)
{
logger.trace("Error performing internal authentication", e);
throw new AuthenticationException(e.toString());
throw e;
}
}
@ -118,6 +168,8 @@ public class PasswordAuthenticator implements IAuthenticator
LEGACY_CREDENTIALS_TABLE);
legacyAuthenticateStatement = prepare(query);
}
cache = new CredentialsCache(this);
}
public AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException
@ -138,21 +190,7 @@ public class PasswordAuthenticator implements IAuthenticator
return new PlainTextSaslAuthenticator();
}
private AuthenticatedUser doAuthenticate(String username, String password, SelectStatement authenticationStatement)
throws RequestExecutionException, AuthenticationException
{
ResultMessage.Rows rows = authenticationStatement.execute(QueryState.forInternalCalls(),
QueryOptions.forInternalCalls(consistencyForRole(username),
Lists.newArrayList(ByteBufferUtil.bytes(username))));
UntypedResultSet result = UntypedResultSet.create(rows.result);
if ((result.isEmpty() || !result.one().has(SALTED_HASH)) || !BCrypt.checkpw(password, result.one().getString(SALTED_HASH)))
throw new AuthenticationException("Username and/or password are incorrect");
return new AuthenticatedUser(username);
}
private SelectStatement prepare(String query)
private static SelectStatement prepare(String query)
{
return (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls()).statement;
}
@ -191,9 +229,8 @@ public class PasswordAuthenticator implements IAuthenticator
* a user being authorized to act on behalf of another with this IAuthenticator).
*
* @param bytes encoded credentials string sent by the client
* @return map containing the username/password pairs in the form an IAuthenticator
* would expect
* @throws javax.security.sasl.SaslException
* @throws org.apache.cassandra.exceptions.AuthenticationException if either the
* authnId or password is null
*/
private void decodeCredentials(byte[] bytes) throws AuthenticationException
{
@ -222,4 +259,36 @@ public class PasswordAuthenticator implements IAuthenticator
password = new String(pass, StandardCharsets.UTF_8);
}
}
private static class CredentialsCache extends AuthCache<String, String> implements CredentialsCacheMBean
{
private CredentialsCache(PasswordAuthenticator authenticator)
{
super("CredentialsCache",
DatabaseDescriptor::setCredentialsValidity,
DatabaseDescriptor::getCredentialsValidity,
DatabaseDescriptor::setCredentialsUpdateInterval,
DatabaseDescriptor::getCredentialsUpdateInterval,
DatabaseDescriptor::setCredentialsCacheMaxEntries,
DatabaseDescriptor::getCredentialsCacheMaxEntries,
authenticator::queryHashedPassword,
() -> true);
}
public void invalidateCredentials(String roleName)
{
invalidate(roleName);
}
}
public static interface CredentialsCacheMBean extends AuthCacheMBean
{
public void invalidateCredentials(String roleName);
}
// Just a marker so we can identify that invalid credentials were the
// cause of a loading exception from the cache
private static final class NoSuchCredentialsException extends RuntimeException
{
}
}

View File

@ -17,137 +17,36 @@
*/
package org.apache.cassandra.auth;
import java.lang.management.ManagementFactory;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.ExecutionException;
import org.apache.cassandra.config.DatabaseDescriptor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
import org.apache.cassandra.utils.Pair;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class PermissionsCache implements PermissionsCacheMBean
public class PermissionsCache extends AuthCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> implements PermissionsCacheMBean
{
private static final Logger logger = LoggerFactory.getLogger(PermissionsCache.class);
private final String MBEAN_NAME = "org.apache.cassandra.auth:type=PermissionsCache";
private final ThreadPoolExecutor cacheRefreshExecutor = new DebuggableThreadPoolExecutor("PermissionsCacheRefresh",
Thread.NORM_PRIORITY);
private final IAuthorizer authorizer;
private volatile LoadingCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> cache;
public PermissionsCache(IAuthorizer authorizer)
{
this.authorizer = authorizer;
this.cache = initCache(null);
try
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(this, new ObjectName(MBEAN_NAME));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
super("PermissionsCache",
DatabaseDescriptor::setPermissionsValidity,
DatabaseDescriptor::getPermissionsValidity,
DatabaseDescriptor::setPermissionsUpdateInterval,
DatabaseDescriptor::getPermissionsUpdateInterval,
DatabaseDescriptor::setPermissionsCacheMaxEntries,
DatabaseDescriptor::getPermissionsCacheMaxEntries,
(p) -> authorizer.authorize(p.left, p.right),
() -> DatabaseDescriptor.getAuthorizer().requireAuthorization());
}
public Set<Permission> getPermissions(AuthenticatedUser user, IResource resource)
{
if (cache == null)
return authorizer.authorize(user, resource);
try
{
return cache.get(Pair.create(user, resource));
return get(Pair.create(user, resource));
}
catch (ExecutionException e)
{
throw new RuntimeException(e);
}
}
public void invalidate()
{
cache = initCache(null);
}
public void setValidity(int validityPeriod)
{
DatabaseDescriptor.setPermissionsValidity(validityPeriod);
cache = initCache(cache);
}
public int getValidity()
{
return DatabaseDescriptor.getPermissionsValidity();
}
public void setUpdateInterval(int updateInterval)
{
DatabaseDescriptor.setPermissionsUpdateInterval(updateInterval);
cache = initCache(cache);
}
public int getUpdateInterval()
{
return DatabaseDescriptor.getPermissionsUpdateInterval();
}
private LoadingCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> initCache(
LoadingCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> existing)
{
if (!authorizer.requireAuthorization())
return null;
if (DatabaseDescriptor.getPermissionsValidity() <= 0)
return null;
LoadingCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> newcache = CacheBuilder.newBuilder()
.refreshAfterWrite(DatabaseDescriptor.getPermissionsUpdateInterval(), TimeUnit.MILLISECONDS)
.expireAfterWrite(DatabaseDescriptor.getPermissionsValidity(), TimeUnit.MILLISECONDS)
.maximumSize(DatabaseDescriptor.getPermissionsCacheMaxEntries())
.build(new CacheLoader<Pair<AuthenticatedUser, IResource>, Set<Permission>>()
{
public Set<Permission> load(Pair<AuthenticatedUser, IResource> userResource)
{
return authorizer.authorize(userResource.left, userResource.right);
}
public ListenableFuture<Set<Permission>> reload(final Pair<AuthenticatedUser, IResource> userResource,
final Set<Permission> oldValue)
{
ListenableFutureTask<Set<Permission>> task = ListenableFutureTask.create(new Callable<Set<Permission>>()
{
public Set<Permission>call() throws Exception
{
try
{
return authorizer.authorize(userResource.left, userResource.right);
}
catch (Exception e)
{
logger.trace("Error performing async refresh of user permissions", e);
throw e;
}
}
});
cacheRefreshExecutor.execute(task);
return task;
}
});
if (existing != null)
newcache.putAll(existing.asMap());
return newcache;
}
}

View File

@ -17,15 +17,10 @@
*/
package org.apache.cassandra.auth;
public interface PermissionsCacheMBean
/**
* Retained since CASSANDRA-7715 for backwards compatibility of MBean interface
* classes. This should be removed in the next major version (4.0)
*/
public interface PermissionsCacheMBean extends AuthCacheMBean
{
public void invalidate();
public void setValidity(int validityPeriod);
public int getValidity();
public void setUpdateInterval(int updateInterval);
public int getUpdateInterval();
}

View File

@ -17,135 +17,35 @@
*/
package org.apache.cassandra.auth;
import java.lang.management.ManagementFactory;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
import org.apache.cassandra.config.DatabaseDescriptor;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class RolesCache implements RolesCacheMBean
public class RolesCache extends AuthCache<RoleResource, Set<RoleResource>> implements RolesCacheMBean
{
private static final Logger logger = LoggerFactory.getLogger(RolesCache.class);
private final String MBEAN_NAME = "org.apache.cassandra.auth:type=RolesCache";
private final ThreadPoolExecutor cacheRefreshExecutor = new DebuggableThreadPoolExecutor("RolesCacheRefresh",
Thread.NORM_PRIORITY);
private final IRoleManager roleManager;
private volatile LoadingCache<RoleResource, Set<RoleResource>> cache;
public RolesCache(IRoleManager roleManager)
{
this.roleManager = roleManager;
this.cache = initCache(null);
try
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(this, new ObjectName(MBEAN_NAME));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
super("RolesCache",
DatabaseDescriptor::setRolesValidity,
DatabaseDescriptor::getRolesValidity,
DatabaseDescriptor::setRolesUpdateInterval,
DatabaseDescriptor::getRolesUpdateInterval,
DatabaseDescriptor::setRolesCacheMaxEntries,
DatabaseDescriptor::getRolesCacheMaxEntries,
(r) -> roleManager.getRoles(r, true),
() -> DatabaseDescriptor.getAuthenticator().requireAuthentication());
}
public Set<RoleResource> getRoles(RoleResource role)
{
if (cache == null)
return roleManager.getRoles(role, true);
try
{
return cache.get(role);
return get(role);
}
catch (ExecutionException e)
{
throw new RuntimeException(e);
}
}
public void invalidate()
{
cache = initCache(null);
}
public void setValidity(int validityPeriod)
{
DatabaseDescriptor.setRolesValidity(validityPeriod);
cache = initCache(cache);
}
public int getValidity()
{
return DatabaseDescriptor.getRolesValidity();
}
public void setUpdateInterval(int updateInterval)
{
DatabaseDescriptor.setRolesUpdateInterval(updateInterval);
cache = initCache(cache);
}
public int getUpdateInterval()
{
return DatabaseDescriptor.getRolesUpdateInterval();
}
private LoadingCache<RoleResource, Set<RoleResource>> initCache(LoadingCache<RoleResource, Set<RoleResource>> existing)
{
if (!DatabaseDescriptor.getAuthenticator().requireAuthentication())
return null;
if (DatabaseDescriptor.getRolesValidity() <= 0)
return null;
LoadingCache<RoleResource, Set<RoleResource>> newcache = CacheBuilder.newBuilder()
.refreshAfterWrite(DatabaseDescriptor.getRolesUpdateInterval(), TimeUnit.MILLISECONDS)
.expireAfterWrite(DatabaseDescriptor.getRolesValidity(), TimeUnit.MILLISECONDS)
.maximumSize(DatabaseDescriptor.getRolesCacheMaxEntries())
.build(new CacheLoader<RoleResource, Set<RoleResource>>()
{
public Set<RoleResource> load(RoleResource primaryRole)
{
return roleManager.getRoles(primaryRole, true);
}
public ListenableFuture<Set<RoleResource>> reload(final RoleResource primaryRole,
final Set<RoleResource> oldValue)
{
ListenableFutureTask<Set<RoleResource>> task;
task = ListenableFutureTask.create(new Callable<Set<RoleResource>>()
{
public Set<RoleResource> call() throws Exception
{
try
{
return roleManager.getRoles(primaryRole, true);
} catch (Exception e)
{
logger.trace("Error performing async refresh of user roles", e);
throw e;
}
}
});
cacheRefreshExecutor.execute(task);
return task;
}
});
if (existing != null)
newcache.putAll(existing.asMap());
return newcache;
}
}

View File

@ -17,15 +17,10 @@
*/
package org.apache.cassandra.auth;
public interface RolesCacheMBean
/**
* Retained since CASSANDRA-7715 for backwards compatibility of MBean interface
* classes. This should be removed in the next major version (4.0)
*/
public interface RolesCacheMBean extends AuthCacheMBean
{
public void invalidate();
public void setValidity(int validityPeriod);
public int getValidity();
public void setUpdateInterval(int updateInterval);
public int getUpdateInterval();
}
}

View File

@ -43,11 +43,14 @@ public class Config
public String authorizer;
public String role_manager;
public volatile int permissions_validity_in_ms = 2000;
public int permissions_cache_max_entries = 1000;
public volatile int permissions_cache_max_entries = 1000;
public volatile int permissions_update_interval_in_ms = -1;
public volatile int roles_validity_in_ms = 2000;
public int roles_cache_max_entries = 1000;
public volatile int roles_cache_max_entries = 1000;
public volatile int roles_update_interval_in_ms = -1;
public volatile int credentials_validity_in_ms = 2000;
public volatile int credentials_cache_max_entries = 1000;
public volatile int credentials_update_interval_in_ms = -1;
/* Hashing strategy Random or OPHF */
public String partitioner;

View File

@ -321,6 +321,19 @@ public class DatabaseDescriptor
if (conf.authenticator != null)
authenticator = FBUtilities.newAuthenticator(conf.authenticator);
// the configuration options regarding credentials caching are only guaranteed to
// work with PasswordAuthenticator, so log a message if some other authenticator
// is in use and non-default values are detected
if (!(authenticator instanceof PasswordAuthenticator)
&& (conf.credentials_update_interval_in_ms != -1
|| conf.credentials_validity_in_ms != 2000
|| conf.credentials_cache_max_entries != 1000))
{
logger.info("Configuration options credentials_update_interval_in_ms, credentials_validity_in_ms and " +
"credentials_cache_max_entries may not be applicable for the configured authenticator ({})",
authenticator.getClass().getName());
}
if (conf.authorizer != null)
authorizer = FBUtilities.newAuthorizer(conf.authorizer);
@ -754,11 +767,6 @@ public class DatabaseDescriptor
conf.permissions_validity_in_ms = timeout;
}
public static int getPermissionsCacheMaxEntries()
{
return conf.permissions_cache_max_entries;
}
public static int getPermissionsUpdateInterval()
{
return conf.permissions_update_interval_in_ms == -1
@ -766,6 +774,21 @@ public class DatabaseDescriptor
: conf.permissions_update_interval_in_ms;
}
public static void setPermissionsUpdateInterval(int updateInterval)
{
conf.permissions_update_interval_in_ms = updateInterval;
}
public static int getPermissionsCacheMaxEntries()
{
return conf.permissions_cache_max_entries;
}
public static int setPermissionsCacheMaxEntries(int maxEntries)
{
return conf.permissions_cache_max_entries = maxEntries;
}
public static int getRolesValidity()
{
return conf.roles_validity_in_ms;
@ -776,11 +799,6 @@ public class DatabaseDescriptor
conf.roles_validity_in_ms = validity;
}
public static int getRolesCacheMaxEntries()
{
return conf.roles_cache_max_entries;
}
public static int getRolesUpdateInterval()
{
return conf.roles_update_interval_in_ms == -1
@ -793,9 +811,46 @@ public class DatabaseDescriptor
conf.roles_update_interval_in_ms = interval;
}
public static void setPermissionsUpdateInterval(int updateInterval)
public static int getRolesCacheMaxEntries()
{
conf.permissions_update_interval_in_ms = updateInterval;
return conf.roles_cache_max_entries;
}
public static int setRolesCacheMaxEntries(int maxEntries)
{
return conf.roles_cache_max_entries = maxEntries;
}
public static int getCredentialsValidity()
{
return conf.credentials_validity_in_ms;
}
public static void setCredentialsValidity(int timeout)
{
conf.credentials_validity_in_ms = timeout;
}
public static int getCredentialsUpdateInterval()
{
return conf.credentials_update_interval_in_ms == -1
? conf.credentials_validity_in_ms
: conf.credentials_update_interval_in_ms;
}
public static void setCredentialsUpdateInterval(int updateInterval)
{
conf.credentials_update_interval_in_ms = updateInterval;
}
public static int getCredentialsCacheMaxEntries()
{
return conf.credentials_cache_max_entries;
}
public static int setCredentialsCacheMaxEntries(int maxEntries)
{
return conf.credentials_cache_max_entries = maxEntries;
}
public static int getThriftFramedTransportSize()