Predefined Resource Classes
The following classes are reference/demo resources that illustrate common microservice admin patterns. They live in the
unpublished juneau-microservice-examples module (package org.apache.juneau.microservice.examples), not in the
shipped juneau-microservice/juneau-microservice-jetty jars, so they aren't a dependency you pull in directly —
treat them as copy-and-adapt samples:
Protecting the admin resources
ConfigResource, LogsResource and ShutdownResource change state: they rewrite the configuration, delete log files
and stop the process. They have no authentication of their own, and they need a CSRF boundary wherever a browser can
reach them, or any page the user visits could send those writes. AdminBoundary puts all three behind their own
CsrfBoundary (see CSRF protection: loopback vs general).
In a microservice whose root resource mounts them as children, contribute its registration as a bean:
@Bean(name="adminBoundary")
public ServletFilterRegistration adminBoundary() {
return AdminBoundary.registration("", "app.example.com");
}
The first argument is the path the three resources are mounted under ("" at the root, or a path such as "/admin").
The rest are the host names the server is reached under, without ports. A request under any other Host is refused
with 421 HOST_NOT_ALLOWED, reads included; this is the DNS-rebinding defence, and it keeps a page on a rebound name
from reading the configuration or the logs. With no host names, only the loopback names localhost, 127.0.0.1 and
[::1] are answered (on any port, AdminBoundary.LOOPBACK_HOSTS). Host names you give replace the loopback names
rather than adding to them, so list localhost too if you still reach the server that way.
The resources have no HTML forms and no write links, so a write from a browser is always refused with 403 and an
X-Request-Boundary header naming the reason. So is a write that a browser could send cross-site without a CORS
preflight: a urlencoded, multipart or text/plain POST, or a GET claiming another method through ?method= or an
override header. Writes come from the command line and REST clients:
curl -X PUT -H 'Content-Type: text/plain' --data-binary @my.cfg http://localhost:10000/config
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
The resources' generated API pages are refused too when they try a write from the browser; use curl or a REST
client. Reads are plain GETs, including the log actions: GET /logs/view/{path}, GET /logs/parse/{path} and
GET /logs/download/{path}.
The boundary answers only CSRF and DNS rebinding. It does not authenticate, and a direct network caller passes it, so do not expose the admin resources 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). Keep that default: a container set to
accept ambiguous paths could route a request to an admin resource without the filter seeing it.
ShutdownResource stops the process with System.exit by default. To observe a shutdown instead, for example in a
test, construct it with new ShutdownResource(exit), or put a ShutdownResource.Exit bean in the bean store its
parent builds it from.
SampleRootResource mounts ConfigResource with no boundary; register AdminBoundary when you adapt it.
DirectoryResource accepts only the HEAD, OPTIONS, VIEW and DOWNLOAD overrides; its view and download
links are reads, and its listing no longer offers a delete link (use curl -X DELETE). DebugResource's
jetty-thread-dump link is a plain GET that shows the dump; writing the dump file needs a real POST. A link
can't reach either resource's writes, because the router refuses any override of a GET to a write, but uploads,
deletes and the dump file still change state, so put both behind a CSRF boundary if a browser can reach them. A boundary judges ?method=VIEW and
?method=DOWNLOAD links as writes, so behind one, send real VIEW or DOWNLOAD requests instead.
See also
- Predefined classes — the REST base classes these resources build on