Skip to content

Commit

Permalink
GUACAMOLE-1855: Implement bypass and enforcement options in the TOTP …
Browse files Browse the repository at this point in the history
…module.
  • Loading branch information
necouchman committed Oct 1, 2023
1 parent ec2e067 commit 850533c
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 4 deletions.
8 changes: 8 additions & 0 deletions extensions/guacamole-auth-totp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@
<version>2.0</version>
<scope>provided</scope>
</dependency>

<!-- Library for unified IPv4/6 parsing and validation -->
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.4.0</version>
<scope>provided</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
package org.apache.guacamole.auth.totp.conf;

import com.google.inject.Inject;
import inet.ipaddr.IPAddress;
import java.util.Collections;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.EnumGuacamoleProperty;
import org.apache.guacamole.properties.IPAddressListProperty;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.totp.TOTPGenerator;
Expand Down Expand Up @@ -88,6 +92,36 @@ public class ConfigurationService {
public String getName() { return "totp-mode"; }

};

/**
* A property that contains a list of IP addresses and/or subnets for which
* MFA via the TOTP module should be bypassed. Users logging in from addresses
* contained in this list will not be prompted for a second authentication
* factor. If this property is empty or not defined, and the TOTP module
* is installed, all users will be prompted for MFA.
*/
private static final IPAddressListProperty TOTP_BYPASS_HOSTS =
new IPAddressListProperty() {

@Override
public String getName() { return "totp-bypass-hosts"; }

};

/**
* A property that contains a list of IP addresses and/or subnets for which
* MFA via the TOTP module should explicitly be enabled. If this property is defined,
* and the TOTP module is installed, users logging in from hosts contained
* in this list will be prompted for MFA, and users logging in from all
* other hosts will not be prompted for MFA.
*/
private static final IPAddressListProperty TOTP_ENFORCE_HOSTS =
new IPAddressListProperty() {

@Override
public String getName() { return "totp-enforce-hosts"; }

};

/**
* Returns the human-readable name of the entity issuing user accounts. If
Expand Down Expand Up @@ -158,5 +192,39 @@ public int getPeriod() throws GuacamoleException {
public TOTPGenerator.Mode getMode() throws GuacamoleException {
return environment.getProperty(TOTP_MODE, TOTPGenerator.Mode.SHA1);
}

/**
* Return the list of IP addresses and/or subnets for which MFA authentication via the
* TOTP module should be bypassed, allowing users from those addresses to log in
* without the MFA requirement.
*
* @return
* A list of IP addresses and/or subnets for which MFA authentication
* should be bypassed.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getBypassHosts() throws GuacamoleException {
return environment.getProperty(TOTP_BYPASS_HOSTS, Collections.emptyList());
}

/**
* Return the list of IP addresses and/or subnets for which MFA authentication via the TOTP
* module should be explicitly enabled, requiring users logging in from hosts specified in
* the list to complete MFA.
*
* @return
* A list of IP addresses and/or subnets for which MFA authentication
* should be explicitly enabled.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getEnforceHosts() throws GuacamoleException {
return environment.getProperty(TOTP_ENFORCE_HOSTS, Collections.emptyList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
import com.google.common.io.BaseEncoding;
import com.google.inject.Inject;
import com.google.inject.Provider;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import java.security.InvalidKeyException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -288,6 +291,65 @@ private boolean totpDisabled(UserContext context,
public void verifyIdentity(UserContext context,
AuthenticatedUser authenticatedUser) throws GuacamoleException {

// Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials();
HttpServletRequest request = credentials.getRequest();

// Get the current client address
IPAddressString clientAddr = new IPAddressString(request.getRemoteAddr());

// Ignore anonymous users
if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
return;

// We enforce by default
boolean enforceHost = true;

// Check for a list of addresses that should be bypassed and iterate
List<IPAddress> bypassAddresses = confService.getBypassHosts();
for (IPAddress bypassAddr : bypassAddresses) {
// If the address contains current client address, flip enforce flag
// and break out
if (clientAddr != null && clientAddr.isIPAddress()
&& bypassAddr.getIPVersion().equals(clientAddr.getIPVersion())
&& bypassAddr.contains(clientAddr.getAddress())) {
enforceHost = false;
break;
}
}

// Check for a list of addresses that should be enforced and iterate
List<IPAddress> enforceAddresses = confService.getEnforceHosts();

// Only continue processing if the list is not empty
if (enforceAddresses != null && !enforceAddresses.isEmpty()) {

if (clientAddr == null || !clientAddr.isIPAddress()) {
logger.warn("Client address is not valid, "
+ "MFA will be enforced.");
enforceHost = true;
}

else {
// With addresses set, this default changes to false.
enforceHost = false;

for (IPAddress enforceAddr : enforceAddresses) {

// If there's a match, flip the enforce flag and break out of the loop
if (enforceAddr.getIPVersion().equals(clientAddr.getIPVersion())
&& enforceAddr.contains(clientAddr.getAddress())) {
enforceHost = true;
break;
}
}
}
}

// If the enforce flag has been changed, exit, bypassing TOTP MFA.
if (!enforceHost)
return;

// Ignore anonymous users
String username = authenticatedUser.getIdentifier();
if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
Expand All @@ -302,10 +364,6 @@ public void verifyIdentity(UserContext context,
if (key == null)
return;

// Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials();
HttpServletRequest request = credentials.getRequest();

// Retrieve TOTP from request
String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);

Expand Down

0 comments on commit 850533c

Please sign in to comment.