diff --git a/doc/licenses/caffeine-2.9.3/README b/doc/licenses/caffeine-2.9.3/README
new file mode 100644
index 000000000..c51ba745c
--- /dev/null
+++ b/doc/licenses/caffeine-2.9.3/README
@@ -0,0 +1,8 @@
+Caffeine (https://github.com/ben-manes/caffeine)
+------------------------------------------------
+
+ Version: 2.9.3
+ From: 'Ben Manes' (https://github.com/ben-manes)
+ License(s):
+ Apache v2.0
+
diff --git a/doc/licenses/caffeine-2.9.3/dep-coordinates.txt b/doc/licenses/caffeine-2.9.3/dep-coordinates.txt
new file mode 100644
index 000000000..feda8aa63
--- /dev/null
+++ b/doc/licenses/caffeine-2.9.3/dep-coordinates.txt
@@ -0,0 +1 @@
+com.github.ben-manes.caffeine:caffeine:jar:2.9.3
diff --git a/doc/licenses/checker-qual-3.19.0/LICENSE.txt b/doc/licenses/checker-qual-3.19.0/LICENSE.txt
new file mode 100644
index 000000000..9837c6b69
--- /dev/null
+++ b/doc/licenses/checker-qual-3.19.0/LICENSE.txt
@@ -0,0 +1,22 @@
+Checker Framework qualifiers
+Copyright 2004-present by the Checker Framework developers
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/doc/licenses/checker-qual-3.19.0/README b/doc/licenses/checker-qual-3.19.0/README
new file mode 100644
index 000000000..4a00db7a7
--- /dev/null
+++ b/doc/licenses/checker-qual-3.19.0/README
@@ -0,0 +1,8 @@
+Checker Framework qualifiers (https://checkerframework.org/)
+------------------------------------------------------------
+
+ Version: 3.19.0
+ From: 'Checker Framework developers' (https://checkerframework.org/)
+ License(s):
+ MIT (bundled/checker-qual-3.19.0/LICENSE.txt)
+
diff --git a/doc/licenses/checker-qual-3.19.0/dep-coordinates.txt b/doc/licenses/checker-qual-3.19.0/dep-coordinates.txt
new file mode 100644
index 000000000..322f3959d
--- /dev/null
+++ b/doc/licenses/checker-qual-3.19.0/dep-coordinates.txt
@@ -0,0 +1 @@
+org.checkerframework:checker-qual:jar:3.19.0
diff --git a/doc/licenses/error-prone-2.10.0/README b/doc/licenses/error-prone-2.10.0/README
new file mode 100644
index 000000000..f286aed3b
--- /dev/null
+++ b/doc/licenses/error-prone-2.10.0/README
@@ -0,0 +1,8 @@
+Error Prone (https://errorprone.info/)
+--------------------------------------
+
+ Version: 2.10.0
+ From: 'Google Inc.' (http://www.google.com/)
+ License(s):
+ Apache v2.0
+
diff --git a/doc/licenses/error-prone-2.10.0/dep-coordinates.txt b/doc/licenses/error-prone-2.10.0/dep-coordinates.txt
new file mode 100644
index 000000000..9473dfcab
--- /dev/null
+++ b/doc/licenses/error-prone-2.10.0/dep-coordinates.txt
@@ -0,0 +1 @@
+com.google.errorprone:error_prone_annotations:jar:2.10.0
diff --git a/extensions/guacamole-auth-ban/pom.xml b/extensions/guacamole-auth-ban/pom.xml
index 2be082fc2..68b8d6f90 100644
--- a/extensions/guacamole-auth-ban/pom.xml
+++ b/extensions/guacamole-auth-ban/pom.xml
@@ -53,6 +53,27 @@
guacamole-ext
1.4.0
provided
+
+
+
+
+ org.checkerframework
+ checker-qual
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+
+
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 2.9.3
diff --git a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureStatus.java b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureStatus.java
index 87bdc5515..ac4f1efc1 100644
--- a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureStatus.java
+++ b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureStatus.java
@@ -53,9 +53,9 @@ public class AuthenticationFailureStatus {
private final long duration;
/**
- * Creates an AuthenticationFailureStatus that represents a single failure
- * and is subject to the given restrictions. Additional failures may be
- * flagged after creation with {@link #notifyFailed()}.
+ * Creates an AuthenticationFailureStatus that is initialized to zero
+ * failures and is subject to the given restrictions. Additional failures
+ * may be flagged after creation with {@link #notifyFailed()}.
*
* @param maxAttempts
* The maximum number of failures that may occur before the
@@ -67,7 +67,7 @@ public class AuthenticationFailureStatus {
*/
public AuthenticationFailureStatus(int maxAttempts, int duration) {
this.lastFailure = System.nanoTime();
- this.failureCount = new AtomicInteger(1);
+ this.failureCount = new AtomicInteger(0);
this.maxAttempts = maxAttempts;
this.duration = TimeUnit.SECONDS.toNanos(duration);
}
diff --git a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureTracker.java b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureTracker.java
index d30f522bd..85f5cc09c 100644
--- a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureTracker.java
+++ b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/AuthenticationFailureTracker.java
@@ -19,8 +19,8 @@
package org.apache.guacamole.auth.ban;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
@@ -44,8 +44,7 @@ public class AuthenticationFailureTracker {
* All authentication failures currently being tracked, stored by the
* associated IP address.
*/
- private final ConcurrentMap failures =
- new ConcurrentHashMap<>();
+ private final Cache failures;
/**
* The maximum number of failed authentication attempts allowed before an
@@ -70,8 +69,14 @@ public class AuthenticationFailureTracker {
* @param banDuration
* The length of time that each address should be banned after reaching
* the maximum number of failed authentication attempts, in seconds.
+ *
+ * @param maxAddresses
+ * The maximum number of unique IP addresses that should be tracked
+ * before discarding older tracked failures.
*/
- public AuthenticationFailureTracker(int maxAttempts, int banDuration) {
+ public AuthenticationFailureTracker(int maxAttempts, int banDuration,
+ long maxAddresses) {
+
this.maxAttempts = maxAttempts;
this.banDuration = banDuration;
@@ -93,6 +98,14 @@ public class AuthenticationFailureTracker {
banDuration, maxAttempts);
}
+ // Limit maximum number of tracked addresses to configured upper bound
+ this.failures = Caffeine.newBuilder()
+ .maximumSize(maxAddresses)
+ .build();
+
+ logger.info("Up to {} unique addresses will be tracked/banned at any "
+ + " given time.", maxAddresses);
+
}
/**
@@ -147,10 +160,8 @@ public class AuthenticationFailureTracker {
*/
private AuthenticationFailureStatus getAuthenticationFailure(String address) {
- AuthenticationFailureStatus newFailure = new AuthenticationFailureStatus(maxAttempts, banDuration);
- AuthenticationFailureStatus status = failures.putIfAbsent(address, newFailure);
- if (status == null)
- return newFailure;
+ AuthenticationFailureStatus status = failures.get(address,
+ (addr) -> new AuthenticationFailureStatus(maxAttempts, banDuration));
status.notifyFailed();
return status;
@@ -199,7 +210,7 @@ public class AuthenticationFailureTracker {
address, status.getFailures(), maxAttempts);
}
else
- status = failures.get(address);
+ status = failures.getIfPresent(address);
if (status != null) {
@@ -216,7 +227,7 @@ public class AuthenticationFailureTracker {
// relevant (all failures are sufficiently old)
else if (!status.isValid()) {
logger.debug("Removing address \"{}\" from tracking as there are no recent authentication failures.", address);
- failures.remove(address);
+ failures.invalidate(address);
}
}
diff --git a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/BanningAuthenticationProvider.java b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/BanningAuthenticationProvider.java
index 12195f5e4..adf54ff0c 100644
--- a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/BanningAuthenticationProvider.java
+++ b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/BanningAuthenticationProvider.java
@@ -20,6 +20,7 @@
package org.apache.guacamole.auth.ban;
import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
@@ -27,6 +28,7 @@ import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
+import org.apache.guacamole.properties.LongGuacamoleProperty;
/**
* AuthenticationProvider implementation that blocks further authentication
@@ -61,6 +63,20 @@ public class BanningAuthenticationProvider extends AbstractAuthenticationProvide
};
+ /**
+ * The maximum number of failed authentication attempts tracked at any
+ * given time. Once this number of addresses is exceeded, the oldest
+ * authentication attempts are rotated off on an LRU basis.
+ */
+ private static final LongGuacamoleProperty MAX_ADDRESSES = new LongGuacamoleProperty() {
+
+ @Override
+ public String getName() {
+ return "ban-max-addresses";
+ }
+
+ };
+
/**
* The default maximum number of failed authentication attempts allowed
* before an address is temporarily banned.
@@ -74,6 +90,13 @@ public class BanningAuthenticationProvider extends AbstractAuthenticationProvide
*/
private static final int DEFAULT_IP_BAN_DURATION = 300;
+ /**
+ * The maximum number of failed authentication attempts tracked at any
+ * given time. Once this number of addresses is exceeded, the oldest
+ * authentication attempts are rotated off on an LRU basis.
+ */
+ private static final long DEFAULT_MAX_ADDRESSES = 10485760;
+
/**
* Shared tracker of addresses that have repeatedly failed authentication.
*/
@@ -95,8 +118,15 @@ public class BanningAuthenticationProvider extends AbstractAuthenticationProvide
Environment environment = LocalEnvironment.getInstance();
int maxAttempts = environment.getProperty(MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
int banDuration = environment.getProperty(IP_BAN_DURATION, DEFAULT_IP_BAN_DURATION);
+ long maxAddresses = environment.getProperty(MAX_ADDRESSES, DEFAULT_MAX_ADDRESSES);
- tracker = new AuthenticationFailureTracker(maxAttempts, banDuration);
+ if (maxAddresses <= 0)
+ throw new GuacamoleServerException("The maximum number of "
+ + "addresses tracked, as specified by the "
+ + "\"" + MAX_ADDRESSES.getName() + "\" property, must be "
+ + "greater than zero.");
+
+ tracker = new AuthenticationFailureTracker(maxAttempts, banDuration, maxAddresses);
BanningAuthenticationListener.setAuthenticationFailureTracker(tracker);
}