Skip to main content

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:

Predefined Resource ClassesConfigResource - View and modify the external INI config fileDirectoryResource - View and modify file system directoriesLogsResource - View, parse, download and delete log filesSampleRootResource - A sample root resource class to get started fromShutdownResource - Shutdown and/or restart the JVM

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​