GUACAMOLE-990: Limit maximum number of tracked addresses.

This commit is contained in:
Michael Jumper
2022-08-22 10:40:52 -07:00
parent 2b19bc95da
commit 43f65357c8
11 changed files with 127 additions and 16 deletions

View File

@@ -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

View File

@@ -0,0 +1 @@
com.github.ben-manes.caffeine:caffeine:jar:2.9.3

View File

@@ -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.

View File

@@ -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)

View File

@@ -0,0 +1 @@
org.checkerframework:checker-qual:jar:3.19.0

View File

@@ -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

View File

@@ -0,0 +1 @@
com.google.errorprone:error_prone_annotations:jar:2.10.0

View File

@@ -53,6 +53,27 @@
<artifactId>guacamole-ext</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
<!-- Exclude transitive dependencies that will be overridden by
newer versions required by Caffeine -->
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Guava Base Libraries -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>

View File

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

View File

@@ -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<String, AuthenticationFailureStatus> failures =
new ConcurrentHashMap<>();
private final Cache<String, AuthenticationFailureStatus> 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);
}
}

View File

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