diff --git a/CHANGES.txt b/CHANGES.txt index c445434391..c981959ab2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,7 @@ Merged from 4.1: * Disk usage guardrail cannot be disabled when failure threshold is reached (CASSANDRA-21057) * ReadCommandController should close fast to avoid deadlock when building secondary index (CASSANDRA-19564) Merged from 4.0: + * Rate limit password changes (CASSANDRA-21202) * Node does not send multiple inflight echos (CASSANDRA-18866) * Obsolete expired SSTables before compaction starts (CASSANDRA-19776) * Switch lz4-java to at.yawk.lz4 version due to CVE (CASSANDRA-21052) diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 0e9c0b5640..fa87442ac3 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -27,14 +27,18 @@ import java.util.function.Supplier; 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; import org.apache.commons.lang3.StringUtils; +import org.mindrot.jbcrypt.BCrypt; 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.DatabaseDescriptor; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.cql3.*; @@ -49,7 +53,7 @@ import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.NoSpamLogger; -import org.mindrot.jbcrypt.BCrypt; +import org.apache.cassandra.utils.Clock; import static org.apache.cassandra.config.CassandraRelevantProperties.AUTH_BCRYPT_GENSALT_LOG2_ROUNDS; import static org.apache.cassandra.service.QueryState.forInternalCalls; @@ -118,6 +122,19 @@ public class CassandraRoleManager implements IRoleManager 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 = AUTH_BCRYPT_GENSALT_LOG2_ROUNDS.getInt(10); @@ -129,6 +146,7 @@ public class CassandraRoleManager implements IRoleManager private SelectStatement loadRoleStatement; private SelectStatement loadIdentityStatement; + private SelectStatement loadRoleWithWritetimeStatement; private final Set