diff --git a/CHANGES.txt b/CHANGES.txt index 6c5b443133..71f0693b68 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0.20 + * Rate limit password changes (CASSANDRA-21202) * Node does not send multiple inflight echos (CASSANDRA-18866) * Obsolete expired SSTables before compaction starts (CASSANDRA-19776) * No need to evict already prepared statements, as it creates a race condition between multiple threads (CASSANDRA-17401) diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 7aed51ac04..327f04d200 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -25,6 +25,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -33,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.SchemaConstants; @@ -107,6 +110,19 @@ public class CassandraRoleManager implements IRoleManager public static final String GENSALT_LOG2_ROUNDS_PROPERTY = Config.PROPERTY_PREFIX + "auth_bcrypt_gensalt_log2_rounds"; private static final int GENSALT_LOG2_ROUNDS = getGensaltLogRounds(); + private static int PASSWORD_UPDATE_MIN_INTERVAL_MS = CassandraRelevantProperties.ROLE_PASSWORD_UPDATE_MIN_INTERVAL_MS.getInt(); + // in-memory protection against excessive loadRoleWithWritetimeStatement queries + private static Cache recentPasswordUpdates = Caffeine.newBuilder() + .expireAfterWrite(PASSWORD_UPDATE_MIN_INTERVAL_MS, TimeUnit.MILLISECONDS) + .build(); + + @VisibleForTesting + public static synchronized void updatePasswordUpdateMinInterval(int newInterval) + { + recentPasswordUpdates = Caffeine.newBuilder().expireAfterWrite(newInterval, TimeUnit.MILLISECONDS).build(); + PASSWORD_UPDATE_MIN_INTERVAL_MS = newInterval; + } + static int getGensaltLogRounds() { int rounds = Integer.getInteger(GENSALT_LOG2_ROUNDS_PROPERTY, 10); @@ -118,6 +134,7 @@ public class CassandraRoleManager implements IRoleManager } private SelectStatement loadRoleStatement; + private SelectStatement loadRoleWithWritetimeStatement; private final Set