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); }