Repository: cassandra Updated Branches: refs/heads/trunk 7871c36e3 -> 186777aac
Refactor auth caches and add credentials cache Patch by Sam Tunnicliffe; reviewed by Mike Adamson for CASSANDRA-7715 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/186777aa Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/186777aa Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/186777aa Branch: refs/heads/trunk Commit: 186777aac55b1aa95c5c9e9cdaff2b981cf9d5af Parents: 7871c36 Author: Sam Tunnicliffe <s...@beobal.com> Authored: Wed Feb 17 13:41:18 2016 +0000 Committer: Sam Tunnicliffe <s...@beobal.com> Committed: Wed Feb 17 17:03:11 2016 +0000 ---------------------------------------------------------------------- CHANGES.txt | 1 + NEWS.txt | 24 +++ conf/cassandra.yaml | 33 ++- conf/jvm.options | 5 + .../org/apache/cassandra/auth/AuthCache.java | 207 +++++++++++++++++++ .../apache/cassandra/auth/AuthCacheMBean.java | 36 ++++ .../cassandra/auth/PasswordAuthenticator.java | 111 ++++++++-- .../apache/cassandra/auth/PermissionsCache.java | 125 ++--------- .../cassandra/auth/PermissionsCacheMBean.java | 15 +- .../org/apache/cassandra/auth/RolesCache.java | 124 ++--------- .../apache/cassandra/auth/RolesCacheMBean.java | 17 +- .../org/apache/cassandra/config/Config.java | 7 +- .../cassandra/config/DatabaseDescriptor.java | 77 ++++++- 13 files changed, 495 insertions(+), 287 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 978f20a..f3248df 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/NEWS.txt ---------------------------------------------------------------------- diff --git a/NEWS.txt b/NEWS.txt index 9dd4e25..68ccefb 100644 --- a/NEWS.txt +++ b/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 === http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/conf/cassandra.yaml ---------------------------------------------------------------------- diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index d6bcace..5663113 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -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 http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/conf/jvm.options ---------------------------------------------------------------------- diff --git a/conf/jvm.options b/conf/jvm.options index 4aec619..692d06b 100644 --- a/conf/jvm.options +++ b/conf/jvm.options @@ -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 # ######################## http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/AuthCache.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AuthCache.java b/src/java/org/apache/cassandra/auth/AuthCache.java new file mode 100644 index 0000000..3e33bbd --- /dev/null +++ b/src/java/org/apache/cassandra/auth/AuthCache.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/AuthCacheMBean.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AuthCacheMBean.java b/src/java/org/apache/cassandra/auth/AuthCacheMBean.java new file mode 100644 index 0000000..43fb88e --- /dev/null +++ b/src/java/org/apache/cassandra/auth/AuthCacheMBean.java @@ -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(); +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java index 3bee0e3..3714523 100644 --- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java @@ -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() { @@ -78,17 +85,60 @@ public class PasswordAuthenticator implements IAuthenticator { 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 + { // If the legacy users table exists try to verify credentials there. This is to handle the case // where the cluster is being upgraded and so is running with mixed versions of the authn tables 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 + { + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/PermissionsCache.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/PermissionsCache.java b/src/java/org/apache/cassandra/auth/PermissionsCache.java index 95aa398..875c473 100644 --- a/src/java/org/apache/cassandra/auth/PermissionsCache.java +++ b/src/java/org/apache/cassandra/auth/PermissionsCache.java @@ -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; - } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java b/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java index d07c98f..d370d06 100644 --- a/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java +++ b/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java @@ -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(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/RolesCache.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/RolesCache.java b/src/java/org/apache/cassandra/auth/RolesCache.java index 2694173..8b9c322 100644 --- a/src/java/org/apache/cassandra/auth/RolesCache.java +++ b/src/java/org/apache/cassandra/auth/RolesCache.java @@ -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; - } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/auth/RolesCacheMBean.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/RolesCacheMBean.java b/src/java/org/apache/cassandra/auth/RolesCacheMBean.java index cf270e6..06482d7 100644 --- a/src/java/org/apache/cassandra/auth/RolesCacheMBean.java +++ b/src/java/org/apache/cassandra/auth/RolesCacheMBean.java @@ -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(); -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/config/Config.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 9277df9..c783960 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -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; http://git-wip-us.apache.org/repos/asf/cassandra/blob/186777aa/src/java/org/apache/cassandra/config/DatabaseDescriptor.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 5c2a5c9..a3a9b4a 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -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,16 +767,26 @@ public class DatabaseDescriptor conf.permissions_validity_in_ms = timeout; } + public static int getPermissionsUpdateInterval() + { + return conf.permissions_update_interval_in_ms == -1 + ? conf.permissions_validity_in_ms + : 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 getPermissionsUpdateInterval() + public static int setPermissionsCacheMaxEntries(int maxEntries) { - return conf.permissions_update_interval_in_ms == -1 - ? conf.permissions_validity_in_ms - : conf.permissions_update_interval_in_ms; + return conf.permissions_cache_max_entries = maxEntries; } public static int getRolesValidity() @@ -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()