mirror of https://github.com/apache/cassandra
Refactor auth caches and add credentials cache
Patch by Sam Tunnicliffe; reviewed by Mike Adamson for CASSANDRA-7715
This commit is contained in:
parent
7871c36e36
commit
186777aac5
|
|
@ -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)
|
||||
|
|
|
|||
24
NEWS.txt
24
NEWS.txt
|
|
@ -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
|
||||
===
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 #
|
||||
########################
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue