Kaynağa Gözat

http proxy exclusion setting

jrivard@gmail.com 7 yıl önce
ebeveyn
işleme
5b3279ebd1

+ 3 - 0
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -93,6 +93,9 @@ public enum PwmSetting
             "locale.cookie.age", PwmSettingSyntax.DURATION, PwmSettingCategory.LOCALIZATION ),
     HTTP_PROXY_URL(
             "http.proxy.url", PwmSettingSyntax.STRING, PwmSettingCategory.GENERAL ),
+    HTTP_PROXY_EXCEPTIONS(
+            "http.proxy.exceptions", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.GENERAL ),
+
     APP_PROPERTY_OVERRIDES(
             "pwm.appProperty.overrides", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.GENERAL ),
 

+ 53 - 0
server/src/main/java/password/pwm/http/PwmURL.java

@@ -23,8 +23,10 @@
 package password.pwm.http;
 
 import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.http.HttpServletRequest;
 import java.net.URI;
@@ -34,9 +36,12 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 public class PwmURL
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmURL.class );
+
     private URI uri;
     private String contextPath;
 
@@ -418,4 +423,52 @@ public class PwmURL
         }
         return requestPath;
     }
+
+    public static boolean testIfUrlMatchesAllowedPattern(
+            final String testURI,
+            final List<String> whiteList,
+            final SessionLabel sessionLabel
+            )
+    {
+        final String regexPrefix = "regex:";
+        for ( final String loopFragment : whiteList )
+        {
+            if ( loopFragment.startsWith( regexPrefix ) )
+            {
+                try
+                {
+                    final String strPattern = loopFragment.substring( regexPrefix.length(), loopFragment.length() );
+                    final Pattern pattern = Pattern.compile( strPattern );
+                    if ( pattern.matcher( testURI ).matches() )
+                    {
+                        LOGGER.debug( sessionLabel, "positive URL match for regex pattern: " + strPattern );
+                        return true;
+                    }
+                    else
+                    {
+                        LOGGER.trace( sessionLabel, "negative URL match for regex pattern: " + strPattern );
+                    }
+                }
+                catch ( Exception e )
+                {
+                    LOGGER.error( sessionLabel, "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage() );
+                }
+
+            }
+            else
+            {
+                if ( testURI.startsWith( loopFragment ) )
+                {
+                    LOGGER.debug( sessionLabel, "positive URL match for pattern: " + loopFragment );
+                    return true;
+                }
+                else
+                {
+                    LOGGER.trace( sessionLabel, "negative URL match for pattern: " + loopFragment );
+                }
+            }
+        }
+
+        return false;
+    }
 }

+ 53 - 4
server/src/main/java/password/pwm/http/client/PwmHttpClient.java

@@ -24,6 +24,7 @@ package password.pwm.http.client;
 
 import org.apache.http.Header;
 import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.auth.AuthScope;
@@ -40,6 +41,8 @@ import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.config.Registry;
 import org.apache.http.config.RegistryBuilder;
 import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.routing.HttpRoutePlanner;
 import org.apache.http.conn.socket.ConnectionSocketFactory;
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
@@ -48,6 +51,7 @@ import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.ProxyAuthenticationStrategy;
 import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.protocol.HttpContext;
 import org.apache.http.ssl.SSLContextBuilder;
 import org.apache.http.ssl.TrustStrategy;
 import org.apache.http.util.EntityUtils;
@@ -62,6 +66,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmURL;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -82,6 +87,7 @@ import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class PwmHttpClient
@@ -111,10 +117,14 @@ public class PwmHttpClient
     public static HttpClient getHttpClient( final Configuration configuration )
             throws PwmUnrecoverableException
     {
-        return getHttpClient( configuration, PwmHttpClientConfiguration.builder().certificates( null ).build() );
+        return getHttpClient( configuration, PwmHttpClientConfiguration.builder().certificates( null ).build(), null );
     }
 
