blob: c1ba8a569fddacb4faea685356120da608caeace [file] [log] [blame]
/*
* 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.auth.ban.status.AuthenticationFailureTracker;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.ban.status.InMemoryAuthenticationFailureTracker;
import org.apache.guacamole.auth.ban.status.NullAuthenticationFailureTracker;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
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;
import org.apache.guacamole.net.event.AuthenticationRequestReceivedEvent;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.LongGuacamoleProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener implementation which automatically tracks authentication failures
* such that further authentication attempts may be automatically blocked if
* they match configured criteria.
*/
public class BanningAuthenticationListener implements Listener {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(BanningAuthenticationListener.class);
/**
* 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 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.
*/
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;
/**
* 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;
/**
* Tracker of addresses that have repeatedly failed authentication.
*/
private final AuthenticationFailureTracker tracker;
/**
* Creates a new BanningAuthenticationListener 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 BanningAuthenticationListener() 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);
long maxAddresses = environment.getProperty(MAX_ADDRESSES, DEFAULT_MAX_ADDRESSES);
// Configure auth failure tracking behavior and inform administrator of
// ultimate result
if (maxAttempts <= 0) {
this.tracker = new NullAuthenticationFailureTracker();
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) {
this.tracker = new NullAuthenticationFailureTracker();
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 if (maxAddresses <= 0) {
this.tracker = new NullAuthenticationFailureTracker();
logger.info("Maximum number of tracked addresses has been set to "
+ "{}. Automatic banning of brute-force authentication "
+ "attempts will be disabled.", maxAddresses);
}
else {
this.tracker = new InMemoryAuthenticationFailureTracker(maxAttempts, banDuration, maxAddresses);
logger.info("Addresses will be automatically banned for {} "
+ "seconds after {} failed authentication attempts. Up "
+ "to {} unique addresses will be tracked/banned at any "
+ "given time.", banDuration, maxAttempts, maxAddresses);
}
}
@Override
public void handleEvent(Object event) throws GuacamoleException {
// Notify auth tracker of each request received BEFORE the request is
// processed ...
if (event instanceof AuthenticationRequestReceivedEvent) {
AuthenticationRequestReceivedEvent request = (AuthenticationRequestReceivedEvent) event;
tracker.notifyAuthenticationRequestReceived(request.getCredentials());
}
// ... as well as every explicit failure ...
else 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());
}
// ... and explicit success.
else if (event instanceof AuthenticationSuccessEvent) {
AuthenticationSuccessEvent success = (AuthenticationSuccessEvent) event;
tracker.notifyAuthenticationSuccess(success.getCredentials());
}
}
}