Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added documentation for DoSHandler. #12546

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.CrossOriginHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.DoSHandler;
import org.eclipse.jetty.server.handler.EventsHandler;
import org.eclipse.jetty.server.handler.QoSHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
Expand Down Expand Up @@ -1747,4 +1748,42 @@ public void requestCustomizer() throws Exception
server.start();
// end::requestCustomizer[]
}

public void dosHandler() throws Exception
{
// tag::dosHandler[]
class CatalogHandler extends Handler.Abstract
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
// Implement the catalog application.
callback.succeeded();
return true;
}
}

Server server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);

// Create and configure DoSHandler.
DoSHandler dosHandler = new DoSHandler(
// Identify remote clients by IP address.
DoSHandler.ID_FROM_REMOTE_ADDRESS,
// Allow 50 requests/s per remote client.
new DoSHandler.LeakingBucketTrackerFactory(50),
// When the request rate is exceeded, delay for 10s and then respond with 429.
new DoSHandler.DelayedRejectHandler(10000, -1, new DoSHandler.StatusRejectHandler()),
// Limit the number of remote clients.
5000
);
server.setHandler(dosHandler);

// Protect the catalog application by wrapping CatalogHandler with DoSHandler.
dosHandler.setHandler(new CatalogHandler());

server.start();
// end::dosHandler[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ The module properties are:
include::{jetty-home}/modules/debuglog.mod[tags=documentation]
----

[[dos]]
== Module `dos`

The `dos` module installs the `org.eclipse.jetty.server.handler.DoSHandler` at the root of the `Handler` tree.

The `DoSHandler` limits the rate of requests per remote client, as explained in xref:programming-guide:server/http.adoc#handler-use-dos[this section].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say that there is only so much a server can do to protect itself from DOS and that true solutions are often better implemented in load balancers and other data center network infrastructure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


The module properties are:

----
include::{jetty-home}/modules/dos.mod[tags=documentation]
----

[[eager-content]]
== Module `eager-content`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,8 @@ This is an example of a `QoSHandler` subclass where you can implement a custom p
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=advancedQoSHandler]
----

Note that `QoSHandler` limits the number of _active_ concurrent requests for the whole server; if you want to apply limits for each remote client, see <<handler-use-thread-limit,`ThreadLimitHandler`>> and <<handler-use-dos,`DoSHandler`>>.

[[handler-use-secured]]
==== SecuredRedirectHandler -- Redirect from HTTP to HTTPS

