Skip to content

Commit bffedfd

Browse files
committed
GUACAMOLE-1855: Implement bypass and enforcement options in the Duo 2FA module.
1 parent 4bf572f commit bffedfd

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

extensions/guacamole-auth-duo/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@
155155
<version>2.5</version>
156156
<scope>provided</scope>
157157
</dependency>
158+
159+
<!-- Library for unified IPv4/6 parsing and validation -->
160+
<dependency>
161+
<groupId>com.github.seancfoley</groupId>
162+
<artifactId>ipaddress</artifactId>
163+
<version>5.4.0</version>
164+
</dependency>
158165

159166
</dependencies>
160167

extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
package org.apache.guacamole.auth.duo;
2121

2222
import com.google.inject.Inject;
23+
import inet.ipaddr.IPAddress;
24+
import inet.ipaddr.IPAddressString;
2325
import java.util.Collections;
26+
import java.util.List;
2427
import javax.servlet.http.HttpServletRequest;
2528
import org.apache.guacamole.GuacamoleException;
2629
import org.apache.guacamole.auth.duo.api.DuoService;
@@ -71,10 +74,41 @@ public void verifyAuthenticatedUser(AuthenticatedUser authenticatedUser)
7174
// Pull the original HTTP request used to authenticate
7275
Credentials credentials = authenticatedUser.getCredentials();
7376
HttpServletRequest request = credentials.getRequest();
77+
IPAddressString clientAddr = new IPAddressString(request.getRemoteAddr());
7478

7579
// Ignore anonymous users
7680
if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
7781
return;
82+
83+
// Check for a list of addresses that should be bypassed and iterate
84+
List<IPAddress> bypassAddresses = confService.getBypassHosts();
85+
for (IPAddress bypassAddr : bypassAddresses) {
86+
87+
// If the address contains current client address, return as the user is allowed.
88+
if (bypassAddr.contains(clientAddr.getAddress()))
89+
return;
90+
}
91+
92+
// Check for a list of addresses that should be enforced and iterate
93+
List<IPAddress> enforceAddresses = confService.getEnforceHosts();
94+
95+
// Only continue processing if the list is not empty
96+
if (enforceAddresses != null && !enforceAddresses.isEmpty()) {
97+
boolean enforce = false;
98+
99+
for (IPAddress enforceAddr : enforceAddresses) {
100+
101+
// If there's a match, flip the enforce flag and break out of the loop
102+
if (enforceAddr.contains(clientAddr.getAddress())) {
103+
enforce = true;
104+
break;
105+
}
106+
}
107+
108+
// If the enforce flag has not been flipped, get out
109+
if (!enforce)
110+
return;
111+
}
78112

79113
// Retrieve signed Duo response from request
80114
String signedResponse = request.getParameter(DuoSignedResponseField.PARAMETER_NAME);

extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/conf/ConfigurationService.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
package org.apache.guacamole.auth.duo.conf;
2121

2222
import com.google.inject.Inject;
23+
import inet.ipaddr.IPAddress;
24+
import java.util.List;
2325
import org.apache.guacamole.GuacamoleException;
2426
import org.apache.guacamole.environment.Environment;
27+
import org.apache.guacamole.properties.IPListProperty;
2528
import org.apache.guacamole.properties.StringGuacamoleProperty;
2629

2730
/**
@@ -90,6 +93,40 @@ public class ConfigurationService {
9093
public String getName() { return "duo-application-key"; }
9194

9295
};
96+
97+
/**
98+
* The optional property that contains a comma-separated list of IP addresses
99+
* or CIDRs for which the MFA requirement should be bypassed. If the Duo
100+
* extension is installed, any/all users authenticating from clients that
101+
* match this list will be able to successfully log in without fulfilling
102+
* the MFA requirement. If this option is omitted or is empty, and the
103+
* Duo module is installed, all users from all hosts will have Duo MFA
104+
* enforced.
105+
*/
106+
private static final IPListProperty DUO_BYPASS_HOSTS =
107+
new IPListProperty() {
108+
109+
@Override
110+
public String getName() { return "duo-bypass-hosts"; }
111+
112+
};
113+
114+
/**
115+
* The optional property that contains a comma-separated list of IP addresses
116+
* or CIDRs for which the MFA requirement should be explicitly enforced. If
117+
* the Duo module is enabled and this property is specified, users that log
118+
* in from hosts that match the items in this list will have Duo MFA required,
119+
* and all users from hosts that do not match this list will be able to log
120+
* in without the MFA requirement. If this option is missing or empty and
121+
* the Duo module is installed, MFA will be enforced for all users.
122+
*/
123+
private static final IPListProperty DUO_ENFORCE_HOSTS =
124+
new IPListProperty() {
125+
126+
@Override
127+
public String getName() { return "duo-enforce-hosts"; }
128+
129+
};
93130

94131
/**
95132
* Returns the hostname of the Duo API endpoint to be used to verify user
@@ -156,5 +193,43 @@ public String getSecretKey() throws GuacamoleException {
156193
public String getApplicationKey() throws GuacamoleException {
157194
return environment.getRequiredProperty(DUO_APPLICATION_KEY);
158195
}
196+
197+
/**
198+
* Returns the list of IP addresses and subnets defined in guacamole.properties
199+
* for which Duo MFA should _not_ be enforced. Users logging in from hosts
200+
* contained in this list will be logged in without the MFA requirement.
201+
*
202+
* @return
203+
* A list of IP addresses and subnets for which Duo MFA should not be
204+
* enforced.
205+
*
206+
* @throws GuacamoleException
207+
* If guacamole.properties cannot be parsed, or if an invalid IP address
208+
* or subnet is specified.
209+
*/
210+
public List<IPAddress> getBypassHosts() throws GuacamoleException {
211+
return environment.getProperty(DUO_BYPASS_HOSTS);
212+
}
213+
214+
/**
215+
* Returns the list of IP addresses and subnets defined in guacamole.properties
216+
* for which Duo MFA should explicitly be enforced, while logins from all
217+
* other hosts should not enforce MFA. Users logging in from hosts
218+
* contained in this list will be required to complete the Duo MFA authentication,
219+
* while users from all other hosts will be logged in without the MFA requirement.
220+
*
221+
* @return
222+
* A list of IP addresses and subnets for which Duo MFA should be
223+
* explicitly enforced.
224+
*
225+
* @throws GuacamoleException
226+
* If guacamole.properties cannot be parsed, or if an invalid IP address
227+
* or subnet is specified.
228+
*/
229+
public List<IPAddress> getEnforceHosts() throws GuacamoleException {
230+
return environment.getProperty(DUO_ENFORCE_HOSTS);
231+
}
232+
233+
159234

160235
}

0 commit comments

Comments
 (0)