288194 Add blacklist/whitelist to ProxyServlet and ProxyHandler
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2020 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
51c6be9c42
commit
6bbaa8feaf
|
@ -1,4 +1,5 @@
|
|||
jetty-7.1.5-SNAPSHOT
|
||||
+ 288194 Add blacklist/whitelist to ProxyServlet and ProxyHandler
|
||||
+ 311550 The WebAppProvider should allow setTempDirectory
|
||||
+ 316449 Websocket disconnect fix
|
||||
+ 316584 Exception on startup if temp path has spaces and extractWAR=false
|
||||
|
|
|
@ -13,10 +13,12 @@
|
|||
|
||||
package org.eclipse.jetty.embedded;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.bio.SocketConnector;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.ProxyHandler;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.servlets.ProxyServlet;
|
||||
|
||||
public class ProxyServer
|
||||
|
@ -24,18 +26,25 @@ public class ProxyServer
|
|||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
Connector connector = new SocketConnector();
|
||||
SelectChannelConnector connector = new SelectChannelConnector();
|
||||
connector.setPort(8080);
|
||||
server.setConnectors(new Connector[]
|
||||
{ connector });
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletHandler handler = new ServletHandler();
|
||||
server.setHandler(handler);
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
server.setHandler(handlers);
|
||||
|
||||
handler.addServletWithMapping(ProxyServlet.class,"/");
|
||||
ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
|
||||
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
|
||||
proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org");
|
||||
proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
|
||||
context.addServlet(proxyServlet, "/*");
|
||||
|
||||
ProxyHandler proxy = new ProxyHandler();
|
||||
proxy.setWhite(new String[]{"mail.google.com"});
|
||||
proxy.addWhite("www.google.com");
|
||||
handlers.addHandler(proxy);
|
||||
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -26,6 +27,7 @@ import org.eclipse.jetty.server.Handler;
|
|||
import org.eclipse.jetty.server.HttpConnection;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.HostMap;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -48,17 +50,31 @@ public class ProxyHandler extends HandlerWrapper
|
|||
private volatile int _writeTimeout = 30000;
|
||||
private volatile ThreadPool _threadPool;
|
||||
private volatile boolean _privateThreadPool;
|
||||
private HostMap<String> _white = new HostMap<String>();
|
||||
private HostMap<String> _black = new HostMap<String>();
|
||||
|
||||
public ProxyHandler()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ProxyHandler(String[] white, String[] black)
|
||||
{
|
||||
this(null, white, black);
|
||||
}
|
||||
|
||||
public ProxyHandler(Handler handler)
|
||||
{
|
||||
setHandler(handler);
|
||||
}
|
||||
|
||||
public ProxyHandler(Handler handler, String[] white, String[] black)
|
||||
{
|
||||
setHandler(handler);
|
||||
set(white, _white);
|
||||
set(black, _black);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout, in milliseconds, to connect to the remote server
|
||||
*/
|
||||
|
@ -208,6 +224,14 @@ public class ProxyHandler extends HandlerWrapper
|
|||
host = serverAddress.substring(0, colon);
|
||||
port = Integer.parseInt(serverAddress.substring(colon + 1));
|
||||
}
|
||||
|
||||
if (!validateDestination(host))
|
||||
{
|
||||
Log.info("ProxyHandler: Forbidden destination "+host);
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
SocketChannel channel = connectToServer(request, host, port);
|
||||
|
||||
|
@ -705,4 +729,119 @@ public class ProxyHandler extends HandlerWrapper
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Add a whitelist entry to an existing handler configuration
|
||||
*
|
||||
* @param entry new whitelist entry
|
||||
*/
|
||||
public void addWhite(String entry)
|
||||
{
|
||||
add(entry, _white);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Add a blacklist entry to an existing handler configuration
|
||||
*
|
||||
* @param entry new blacklist entry
|
||||
*/
|
||||
public void addBlack(String entry)
|
||||
{
|
||||
add(entry, _black);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Re-initialize the whitelist of existing handler object
|
||||
*
|
||||
* @param entries array of whitelist entries
|
||||
*/
|
||||
public void setWhite(String[] entries)
|
||||
{
|
||||
set(entries, _white);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Re-initialize the blacklist of existing handler object
|
||||
*
|
||||
* @param entries array of blacklist entries
|
||||
*/
|
||||
public void setBlack(String[] entries)
|
||||
{
|
||||
set(entries, _black);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Helper method to process a list of new entries and replace
|
||||
* the content of the specified host map
|
||||
*
|
||||
* @param entries new entries
|
||||
* @param patternMap target host map
|
||||
*/
|
||||
protected void set(String[] entries, HostMap<String> hostMap)
|
||||
{
|
||||
hostMap.clear();
|
||||
|
||||
if (entries != null && entries.length > 0)
|
||||
{
|
||||
for (String addrPath:entries)
|
||||
{
|
||||
add(addrPath, hostMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Helper method to process the new entry and add it to
|
||||
* the specified host map.
|
||||
*
|
||||
* @param entry new entry
|
||||
* @param patternMap target host map
|
||||
*/
|
||||
private void add(String entry, HostMap<String> hostMap)
|
||||
{
|
||||
if (entry != null && entry.length() > 0)
|
||||
{
|
||||
entry = entry.trim();
|
||||
if (hostMap.get(entry) == null)
|
||||
{
|
||||
hostMap.put(entry,entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Check the request hostname against white- and blacklist.
|
||||
*
|
||||
* @param host hostname to check
|
||||
* @return true if hostname is allowed to be proxied
|
||||
*/
|
||||
public boolean validateDestination(String host)
|
||||
{
|
||||
if (_white.size()>0)
|
||||
{
|
||||
Object whiteObj = _white.getLazyMatches(host);
|
||||
if (whiteObj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_black.size() > 0)
|
||||
{
|
||||
Object blackObj = _black.getLazyMatches(host);
|
||||
if (blackObj != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,13 @@ import java.net.MalformedURLException;
|
|||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
|
@ -42,8 +47,10 @@ import org.eclipse.jetty.http.HttpHeaderValues;
|
|||
import org.eclipse.jetty.http.HttpHeaders;
|
||||
import org.eclipse.jetty.http.HttpSchemes;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.PathMap;
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.HostMap;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -67,13 +74,15 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
* <li>maxThreads - maximum threads
|
||||
* <li>maxConnections - maximum connections per destination
|
||||
* <li>HostHeader - Force the host header to a particular value
|
||||
* <li>whiteList - comma-separated list of allowed proxy destinations
|
||||
* <li>blackList - comma-separated list of forbidden proxy destinations
|
||||
* </ul>
|
||||
*/
|
||||
public class ProxyServlet implements Servlet
|
||||
{
|
||||
protected Logger _log;
|
||||
HttpClient _client;
|
||||
String _hostHeader;
|
||||
protected HttpClient _client;
|
||||
protected String _hostHeader;
|
||||
|
||||
protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
|
||||
{
|
||||
|
@ -90,6 +99,8 @@ public class ProxyServlet implements Servlet
|
|||
|
||||
protected ServletConfig _config;
|
||||
protected ServletContext _context;
|
||||
protected HostMap<PathMap> _white = new HostMap<PathMap>();
|
||||
protected HostMap<PathMap> _black = new HostMap<PathMap>();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* (non-Javadoc)
|
||||
|
@ -129,6 +140,17 @@ public class ProxyServlet implements Servlet
|
|||
_context.setAttribute(config.getServletName()+".ThreadPool",_client.getThreadPool());
|
||||
_context.setAttribute(config.getServletName()+".HttpClient",_client);
|
||||
}
|
||||
|
||||
String white = config.getInitParameter("whiteList");
|
||||
if (white != null)
|
||||
{
|
||||
parseList(white, _white);
|
||||
}
|
||||
String black = config.getInitParameter("blackList");
|
||||
if (black != null)
|
||||
{
|
||||
parseList(black, _black);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -136,6 +158,95 @@ public class ProxyServlet implements Servlet
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Helper function to process a parameter value containing a list
|
||||
* of new entries and initialize the specified host map.
|
||||
*
|
||||
* @param list comma-separated list of new entries
|
||||
* @param hostMap target host map
|
||||
*/
|
||||
private void parseList(String list, HostMap<PathMap> hostMap)
|
||||
{
|
||||
if (list != null && list.length() > 0)
|
||||
{
|
||||
int idx;
|
||||
String entry;
|
||||
|
||||
StringTokenizer entries = new StringTokenizer(list, ",");
|
||||
while(entries.hasMoreTokens())
|
||||
{
|
||||
entry = entries.nextToken();
|
||||
idx = entry.indexOf('/');
|
||||
|
||||
String host = idx > 0 ? entry.substring(0,idx) : entry;
|
||||
String path = idx > 0 ? entry.substring(idx) : "/*";
|
||||
|
||||
host = host.trim();
|
||||
PathMap pathMap = hostMap.get(host);
|
||||
if (pathMap == null)
|
||||
{
|
||||
pathMap = new PathMap(true);
|
||||
hostMap.put(host,pathMap);
|
||||
}
|
||||
if (path != null)
|
||||
{
|
||||
pathMap.put(path,path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Check the request hostname and path against white- and blacklist.
|
||||
*
|
||||
* @param host hostname to check
|
||||
* @param path path to check
|
||||
* @return true if request is allowed to be proxied
|
||||
*/
|
||||
public boolean validateDestination(String host, String path)
|
||||
{
|
||||
if (_white.size()>0)
|
||||
{
|
||||
boolean match = false;
|
||||
|
||||
Object whiteObj = _white.getLazyMatches(host);
|
||||
if (whiteObj != null)
|
||||
{
|
||||
List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
|
||||
|
||||
for (Object entry: whiteList)
|
||||
{
|
||||
PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
|
||||
if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_black.size() > 0)
|
||||
{
|
||||
Object blackObj = _black.getLazyMatches(host);
|
||||
if (blackObj != null)
|
||||
{
|
||||
List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
|
||||
|
||||
for (Object entry: blackList)
|
||||
{
|
||||
PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
|
||||
if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#getServletConfig()
|
||||
|
@ -423,6 +534,9 @@ public class ProxyServlet implements Servlet
|
|||
protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
|
||||
throws MalformedURLException
|
||||
{
|
||||
if (!validateDestination(serverName, uri))
|
||||
return null;
|
||||
|
||||
return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
|
||||
}
|
||||
|
||||
|
@ -517,8 +631,13 @@ public class ProxyServlet implements Servlet
|
|||
{
|
||||
if (!uri.startsWith(_prefix))
|
||||
return null;
|
||||
|
||||
URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
|
||||
|
||||
if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
|
||||
return null;
|
||||
|
||||
return new HttpURI(new URI(_proxyTo + uri.substring(_prefix.length())).normalize().toString());
|
||||
return new HttpURI(dstUri.toString());
|
||||
}
|
||||
catch (URISyntaxException ex)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
|
||||
|
||||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public class HostMap<TYPE> extends HashMap<String, TYPE>
|
||||
{
|
||||
/* --------------------------------------------------------------- */
|
||||
/** Construct empty HostMap.
|
||||
*/
|
||||
public HostMap()
|
||||
{
|
||||
super(11);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/** Construct empty HostMap.
|
||||
*
|
||||
* @param capacity initial capacity
|
||||
*/
|
||||
public HostMap(int capacity)
|
||||
{
|
||||
super (capacity);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public TYPE put(String host, TYPE object)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
return super.put(host, object);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see java.util.HashMap#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public TYPE get(Object key)
|
||||
{
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Retrieve a lazy list of map entries associated with specified
|
||||
* hostname by taking into account the domain suffix matches.
|
||||
*
|
||||
* @param host hostname
|
||||
* @return lazy list of map entries
|
||||
*/
|
||||
public Object getLazyMatches(String host)
|
||||
{
|
||||
if (host == null)
|
||||
return LazyList.getList(super.entrySet());
|
||||
|
||||
int idx = 0;
|
||||
String domain = host.trim();
|
||||
HashSet<String> domains = new HashSet<String>();
|
||||
do {
|
||||
domains.add(domain);
|
||||
if ((idx = domain.indexOf('.')) > 0)
|
||||
{
|
||||
domain = domain.substring(idx+1);
|
||||
}
|
||||
} while (idx > 0);
|
||||
|
||||
Object entries = null;
|
||||
for(Map.Entry<String, TYPE> entry: super.entrySet())
|
||||
{
|
||||
if (domains.contains(entry.getKey()))
|
||||
{
|
||||
entries = LazyList.add(entries,entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue