mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-990: Add extension for automatically blocking brute-force auth attempts.
This commit is contained in:
0
extensions/guacamole-auth-ban/.ratignore
Normal file
0
extensions/guacamole-auth-ban/.ratignore
Normal file
60
extensions/guacamole-auth-ban/pom.xml
Normal file
60
extensions/guacamole-auth-ban/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-auth-ban</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<name>guacamole-auth-ban</name>
|
||||||
|
<url>http://guacamole.apache.org/</url>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>extensions</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<relativePath>../</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Java servlet API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>servlet-api</artifactId>
|
||||||
|
<version>2.5</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guacamole Extension API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-ext</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
54
extensions/guacamole-auth-ban/src/main/assembly/dist.xml
Normal file
54
extensions/guacamole-auth-ban/src/main/assembly/dist.xml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
-->
|
||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0
|
||||||
|
http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
|
||||||
|
<id>dist</id>
|
||||||
|
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
|
||||||
|
|
||||||
|
<!-- Output tar.gz -->
|
||||||
|
<formats>
|
||||||
|
<format>tar.gz</format>
|
||||||
|
</formats>
|
||||||
|
|
||||||
|
<!-- Include licenses and extension .jar -->
|
||||||
|
<fileSets>
|
||||||
|
|
||||||
|
<!-- Include licenses -->
|
||||||
|
<fileSet>
|
||||||
|
<outputDirectory></outputDirectory>
|
||||||
|
<directory>target/licenses</directory>
|
||||||
|
</fileSet>
|
||||||
|
|
||||||
|
<!-- Include extension .jar -->
|
||||||
|
<fileSet>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory></outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>*.jar</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
|
||||||
|
</fileSets>
|
||||||
|
|
||||||
|
</assembly>
|
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.auth.ban;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of an authentication failure, including the number of
|
||||||
|
* times the failure has occurred.
|
||||||
|
*/
|
||||||
|
public class AuthenticationFailureStatus {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the last authentication failure, as returned by
|
||||||
|
* System.nanoTime().
|
||||||
|
*/
|
||||||
|
private long lastFailure;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of failures that have occurred.
|
||||||
|
*/
|
||||||
|
private final AtomicInteger failureCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of failures that may occur before the user/address
|
||||||
|
* causing the failures is blocked.
|
||||||
|
*/
|
||||||
|
private final int maxAttempts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of time that a user/address must remain blocked after they
|
||||||
|
* have reached the maximum number of failures. Unlike the value provided
|
||||||
|
* at construction time, this value is maintained in nanoseconds.
|
||||||
|
*/
|
||||||
|
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()}.
|
||||||
|
*
|
||||||
|
* @param maxAttempts
|
||||||
|
* The maximum number of failures that may occur before the
|
||||||
|
* user/address causing the failures is blocked.
|
||||||
|
*
|
||||||
|
* @param duration
|
||||||
|
* The amount of time, in seconds, that a user/address must remain
|
||||||
|
* blocked after they have reached the maximum number of failures.
|
||||||
|
*/
|
||||||
|
public AuthenticationFailureStatus(int maxAttempts, int duration) {
|
||||||
|
this.lastFailure = System.nanoTime();
|
||||||
|
this.failureCount = new AtomicInteger(1);
|
||||||
|
this.maxAttempts = maxAttempts;
|
||||||
|
this.duration = TimeUnit.SECONDS.toNanos(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates this authentication failure, noting that the failure it
|
||||||
|
* represents has recurred.
|
||||||
|
*/
|
||||||
|
public void notifyFailed() {
|
||||||
|
lastFailure = System.nanoTime();
|
||||||
|
failureCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this authentication failure is recent enough that it
|
||||||
|
* should still be tracked. This function will return false for
|
||||||
|
* authentication failures that have not recurred for at least the duration
|
||||||
|
* provided at construction time.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* true if this authentication failure is recent enough that it should
|
||||||
|
* still be tracked, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return System.nanoTime() - lastFailure <= duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the user/address causing this authentication failure
|
||||||
|
* should be blocked based on the restrictions provided at construction
|
||||||
|
* time.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* true if the user/address causing this failure should be blocked,
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isBlocked() {
|
||||||
|
return isValid() && failureCount.get() >= maxAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of authentication failures that have been
|
||||||
|
* recorded through creating this object and invoking
|
||||||
|
* {@link #notifyFailed()}.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The total number of failures that have occurred.
|
||||||
|
*/
|
||||||
|
public int getFailures() {
|
||||||
|
return failureCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.auth.ban;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.GuacamoleClientTooManyException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides automated tracking and blocking of IP addresses that repeatedly
|
||||||
|
* fail authentication.
|
||||||
|
*/
|
||||||
|
public class AuthenticationFailureTracker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFailureTracker.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All authentication failures currently being tracked, stored by the
|
||||||
|
* associated IP address.
|
||||||
|
*/
|
||||||
|
private final ConcurrentMap<String, AuthenticationFailureStatus> failures =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of failed authentication attempts allowed before an
|
||||||
|
* address is temporarily banned.
|
||||||
|
*/
|
||||||
|
private final int maxAttempts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of time that each address should be banned after reaching the
|
||||||
|
* maximum number of failed authentication attempts, in seconds.
|
||||||
|
*/
|
||||||
|
private final int banDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AuthenticationFailureTracker that automatically blocks
|
||||||
|
* authentication attempts based on the provided blocking criteria.
|
||||||
|
*
|
||||||
|
* @param maxAttempts
|
||||||
|
* The maximum number of failed authentication attempts allowed before
|
||||||
|
* an address is temporarily banned.
|
||||||
|
*
|
||||||
|
* @param banDuration
|
||||||
|
* The length of time that each address should be banned after reaching
|
||||||
|
* the maximum number of failed authentication attempts, in seconds.
|
||||||
|
*/
|
||||||
|
public AuthenticationFailureTracker(int maxAttempts, int banDuration) {
|
||||||
|
this.maxAttempts = maxAttempts;
|
||||||
|
this.banDuration = banDuration;
|
||||||
|
|
||||||
|
// Inform administrator of configured behavior
|
||||||
|
if (maxAttempts <= 0) {
|
||||||
|
logger.info("Maximum failed authentication attempts has been set "
|
||||||
|
+ "to {}. Automatic banning of brute-force authentication "
|
||||||
|
+ "attempts will be disabled.", maxAttempts);
|
||||||
|
}
|
||||||
|
else if (banDuration <= 0) {
|
||||||
|
logger.info("Ban duration for addresses that repeatedly fail "
|
||||||
|
+ "authentication has been set to {}. Automatic banning "
|
||||||
|
+ "of brute-force authentication attempts will be "
|
||||||
|
+ "disabled.", banDuration);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info("Addresses will be automatically banned for {} "
|
||||||
|
+ "seconds after {} failed authentication attempts.",
|
||||||
|
banDuration, maxAttempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given Credentials do not contain any specific
|
||||||
|
* authentication parameters, including HTTP parameters. An authentication
|
||||||
|
* request that contains no parameters whatsoever will tend to be the
|
||||||
|
* first, anonymous, credential-less authentication attempt that results in
|
||||||
|
* the initial login screen rendering.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The Credentials object to test.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* true if the given Credentials contain no authentication parameters
|
||||||
|
* whatsoever, false otherwise.
|
||||||
|
*/
|
||||||
|
private boolean isEmpty(Credentials credentials) {
|
||||||
|
|
||||||
|
// An authentication request that contains an explicit username or
|
||||||
|
// password (even if blank) is non-empty, regardless of how the values
|
||||||
|
// were passed
|
||||||
|
if (credentials.getUsername() != null || credentials.getPassword() != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// All further tests depend on HTTP request details
|
||||||
|
HttpServletRequest request = credentials.getRequest();
|
||||||
|
if (request == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// An authentication request is non-empty if it contains any HTTP
|
||||||
|
// parameters at all or contains an authentication token
|
||||||
|
return !request.getParameterNames().hasMoreElements()
|
||||||
|
&& request.getHeader("Guacamole-Token") == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that the given address has just failed to authenticate and
|
||||||
|
* returns the AuthenticationFailureStatus that represents that failure. If
|
||||||
|
* the address isn't already being tracked, it will begin being tracked as
|
||||||
|
* of this call. If the address is already tracked, the returned
|
||||||
|
* AuthenticationFailureStatus will represent past authentication failures,
|
||||||
|
* as well.
|
||||||
|
*
|
||||||
|
* @param address
|
||||||
|
* The address that has failed to authenticate.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An AuthenticationFailureStatus that represents this latest
|
||||||
|
* authentication failure for the given address, as well as any past
|
||||||
|
* failures.
|
||||||
|
*/
|
||||||
|
private AuthenticationFailureStatus getAuthenticationFailure(String address) {
|
||||||
|
|
||||||
|
AuthenticationFailureStatus newFailure = new AuthenticationFailureStatus(maxAttempts, banDuration);
|
||||||
|
AuthenticationFailureStatus status = failures.putIfAbsent(address, newFailure);
|
||||||
|
if (status == null)
|
||||||
|
return newFailure;
|
||||||
|
|
||||||
|
status.notifyFailed();
|
||||||
|
return status;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that an authentication request has been received, as well as
|
||||||
|
* whether that request is known to have failed. If the associated address
|
||||||
|
* is currently being blocked, an exception will be thrown.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials associated with the authentication request.
|
||||||
|
*
|
||||||
|
* @param failed
|
||||||
|
* Whether the request is known to have failed. If the status of the
|
||||||
|
* request is not yet known, this should be false.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the authentication request is being blocked due to brute force
|
||||||
|
* prevention rules.
|
||||||
|
*/
|
||||||
|
private void notifyAuthenticationStatus(Credentials credentials,
|
||||||
|
boolean failed) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Do not track/ban if tracking or banning are disabled
|
||||||
|
if (maxAttempts <= 0 || banDuration <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ignore requests that do not contain explicit parameters of any kind
|
||||||
|
if (isEmpty(credentials))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Determine originating address of the authentication request
|
||||||
|
String address = credentials.getRemoteAddress();
|
||||||
|
if (address == null)
|
||||||
|
throw new GuacamoleServerException("Source address cannot be determined.");
|
||||||
|
|
||||||
|
// Get current failure status for the address associated with the
|
||||||
|
// authentication request, adding/updating that status if the request
|
||||||
|
// was itself a failure
|
||||||
|
AuthenticationFailureStatus status;
|
||||||
|
if (failed) {
|
||||||
|
status = getAuthenticationFailure(address);
|
||||||
|
logger.debug("Authentication has failed for address \"{}\" (current total failures: {}/{}).",
|
||||||
|
address, status.getFailures(), maxAttempts);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
status = failures.get(address);
|
||||||
|
|
||||||
|
if (status != null) {
|
||||||
|
|
||||||
|
// Explicitly block further processing of authentication/authorization
|
||||||
|
// if too many failures have occurred
|
||||||
|
if (status.isBlocked()) {
|
||||||
|
logger.debug("Blocking authentication attempt from address \"{}\" due to number of authentication failures.", address);
|
||||||
|
throw new GuacamoleClientTooManyException("Too many failed "
|
||||||
|
+ "authentication attempts. Please try again later.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up tracking of failures if the address is no longer
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that an authentication request has been received, but it is
|
||||||
|
* either not yet known whether the request has succeeded or failed. If the
|
||||||
|
* associated address is currently being blocked, an exception will be
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials associated with the authentication request.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the authentication request is being blocked due to brute force
|
||||||
|
* prevention rules.
|
||||||
|
*/
|
||||||
|
public void notifyAuthenticationRequestReceived(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
notifyAuthenticationStatus(credentials, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that an authentication request has been received and has
|
||||||
|
* succeeded. If the associated address is currently being blocked, an
|
||||||
|
* exception will be thrown.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials associated with the successful authentication
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the authentication request is being blocked due to brute force
|
||||||
|
* prevention rules.
|
||||||
|
*/
|
||||||
|
public void notifyAuthenticationSuccess(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
notifyAuthenticationStatus(credentials, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports that an authentication request has been received and has
|
||||||
|
* failed. If the associated address is currently being blocked, an
|
||||||
|
* exception will be thrown.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials associated with the failed authentication request.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the authentication request is being blocked due to brute force
|
||||||
|
* prevention rules.
|
||||||
|
*/
|
||||||
|
public void notifyAuthenticationFailed(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
notifyAuthenticationStatus(credentials, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.auth.ban;
|
||||||
|
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||||
|
import org.apache.guacamole.net.event.AuthenticationFailureEvent;
|
||||||
|
import org.apache.guacamole.net.event.AuthenticationSuccessEvent;
|
||||||
|
import org.apache.guacamole.net.event.listener.Listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener implementation which automatically tracks authentication failures
|
||||||
|
* such that further authentication attempts may be automatically blocked by
|
||||||
|
* {@link BanningAuthenticationProvider} if they match configured criteria.
|
||||||
|
*/
|
||||||
|
public class BanningAuthenticationListener implements Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared tracker of addresses that have repeatedly failed authentication.
|
||||||
|
*/
|
||||||
|
private static AuthenticationFailureTracker tracker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the shared tracker instance used by both the {@link BanningAuthenticationProvider}
|
||||||
|
* and this listener. This function MUST be invoked with the tracker
|
||||||
|
* created for BanningAuthenticationProvider as soon as possible (during
|
||||||
|
* construction of BanningAuthenticationProvider), or processing of
|
||||||
|
* received events will fail internally.
|
||||||
|
*
|
||||||
|
* @param tracker
|
||||||
|
* The tracker instance to use for received authentication events.
|
||||||
|
*/
|
||||||
|
public static void setAuthenticationFailureTracker(AuthenticationFailureTracker tracker) {
|
||||||
|
BanningAuthenticationListener.tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleEvent(Object event) throws GuacamoleException {
|
||||||
|
|
||||||
|
if (event instanceof AuthenticationFailureEvent) {
|
||||||
|
|
||||||
|
AuthenticationFailureEvent failure = (AuthenticationFailureEvent) event;
|
||||||
|
|
||||||
|
// Requests for additional credentials are not failures per se,
|
||||||
|
// but continuations of a multi-request authentication attempt that
|
||||||
|
// has not yet succeeded OR failed
|
||||||
|
if (failure.getFailure() instanceof GuacamoleInsufficientCredentialsException) {
|
||||||
|
tracker.notifyAuthenticationRequestReceived(failure.getCredentials());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider all other errors to be failed auth attempts
|
||||||
|
tracker.notifyAuthenticationFailed(failure.getCredentials());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (event instanceof AuthenticationSuccessEvent) {
|
||||||
|
AuthenticationSuccessEvent success = (AuthenticationSuccessEvent) event;
|
||||||
|
tracker.notifyAuthenticationSuccess(success.getCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.auth.ban;
|
||||||
|
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.environment.Environment;
|
||||||
|
import org.apache.guacamole.environment.LocalEnvironment;
|
||||||
|
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationProvider implementation that blocks further authentication
|
||||||
|
* attempts that are related to past authentication failures flagged by
|
||||||
|
* {@link BanningAuthenticationListener}.
|
||||||
|
*/
|
||||||
|
public class BanningAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of failed authentication attempts allowed before an
|
||||||
|
* address is temporarily banned.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty MAX_ATTEMPTS = new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "ban-max-invalid-attempts";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of time that each address should be banned after reaching the
|
||||||
|
* maximum number of failed authentication attempts, in seconds.
|
||||||
|
*/
|
||||||
|
private static final IntegerGuacamoleProperty IP_BAN_DURATION = new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "ban-address-duration";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default maximum number of failed authentication attempts allowed
|
||||||
|
* before an address is temporarily banned.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_MAX_ATTEMPTS = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default length of time that each address should be banned after
|
||||||
|
* reaching the maximum number of failed authentication attempts, in
|
||||||
|
* seconds.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_IP_BAN_DURATION = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared tracker of addresses that have repeatedly failed authentication.
|
||||||
|
*/
|
||||||
|
private final AuthenticationFailureTracker tracker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BanningAuthenticationProvider which automatically bans
|
||||||
|
* further authentication attempts from addresses that have repeatedly
|
||||||
|
* failed to authenticate. The ban duration and maximum number of failed
|
||||||
|
* attempts allowed before banning are configured within
|
||||||
|
* guacamole.properties.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs parsing the configuration properties used by this
|
||||||
|
* extension.
|
||||||
|
*/
|
||||||
|
public BanningAuthenticationProvider() throws GuacamoleException {
|
||||||
|
|
||||||
|
Environment environment = LocalEnvironment.getInstance();
|
||||||
|
int maxAttempts = environment.getProperty(MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
|
||||||
|
int banDuration = environment.getProperty(IP_BAN_DURATION, DEFAULT_IP_BAN_DURATION);
|
||||||
|
|
||||||
|
tracker = new AuthenticationFailureTracker(maxAttempts, banDuration);
|
||||||
|
BanningAuthenticationListener.setAuthenticationFailureTracker(tracker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "ban";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException {
|
||||||
|
tracker.notifyAuthenticationRequestReceived(credentials);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserContext getUserContext(AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||||
|
tracker.notifyAuthenticationRequestReceived(authenticatedUser.getCredentials());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"guacamoleVersion" : "1.4.0",
|
||||||
|
|
||||||
|
"name" : "Brute-force Authentication Detection/Prevention",
|
||||||
|
"namespace" : "ban",
|
||||||
|
|
||||||
|
"authProviders" : [
|
||||||
|
"org.apache.guacamole.auth.ban.BanningAuthenticationProvider"
|
||||||
|
],
|
||||||
|
|
||||||
|
"listeners" : [
|
||||||
|
"org.apache.guacamole.auth.ban.BanningAuthenticationListener"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
@@ -40,6 +40,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
|
|
||||||
<!-- Authentication extensions -->
|
<!-- Authentication extensions -->
|
||||||
|
<module>guacamole-auth-ban</module>
|
||||||
<module>guacamole-auth-duo</module>
|
<module>guacamole-auth-duo</module>
|
||||||
<module>guacamole-auth-header</module>
|
<module>guacamole-auth-header</module>
|
||||||
<module>guacamole-auth-jdbc</module>
|
<module>guacamole-auth-jdbc</module>
|
||||||
|
Reference in New Issue
Block a user