> patternMap)
- {
- for (String path: patternMap.keySet())
- {
- for (String addr: patternMap.get(path).keySet())
- {
- buf.append("# ");
- buf.append(addr);
- buf.append("|");
- buf.append(path);
- buf.append("\n");
- }
- }
+ dumpObjects(out, indent,
+ DumpableCollection.from("white", _white),
+ DumpableCollection.from("black", _black),
+ DumpableCollection.from("whiteListByPath", _whiteListByPath));
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java
index 0573f237286..0bd6def29a6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java
@@ -30,6 +30,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.component.DumpableCollection;
@@ -39,16 +40,32 @@ import org.eclipse.jetty.util.log.Logger;
/**
* InetAddress Access Handler
*
- * Controls access to the wrapped handler using the real remote IP. Control is provided
- * by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This handler
- * uses the real internet address of the connection, not one reported in the forwarded
- * for headers, as this cannot be as easily forged.
+ * Controls access to the wrapped handler using the real remote IP. Control is
+ * provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This
+ * handler uses the real internet address of the connection, not one reported in
+ * the forwarded for headers, as this cannot be as easily forged.
+ *
+ * Additionally, there may be times when you want to only apply this handler to
+ * a subset of your connectors. In this situation you can use
+ * connectorNames to specify the connector names that you want this IP
+ * access filter to apply to.
*/
public class InetAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
private final IncludeExcludeSet _set = new IncludeExcludeSet<>(InetAddressSet.class);
+ private final IncludeExclude _connectorNames = new IncludeExclude<>();
+
+ /**
+ * Clears all the includes, excludes, included connector names and excluded
+ * connector names.
+ */
+ public void clear()
+ {
+ _set.clear();
+ _connectorNames.clear();
+ }
/**
* Includes an InetAddress pattern
@@ -94,11 +111,52 @@ public class InetAccessHandler extends HandlerWrapper
_set.exclude(patterns);
}
+ /**
+ * Includes a connector name.
+ *
+ * @param name Connector name to include in this handler.
+ */
+ public void includeConnectorName(String name)
+ {
+ _connectorNames.include(name);
+ }
+
+ /**
+ * Excludes a connector name.
+ *
+ * @param name Connector name to exclude in this handler.
+ */
+ public void excludeConnectorName(String name)
+ {
+ _connectorNames.exclude(name);
+ }
+
+ /**
+ * Includes connector names.
+ *
+ * @param names Connector names to include in this handler.
+ */
+ public void includeConnectorNames(String... names)
+ {
+ _connectorNames.include(names);
+ }
+
+ /**
+ * Excludes connector names.
+ *
+ * @param names Connector names to exclude in this handler.
+ */
+ public void excludeConnectorNames(String... names)
+ {
+ _connectorNames.exclude(names);
+ }
+
/**
* Checks the incoming request against the whitelist and blacklist
*/
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
{
// Get the real remote IP (not the one set by the forwarded headers (which may be forged))
HttpChannel channel = baseRequest.getHttpChannel();
@@ -108,7 +166,7 @@ public class InetAccessHandler extends HandlerWrapper
if (endp != null)
{
InetSocketAddress address = endp.getRemoteAddress();
- if (address != null && !isAllowed(address.getAddress(), request))
+ if (address != null && !isAllowed(address.getAddress(), baseRequest, request))
{
response.sendError(HttpStatus.FORBIDDEN_403);
baseRequest.setHandled(true);
@@ -123,23 +181,46 @@ public class InetAccessHandler extends HandlerWrapper
/**
* Checks if specified address and request are allowed by current InetAddress rules.
*
- * @param address the inetAddress to check
- * @param request the request to check
+ * @param address the inetAddress to check
+ * @param baseRequest the base request to check
+ * @param request the HttpServletRequest request to check
* @return true if inetAddress and request are allowed
*/
- protected boolean isAllowed(InetAddress address, HttpServletRequest request)
+ protected boolean isAllowed(InetAddress address, Request baseRequest, HttpServletRequest request)
{
- boolean allowed = _set.test(address);
+ String connectorName = baseRequest.getHttpChannel().getConnector().getName();
+ boolean allowed = !isMatchingConnectorName(connectorName) || _set.test(address);
if (LOG.isDebugEnabled())
LOG.debug("{} {} {} for {}", this, allowed ? "allowed" : "denied", address, request);
return allowed;
}
+ /**
+ * Checks if this is a connector name that applies to this access handler.
+ *
+ * @return true if connector name is applicable given connectorNames property
+ */
+ protected boolean isMatchingConnectorName(String connectorName)
+ {
+ boolean hasConnectorNames = !_connectorNames.getIncluded().isEmpty();
+ if (connectorName == null)
+ {
+ return !hasConnectorNames;
+ }
+ if (hasConnectorNames && !_connectorNames.getIncluded().contains(connectorName))
+ {
+ return false;
+ }
+ return !_connectorNames.getExcluded().contains(connectorName);
+ }
+
@Override
public void dump(Appendable out, String indent) throws IOException
{
- dumpObjects(out, indent,
- DumpableCollection.from("included",_set.getIncluded()),
- DumpableCollection.from("excluded",_set.getExcluded()));
+ dumpObjects(out, indent,
+ DumpableCollection.from("included", _set.getIncluded()),
+ DumpableCollection.from("excluded", _set.getExcluded()),
+ DumpableCollection.from("includedConnectorNames", _connectorNames.getIncluded()),
+ DumpableCollection.from("excludedConnectorNames", _connectorNames.getExcluded()));
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetHandlerTest.java
new file mode 100644
index 00000000000..e23d89688d6
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/InetHandlerTest.java
@@ -0,0 +1,273 @@
+package org.eclipse.jetty.server.handler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class InetHandlerTest
+{
+ private static Server _server;
+ private static ServerConnector _connector;
+ private static InetAccessHandler _handler;
+
+ @BeforeAll
+ public static void setUp() throws Exception
+ {
+ _server = new Server();
+ _connector = new ServerConnector(_server);
+ _connector.setName("http");
+ _server.setConnectors(new Connector[] { _connector });
+
+ _handler = new InetAccessHandler();
+ _handler.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request,
+ HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setStatus(HttpStatus.OK_200);
+ }
+ });
+ _server.setHandler(_handler);
+ _server.start();
+ }
+
+ /* ------------------------------------------------------------ */
+ @AfterAll
+ public static void tearDown() throws Exception
+ {
+ _server.stop();
+ }
+
+ /* ------------------------------------------------------------ */
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testHandler(String include, String exclude, String includeConnectors, String excludeConnectors,
+ String host, String uri, String code) throws Exception
+ {
+ _handler.clear();
+ for (String inc : include.split(";", -1)) {
+ if (inc.length() > 0) {
+ _handler.include(inc);
+ }
+ }
+ for (String exc : exclude.split(";", -1)) {
+ if (exc.length() > 0) {
+ _handler.exclude(exc);
+ }
+ }
+ for (String inc : includeConnectors.split(";", -1)) {
+ if (inc.length() > 0) {
+ _handler.includeConnectorName(inc);
+ }
+ }
+ for (String exc : excludeConnectors.split(";", -1)) {
+ if (exc.length() > 0) {
+ _handler.excludeConnectorName(exc);
+ }
+ }
+ String request = "GET " + uri + " HTTP/1.1\n" + "Host: " + host + "\n\n";
+ Socket socket = new Socket("127.0.0.1", _connector.getLocalPort());
+ socket.setSoTimeout(5000);
+ try {
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ output.write(request.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ Response response = readResponse(input);
+ Object[] params = new Object[] { "Request WBHUC", include, exclude, includeConnectors, excludeConnectors,
+ host, uri, code, "Response", response.getCode() };
+ assertEquals(code, response.getCode(), Arrays.deepToString(params));
+ } finally {
+ socket.close();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Response readResponse(BufferedReader reader) throws IOException
+ {
+ // Simplified parser for HTTP responses
+ String line = reader.readLine();
+ if (line == null)
+ throw new EOFException();
+ Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
+ assertTrue(responseLine.lookingAt());
+ String code = responseLine.group(1);
+
+ Map headers = new LinkedHashMap<>();
+ while ((line = reader.readLine()) != null) {
+ if (line.trim().length() == 0)
+ break;
+
+ Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
+ assertTrue(header.lookingAt());
+ String headerName = header.group(1);
+ String headerValue = header.group(2);
+ headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
+ }
+
+ StringBuilder body = new StringBuilder();
+ if (headers.containsKey("content-length")) {
+ int length = Integer.parseInt(headers.get("content-length"));
+ for (int i = 0; i < length; ++i) {
+ char c = (char) reader.read();
+ body.append(c);
+ }
+ } else if ("chunked".equals(headers.get("transfer-encoding"))) {
+ while ((line = reader.readLine()) != null) {
+ if ("0".equals(line)) {
+ line = reader.readLine();
+ assertEquals("", line);
+ break;
+ }
+
+ int length = Integer.parseInt(line, 16);
+ for (int i = 0; i < length; ++i) {
+ char c = (char) reader.read();
+ body.append(c);
+ }
+ line = reader.readLine();
+ assertEquals("", line);
+ }
+ }
+
+ return new Response(code, headers, body.toString().trim());
+ }
+
+ /* ------------------------------------------------------------ */
+ protected class Response
+ {
+ private final String code;
+ private final Map headers;
+ private final String body;
+
+ /* ------------------------------------------------------------ */
+ private Response(String code, Map headers, String body)
+ {
+ this.code = code;
+ this.headers = headers;
+ this.body = body;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getCode()
+ {
+ return code;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Map getHeaders()
+ {
+ return headers;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getBody()
+ {
+ return body;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(code).append("\r\n");
+ for (Map.Entry entry : headers.entrySet())
+ builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
+ builder.append("\r\n");
+ builder.append(body);
+ return builder.toString();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Stream data()
+ {
+ Object[][] data = new Object[][] {
+ // Empty lists
+ { "", "", "", "", "127.0.0.1", "/", "200" }, { "", "", "", "", "127.0.0.1", "/dump/info", "200" },
+
+ // test simple filters
+ { "127.0.0.1", "", "", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1", "", "", "", "127.0.0.1", "/dump/info", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1", "", "", "", "127.0.0.1", "/", "403" },
+ { "192.0.0.1", "", "", "", "127.0.0.1", "/dump/info", "403" },
+ { "192.0.0.1-192.0.0.254", "", "", "", "127.0.0.1", "/", "403" },
+ { "192.0.0.1-192.0.0.254", "", "", "", "127.0.0.1", "/dump/info", "403" },
+
+ // test connector name filters
+ { "127.0.0.1", "", "http", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1", "", "http", "", "127.0.0.1", "/dump/info", "200" },
+ { "127.0.0.1-127.0.0.254", "", "http", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1-127.0.0.254", "", "http", "", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1", "", "http", "", "127.0.0.1", "/", "403" },
+ { "192.0.0.1", "", "http", "", "127.0.0.1", "/dump/info", "403" },
+ { "192.0.0.1-192.0.0.254", "", "http", "", "127.0.0.1", "/", "403" },
+ { "192.0.0.1-192.0.0.254", "", "http", "", "127.0.0.1", "/dump/info", "403" },
+
+ { "127.0.0.1", "", "nothttp", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1", "", "nothttp", "", "127.0.0.1", "/dump/info", "200" },
+ { "127.0.0.1-127.0.0.254", "", "nothttp", "", "127.0.0.1", "/", "200" },
+ { "127.0.0.1-127.0.0.254", "", "nothttp", "", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1", "", "nothttp", "", "127.0.0.1", "/", "200" },
+ { "192.0.0.1", "", "nothttp", "", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1-192.0.0.254", "", "nothttp", "", "127.0.0.1", "/", "200" },
+ { "192.0.0.1-192.0.0.254", "", "nothttp", "", "127.0.0.1", "/dump/info", "200" },
+
+ { "127.0.0.1", "", "", "http", "127.0.0.1", "/", "200" },
+ { "127.0.0.1", "", "", "http", "127.0.0.1", "/dump/info", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "http", "127.0.0.1", "/", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "http", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1", "", "", "http", "127.0.0.1", "/", "200" },
+ { "192.0.0.1", "", "", "http", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1-192.0.0.254", "", "", "http", "127.0.0.1", "/", "200" },
+ { "192.0.0.1-192.0.0.254", "", "", "http", "127.0.0.1", "/dump/info", "200" },
+
+ { "127.0.0.1", "", "", "nothttp", "127.0.0.1", "/", "200" },
+ { "127.0.0.1", "", "", "nothttp", "127.0.0.1", "/dump/info", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "nothttp", "127.0.0.1", "/", "200" },
+ { "127.0.0.1-127.0.0.254", "", "", "nothttp", "127.0.0.1", "/dump/info", "200" },
+ { "192.0.0.1", "", "", "nothttp", "127.0.0.1", "/", "403" },
+ { "192.0.0.1", "", "", "nothttp", "127.0.0.1", "/dump/info", "403" },
+ { "192.0.0.1-192.0.0.254", "", "", "nothttp", "127.0.0.1", "/", "403" },
+ { "192.0.0.1-192.0.0.254", "", "", "nothttp", "127.0.0.1", "/dump/info", "403" },
+
+ };
+ return Arrays.asList(data).stream().map(Arguments::of);
+ }
+}