-    public static HttpClient getHttpClient( final Configuration configuration, final PwmHttpClientConfiguration pwmHttpClientConfiguration )
+    public static HttpClient getHttpClient(
+            final Configuration configuration,
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration,
+            final SessionLabel sessionLabel
+    )
             throws PwmUnrecoverableException
     {
         final HttpClientBuilder clientBuilder = HttpClientBuilder.create();
@@ -155,10 +165,12 @@ public class PwmHttpClient
         if ( proxyUrl != null && proxyUrl.length() > 0 )
         {
             final URI proxyURI = URI.create( proxyUrl );
+
             final String host = proxyURI.getHost();
             final int port = proxyURI.getPort();
+            final HttpHost proxyHost = new HttpHost( host, port );
 
-            clientBuilder.setProxy( new HttpHost( host, port ) );
+            clientBuilder.setProxy( proxyHost );
 
             final String userInfo = proxyURI.getUserInfo();
             if ( userInfo != null && userInfo.length() > 0 )
@@ -173,6 +185,8 @@ public class PwmHttpClient
                 clientBuilder.setDefaultCredentialsProvider( credsProvider );
                 clientBuilder.setProxyAuthenticationStrategy( new ProxyAuthenticationStrategy() );
             }
+
+            clientBuilder.setRoutePlanner( new ProxyRoutePlanner( proxyHost, configuration, sessionLabel ) );
         }
 
         clientBuilder.setDefaultRequestConfig( RequestConfig.copy( RequestConfig.DEFAULT )
@@ -349,7 +363,7 @@ public class PwmHttpClient
             }
         }
 
-        final HttpClient httpClient = getHttpClient( pwmApplication.getConfig(), pwmHttpClientConfiguration );
+        final HttpClient httpClient = getHttpClient( pwmApplication.getConfig(), pwmHttpClientConfiguration, sessionLabel );
         return httpClient.execute( httpRequest );
     }
 
@@ -395,5 +409,40 @@ public class PwmHttpClient
 
         throw new IllegalArgumentException( "unknown protocol type: " + url.getProtocol() );
     }
+
+
+
+    private static class ProxyRoutePlanner implements HttpRoutePlanner
+    {
+        private final HttpHost proxyServer;
+        private final Configuration configuration;
+        private final SessionLabel sessionLabel;
+
+
+        ProxyRoutePlanner( final HttpHost proxyServer, final Configuration configuration, final SessionLabel sessionLabel )
+        {
+            this.proxyServer = proxyServer;
+            this.configuration = configuration;
+            this.sessionLabel = sessionLabel;
+        }
+
+        public HttpRoute determineRoute(
+                final HttpHost target,
+                final HttpRequest request,
+                final HttpContext context
+        )
+        {
+            final String targetUri = target.toURI();
+
+            final List<String> proxyExceptionUrls = configuration.readSettingAsStringArray( PwmSetting.HTTP_PROXY_EXCEPTIONS );
+
+            if ( PwmURL.testIfUrlMatchesAllowedPattern( targetUri, proxyExceptionUrls, sessionLabel ) )
+            {
+                return new HttpRoute( target );
+            }
+
+            return new HttpRoute( target, proxyServer );
+        }
+    }
 }
 

+ 3 - 37
server/src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -62,7 +62,6 @@ import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Pattern;
 
 /**
  * This session filter (invoked by the container through the web.xml descriptor) wraps all calls to the
@@ -588,44 +587,11 @@ public class SessionFilter extends AbstractPwmFilter
         final String testURI = sb.toString();
         LOGGER.trace( sessionLabel, "preparing to whitelist test parsed and decoded URL: " + testURI );
 
-        final String regexPrefix = "regex:";
         final List<String> whiteList = pwmApplication.getConfig().readSettingAsStringArray( PwmSetting.SECURITY_REDIRECT_WHITELIST );
-        for ( final String loopFragment : whiteList )
-        {
-            if ( loopFragment.startsWith( regexPrefix ) )
-            {
-                try
-                {
-                    final String strPattern = loopFragment.substring( regexPrefix.length(), loopFragment.length() );
-                    final Pattern pattern = Pattern.compile( strPattern );
-                    if ( pattern.matcher( testURI ).matches() )
-                    {
-                        LOGGER.debug( sessionLabel, "positive URL match for regex pattern: " + strPattern );
-                        return;
-                    }
-                    else
-                    {
-                        LOGGER.trace( sessionLabel, "negative URL match for regex pattern: " + strPattern );
-                    }
-                }
-                catch ( Exception e )
-                {
-                    LOGGER.error( sessionLabel, "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage() );
-                }
 
-            }
-            else
-            {
-                if ( testURI.startsWith( loopFragment ) )
-                {
-                    LOGGER.debug( sessionLabel, "positive URL match for pattern: " + loopFragment );
-                    return;
-                }
-                else
-                {
-                    LOGGER.trace( sessionLabel, "negative URL match for pattern: " + loopFragment );
-                }
-            }
+        if ( PwmURL.testIfUrlMatchesAllowedPattern( testURI, whiteList, sessionLabel ) )
+        {
+            return;
         }
 
         final String errorMsg = testURI + " is not a match for any configured redirect whitelist, see setting: "

+ 4 - 3
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -3775,9 +3775,10 @@
         </options>
     </setting>
     <setting hidden="false" key="http.proxy.url" level="1">
-        <default>
-            <value />
-        </default>
+        <default/>
+    </setting>
+    <setting hidden="false" key="http.proxy.exceptions" level="1">
+        <default/>
     </setting>
     <setting hidden="false" key="cas.clearPassUrl" level="2">
         <default>

+ 3 - 1
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -413,6 +413,7 @@ Setting_Description_helpdesk.verification.form=Specify the attributes used for <
 Setting_Description_helpdesk.verificationMethods=Select the verification methods that the Help Desk operators use to confirm the identity of a user.  Any method you set to required or optional is available to the Help Desk operator.  If one or more methods are set to required, at least one of the required methods must be successfully completed before the Help Desk operator can view the user's details.
 Setting_Description_helpdesk.viewStatusValues=Select the fields that are available to help desk administrators to view the status of the users.
 Setting_Description_http.proxy.url=Specify the URL of the HTTP proxy server.  If blank, the system uses no proxy server.<ul><li>For HTTP proxy server, use "<i>http\://serverame\:3128</i>" format</li><li>For the authenticated proxy server, use the "<i>http\://username\:password@servername\:3128</i>" format</ul></li><br/>
+Setting_Description_http.proxy.exceptions=<p>Specify one or more URLs of proxy exceptions. If an outgoing HTTP request from @PwmAppName@ matches a value in the list the request will be sent direct from the server and not through the configured HTTP Proxy server.</p><ul><li>@PwmAppName@ attempts to match each item from the <b>beginning</b> of the requested URL string.</li><li>@PwmAppName@ decodes and parses the redirect URL before checking it against the whitelist.</li>          <li>If an error occurs when setting a redirect URL, set the debug logs to TRACE and watch the output as the error occurs.</li><li>@PwmAppName@ does not permit wildcards or case mis-matches, the values must match exactly.</li><li>If a fragment has the prefix <i>regex\:</i>, @PwmAppName@ treats the remainder of the fragment as a regular expression.  Regular expression matches must match the entire URL.</li></ul> <table><tr><td>Example</td><td>Matches</td><td>Not Matched</td></tr><td>https\://www.example.com</td><td>https\://www.example.com<br/>https\://www.example.com/<br/>https\://www.example.com/path</td><td>http\://www.example.com<br/>https\://mail.example.com</td></tr><td>http\://www.example.com/p1</td><td>http\://www.example.com/p1<br/>http\://www.example.com/p1/p2<br/>http\://www.example.com/p1?a1\=v1</td><td>https\://www.example.com/p1<br/>http\://www.example.com/p2</td></tr><td>/path1</td><td>/path1<br/>/path1/path2<br/>/path1/path2/?param\=v1</td><td>www.example.com/path1/<br/>https\://www.example.com/path1<br/>/path2</td></tr><td>regex\:^(https?\:\\/\\/)[a-z]*\\.example\\.com.*?$</td><td>http\://www.example.com<br/>https\://www.example.com<br/>http\://www.example.com/p1<br/>http\://mail.example.com/p1</td><td>www.example.com<br/>http\://www.example.org</td></tr></table>
 Setting_Description_https.server.cert=Import the private key and certificate used by the @PwmAppName@ HTTPS web server.  If this setting does not have a value, the @PwmAppName@ HTTPS web server uses an auto-generated value based on @PwmSettingReference\:pwm.selfURL@ and other current configuration data.  Changes to this setting require a server restart.
 Setting_Description_https.server.tls.ciphers=Specify the HTTPS TLS ciphers accepted by the @PwmAppName@ HTTPS web server.  The value for this setting is an ordered, comma separated list of Java SSE provided cipher names.   Changes to this setting require a server restart.
 Setting_Description_https.server.tls.protocols=Select the HTTPS TLS protocols supported by the @PwmAppName@ HTTPS web server.  Changes to this setting require a server restart.
@@ -649,7 +650,7 @@ Setting_Description_security.loginSession.mode=Select the mode @PwmAppName@ uses
 Setting_Description_security.moduleSession.mode=Select the mode @PwmAppName@ uses to manage the module session state.  Local mode is the most secure and reliable, but it does not allow for server fail-over.
 Setting_Description_security.page.leaveNoticeTimeout=Specify a timeout period for when a user navigates away from any page. The browser sends a notice to the server. The next time the browser requrest a page, @PwmAppName@ checks the timeout to determine if the last page leave time was greater then the timeout, and if so, it invalidates the user's session. This has the effect of logging out the users that navigate away from @PwmAppName@ without explicitly logging out. If set to zero, you disable this feature.
 Setting_Description_security.preventFraming=Enable this option to prevent browsers form displaying @PwmAppName@ inside an IFrame.  @PwmAppName@ does this by setting the <b>X-Frame-Options</b> HTTP Header to <b>DENY</b> on all pages.
-Setting_Description_security.redirectUrl.whiteList=Specify a list of partial URL fragments. Any attempt to set the forwardURL or logoutURL via request parameter must match a URL fragment listed here. <ul><li>@PwmAppName@ attempts to match each item from the <b>beginning</b> of the requested URL string.</li><li>@PwmAppName@ decodes and parses the redirect URL before checking it against the whitelist.</li>          <li>If an error occurs when setting a redirect URL, set the debug logs to TRACE and watch the output as the error occurs.</li><li>@PwmAppName@ does not permit wildcards or case mis-matches, the values must match exactly.</li><li>If a fragment has the prefix <i>regex\:</i>, @PwmAppName@ treats the remainder of the fragment as a regular expression.  Regular expression matches must match the entire URL.</li></ul> <table>\n        <tr><td>Example</td><td>Matches</td><td>Not Matched</td></tr>\n        <td>https\://www.example.com</td><td>https\://www.example.com<br/>https\://www.example.com/<br/>https\://www.example.com/path</td><td>http\://www.example.com<br/>https\://mail.example.com</td></tr>\n        <td>http\://www.example.com/p1</td><td>http\://www.example.com/p1<br/>http\://www.example.com/p1/p2<br/>http\://www.example.com/p1?a1\=v1</td><td>https\://www.example.com/p1<br/>http\://www.example.com/p2</td></tr>\n        <td>/path1</td><td>/path1<br/>/path1/path2<br/>/path1/path2/?param\=v1</td><td>www.example.com/path1/<br/>https\://www.example.com/path1<br/>/path2</td></tr>\n        <td>regex\:^(https?\:\\/\\/)[a-z]*\\.example\\.com.*?$</td><td>http\://www.example.com<br/>https\://www.example.com<br/>http\://www.example.com/p1<br/>http\://mail.example.com/p1</td><td>www.example.com<br/>http\://www.example.org</td></tr>\n        </table>\n
+Setting_Description_security.redirectUrl.whiteList=Specify a list of partial URL fragments. Any attempt to set the forwardURL or logoutURL via request parameter must match a URL fragment listed here. <ul><li>@PwmAppName@ attempts to match each item from the <b>beginning</b> of the requested URL string.</li><li>@PwmAppName@ decodes and parses the redirect URL before checking it against the whitelist.</li>          <li>If an error occurs when setting a redirect URL, set the debug logs to TRACE and watch the output as the error occurs.</li><li>@PwmAppName@ does not permit wildcards or case mis-matches, the values must match exactly.</li><li>If a fragment has the prefix <i>regex\:</i>, @PwmAppName@ treats the remainder of the fragment as a regular expression.  Regular expression matches must match the entire URL.</li></ul> <table><tr><td>Example</td><td>Matches</td><td>Not Matched</td></tr><td>https\://www.example.com</td><td>https\://www.example.com<br/>https\://www.example.com/<br/>https\://www.example.com/path</td><td>http\://www.example.com<br/>https\://mail.example.com</td></tr><td>http\://www.example.com/p1</td><td>http\://www.example.com/p1<br/>http\://www.example.com/p1/p2<br/>http\://www.example.com/p1?a1\=v1</td><td>https\://www.example.com/p1<br/>http\://www.example.com/p2</td></tr><td>/path1</td><td>/path1<br/>/path1/path2<br/>/path1/path2/?param\=v1</td><td>www.example.com/path1/<br/>https\://www.example.com/path1<br/>/path2</td></tr><td>regex\:^(https?\:\\/\\/)[a-z]*\\.example\\.com.*?$</td><td>http\://www.example.com<br/>https\://www.example.com<br/>http\://www.example.com/p1<br/>http\://mail.example.com/p1</td><td>www.example.com<br/>http\://www.example.org</td></tr></table>
 Setting_Description_security.sso.authHeaderName=Specify the name of the HTTP header that configures @PwmAppName@ to use an upstream server to allow automatic logins with a only a user name, a password is not required.  This setting controls the name of the HTTP header.  When used, @PwmAppName@ prompts users for their passwords to access certain functionality.
 Setting_Description_session.maxSeconds=Specify the maximum duration of a session (in seconds).  Having a maximum session lifetime prevents certain types of long-term session fixation attacks.
 Setting_Description_shortcut.enable=Enable this option to enable the shortcuts module.
@@ -913,6 +914,7 @@ Setting_Label_helpdesk.verification.form=Verification Attributes
 Setting_Label_helpdesk.verificationMethods=Verification Methods
 Setting_Label_helpdesk.viewStatusValues=Viewable Status Fields
 Setting_Label_http.proxy.url=HTTP Proxy
+Setting_Label_http.proxy.exceptions=HTTP Proxy Exceptions
 Setting_Label_https.server.cert=HTTPS Private Key & Certificate
 Setting_Label_https.server.tls.ciphers=TLS Ciphers
 Setting_Label_https.server.tls.protocols=TLS Protocols

+ 1 - 1
server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java

@@ -157,7 +157,7 @@ public class PwmHttpClientTest {
                 .certificates(getWireMockSelfSignedCertificate())
                 .build();
 
-        HttpClient httpClient = PwmHttpClient.getHttpClient(configuration, pwmHttpClientConfiguration);
+        HttpClient httpClient = PwmHttpClient.getHttpClient(configuration, pwmHttpClientConfiguration, null);
 
         HttpGet httpGet = new HttpGet(String.format("https://localhost:%d/simpleHello", wm.httpsPort()));
         HttpResponse response = httpClient.execute(httpGet);