Expand Down Expand Up @@ -1288,10 +1290,36 @@ This allows web applications that use blocking API calls such as `HttpServletReq
The remote IP address can be derived from the network, or from the `Forwarded` (or the now obsolete `X-Forwarded-For`) HTTP header.
The `Forwarded` header is typically present in requests that have been forwarded to Jetty by a reverse proxy such as HAProxy, Nginx, Apache, etc.

// TODO: mention the DoSHandler in Jetty 12.1.x.

Note that `ThreadLimitHandler` is different from xref:handler-use-qos[`QoSHandler`] in that it limits the number of concurrent requests per remote IP address, while `QoSHandler` limits the total number of concurrent requests.

Note also that `ThreadLimitHandler` is different from xref:handler-use-dos[`DoSHandler`] in that it limits the number of concurrent requests per remote IP address, while `DoSHandler` limits the request rate per remote IP address.

[[handler-use-dos]]
==== DoSHandler

`DoSHandler` tracks remote clients (in a pluggable way, by default using the remote IP address), and limits the rate of requests for each remote client, to protect against denial-of-service attacks.

`DoSHandler` extends xref:handler-use-conditional[`ConditionalHandler`], so you may be able to restrict what `DoSHandler` does to only requests that match the conditions (for example, only to `POST` requests, or only for certain request URIs, etc.)

`DoSHandler` allows you to specify how to identify a remote client (by default using the remote IP address, but this is configurable), and the algorithm to use to calculate the rate of requests for each remote client (by default the link:https://en.wikipedia.org/wiki/Leaky_bucket[Leaky Bucket Algorithm]).

If a remote client exceeds the configured request rate, `DoSHandler` forwards the request handling to a configurable `Request.Handler` that allows you to decide how to handle the request.
`DosHandler` provides two such ``Request.Handler``s out of the box:

* `StatusRejectHandler` (the default) that responds with a configurable HTTP status code (by default `429 Too Many Requests`)
* `DelayedRejectHandler` that delays the request handling by a configurable timeout, and then forwards to request handling to another `Request.Handler` (by default `StatusRejectHandler`).
Delaying the request handling is of little cost for the server (since the request is delayed asynchronously), but the attacker would not know whether the request it sent was processed or rejected until the timeout elapses.
Hopefully, the delay forces the attacker to use more resources, and/or reduce the request rate, therefore reducing the attack impact on the server.

Here is a simple example that shows how to use `DoSHandler`:

[,java,indent=0,options=nowrap]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=dosHandler]
----

For other denial-of-service protections, see also <<handler-use-qos,`QoSHandler`>> and <<handler-use-thread-limit,`ThreadLimitHandler`>>.

[[handler-use-servlet]]
=== Servlet API Handlers

Expand Down
23 changes: 18 additions & 5 deletions jetty-core/jetty-server/src/main/config/etc/jetty-dos.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,17 @@
</Arg>
<Arg name="maxTrackers" type="int"><Property name="jetty.dos.maxTrackers" default="-1"/></Arg>
<Arg name="rejectUntracked" type="boolean"><Property name="jetty.dos.rejectUntracked" default="false"/></Arg>

<Call name="includeInetAddressPattern">
<Call name="includeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.dos.include.inet" default="" /></Arg>
<Arg><Property name="jetty.dos.include.method" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeInetAddressPattern">
<Call name="excludeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.dos.exclude.inet" default="" /></Arg>
<Arg><Property name="jetty.dos.exclude.method" default="" /></Arg>
</Call>
</Arg>
</Call>
Expand All @@ -69,6 +68,20 @@
</Call>
</Arg>
</Call>
<Call name="includeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.dos.include.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.dos.exclude.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
</New>
</Arg>
</Call>
Expand Down
30 changes: 20 additions & 10 deletions jetty-core/jetty-server/src/main/config/modules/dos.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ Enables the DosHandler for the server.
[tags]
connector

[depend]
[before]
compression
gzip

[depends]
server

[xml]
etc/jetty-dos.xml

[ini-template]

#tag::documentation[]
## The algorithm to use for obtaining an remote client identifier from a Request: ID_FROM_REMOTE_ADDRESS, ID_FROM_REMOTE_PORT, ID_FROM_REMOTE_ADDRESS_PORT, ID_FROM_CONNECTION
#jetty.dos.id.type=ID_FROM_REMOTE_ADDRESS
#jetty.dos.id.class=org.eclipse.jetty.server.handler.DosHandler
Expand Down Expand Up @@ -48,15 +52,21 @@ etc/jetty-dos.xml
## The status code used to reject requests; or 0 to abort the request; or -1 for a default
#jetty.dos.rejectStatus=429

## List of InetAddress patterns to include
#jetty.dos.include.inet=10.10.10-14.0-128
## A comma-separated list of HTTP methods to include when matching a request.
# jetty.dos.include.method=

## A comma-separated list of HTTP methods to exclude when matching a request.
# jetty.dos.exclude.method=

## List of InetAddressPatterns to exclude
#jetty.dos.exclude.inet=10.10.10-14.0-128
## A comma-separated list of URI path patterns to include when matching a request.
# jetty.dos.include.path=

## List of path patterns to include
#jetty.dos.include.path=/context/*
## A comma-separated list of URI path patterns to exclude when matching a request.
# jetty.dos.exclude.path=

## List of path to exclude
#jetty.dos.exclude.path=/context/*
## A comma-separated list of remote addresses patterns to include when matching a request.
# jetty.dos.include.inet=

## A comma-separated list of remote addresses patterns to exclude when matching a request.
# jetty.dos.exclude.inet=
#end::documentation[]
Original file line number Diff line number Diff line change
Expand Up @@ -185,26 +185,26 @@ protected boolean onConditionsMet(Request request, Response response, Callback c
// Calculate an id for the request (which may be global empty string).
String id = _clientIdFn.apply(request);

// Reject or handle untracked request
// Reject or handle untracked request.
if (id == null)
id = "";

// Obtain a tracker, creating a new one if necessary (and not too many)
// Trackers are removed if CyclicTimeouts#onExpired returns true.
Tracker tracker = _trackers.computeIfAbsent(id, this::newTracker);

// If we have too many trackers, then we will have a null tracker
// If we have too many trackers, then we will have a null tracker.
if (tracker == null)
return _rejectUntracked ? _rejectHandler.handle(request, response, callback) : nextHandler(request, response, callback);

// IS the request allowed by the tracker?
// Is the request allowed by the tracker?
boolean allowed = tracker.onRequest(NanoTime.now());
if (LOG.isDebugEnabled())
LOG.debug("allowed={} {}", allowed, tracker);
if (allowed)
return nextHandler(request, response, callback);

// Otherwise reject the request as it is over rate
// Otherwise reject the request as it is over the rate.
return _rejectHandler.handle(request, response, callback);
}

Expand Down
Loading