Class AdminBoundary

java.lang.Object
org.apache.juneau.microservice.examples.AdminBoundary

public final class AdminBoundary extends Object
The CSRF boundary for the admin resources ConfigResource, LogsResource and ShutdownResource.

registration(String, String...) returns a ServletFilterRegistration that puts the three resources behind their own CsrfBoundary. A write from another site's page is refused with 403 and an "X-Request-Boundary" header naming the reason. So is a write that a browser could have sent cross-site without a CORS preflight and that carries no Origin: a urlencoded, multipart or text/plain POST, or a GET claiming another method through ?method= or an override header. Reads are allowed.

The admin resources have no HTML forms or write links, so no page ever carries a token, and a browser write to them always fails. Writes come from REST clients and the command line as requests a browser could not send cross-site without a preflight, and the boundary admits those with no token:

curl -X PUT -H 'Content-Type: text/plain' -d 'newValue' http://localhost:10000/config/MySection/myKey curl -X DELETE http://localhost:10000/logs/old.log curl -X POST -H 'Content-Type: application/json' http://localhost:10000/shutdown

Every request, reads included, must name one of the allowed hosts in its Host header, or it is refused with 421. Unless other host names are given, only the loopback names LOOPBACK_HOSTS are answered, so a page on a rebound DNS name cannot read the configuration or the logs. A server reached under any other name must list that name.

The boundary answers only CSRF and DNS rebinding. It does not authenticate anyone, and a direct network caller passes it. The admin resources have no authentication of their own, so do not expose them on an untrusted network.

The filter is mapped on the request path as the servlet container normalizes it, while Juneau routes on the path segments it receives. The two agree under the container's default URI handling (for Jetty 12, UriCompliance.DEFAULT, which rejects ambiguous paths such as encoded separators and dot segments). A container configured to accept ambiguous paths could route a request to an admin resource without the filter seeing it, so keep the default, and verify any other container the same way.

Example:

In a Juneau microservice whose root resource mounts the three resources as children, contribute the registration as a @Bean:

@Bean(name="adminBoundary") public ServletFilterRegistration adminBoundary() { return AdminBoundary.registration("", "app.example.com"); }

See Also:
Since:
10.0.0
  • Field Details

    • LOOPBACK_HOSTS

      public static final List<String> LOOPBACK_HOSTS
      The host names answered when none are given: localhost, 127.0.0.1 and [::1], on any port.
    • ORDER

      public static final int ORDER
      The order the registration runs at: ahead of authentication, behind anything registered at the very front.
      See Also:
  • Method Details

    • registration

      public static ServletFilterRegistration registration(String prefix, String... allowedHosts)
      A registration of a new CSRF boundary over the three admin resources.

      The registration maps each resource's path both exactly and as a prefix (/config and /config/*, and so on: six patterns), for REQUEST dispatches only, at ORDER. Each call builds its own filter, so the registration never shares an instance with another boundary. The boundary's token is per process; no admin page carries one.

      Parameters:
      prefix - The path the three resources are mounted under: "" when they are children of the root resource, otherwise a path such as "/admin": one or more /-separated segments of letters, digits, ., _, ~ or -, none starting with ., and no trailing /. A prefix the container would not match as written is refused, so the resources are never left uncovered. Must not be null.
      allowedHosts - The host names this server is reached under, without ports. A request to the admin resources under any other Host is refused with 421, reads included: this is the DNS-rebinding defence a server on plain HTTP needs. With none, LOOPBACK_HOSTS applies. The list replaces the loopback names rather than adding to them. Must not be null, and no entry may be null or blank.
      Returns:
      A new registration.
      Throws:
      IllegalArgumentException - If prefix is malformed, or allowedHosts or an entry is null, blank or not a bare host name.