Security

Security in JSR-160 remoting is an all-or-nothing option. Either all or none of your MBeans are accessible (except when your application server uses a SecurityManager, but that is not often the case). Jolokia, on the other hand, allows for fine grained security defined in an XML security policy file. It allows for access restrictions on MBean names (or patterns), attributes, operations, source IP address (or a subnet) and type of Jolokia operation.

Policy based security

Access to MBean and to the Jolokia agents in general can be restricted with an XML policy file. This policy can be configured for various parameters and is divided into several sections.

IP based restrictions

Overall access can be granted based on the IP address of an HTTP client. These restrictions are specified within a <remote> section, which contains one or more <host> elements. The source can be given either as an IP address, a host name, or a netmask given in CIDR format (e.g. 10.0.0.0/16 for all clients coming from the 10.0 network). The following allows access from localhost and all clients whose IP addresses start with "10.0". For all other IP addresses access is denied. IPv6 addresses are also supported.

<remote>
  <host>localhost</host>
  <host>10.0.0.0/16</host>
  <host>[::1]</host>
  <host>2001:db8:1::1</host>
  <host>2001:db8:2::/48</host>
</remote>

Starting with Jolokia 2.6.1, the client IP address can be detected even if Jolokia runs behind a reverse proxy. Typical NGinx reverse proxy setup uses:

location ^~ /jolokia {
    proxy_pass http://<internal_ip>:<port>/jolokia;

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # or for the "most external trusted proxy"
    proxy_set_header X-Forwarded-For $remote_addr;
}

Jolokia can obtain the client IP address from the incoming X-Forwarded-For header, but headers like X-Real-IP and Forwarded (from RFC 7239) are also checked. From security perspective, the incoming headers should not be trusted blindly, so Jolokia always checks the actual incoming client IP address (whether it’s a browser address or the address of a reverse proxy).

In a complex scenario, where the actual user connects through multiple reverse proxies, X-Forwarded-For and Forwarded headers may contain a list of "remote addresses" for each hop along the route.

There’s a special configuration option named trustProxyHeaders (defaults to false) which can be used to declare that there’s a trusted reverse proxy which set a correct client (or untrusted reverse proxy) IP address as the "most external known remote address". By default the entire list of addresses is validated and if any of the addresses fails to match a pattern from <remote>/<host> list, the request is rejected.

Commands

This section specifies the Jolokia commands for which access is generally granted. For each command in the list, access can be further restricted within the <deny> part and each command missing in the list, which is forbidden globally, can be selectively enabled for certain MBeans in the <allow> section. If the <commands> section is missing completely, access to all commands is allowed.

All Jolokia commands described in Jolokia Protocol can be used in this section:

read

Reading of MBean attributes

write

Setting of MBean attributes

exec

Execution of JMX operations

list

List the available MBeans along with their supported

search

Searching for MBeans

notification

New in Jolokia 2 Subscribing to and receiving notifications

version

Getting version and server information

In the following example, access is granted to the read, list, search and version command, but not to write, notification and exec operations.

<commands>
  <command>read</command>
  <command>list</command>
  <command>version</command>
  <command>search</command>
</commands>

Allow and deny access to certain MBeans

Within an <allow> section, access to MBeans can be granted regardless of the operations specified in the <commands> section. The reverse is true for the <deny> section: It rejects access to the MBeans specified here. Both sections contain one or more <mbean> elements which have a format like:

<mbean>
  <name>java.lang:type=Memory</name>
  <attribute>*Memory*</attribute>
  <attribute mode="read">Verbose</attribute>
  <operation>gc</operation>
</mbean>

Within the <name> section the name of the MBean is specified. This can be either a complete ObjectName or a MBean pattern containing wildcards. The value given here must conform to the JMX specification for a valid ObjectName. On this MBean (or MBeans if name is a pattern), attributes are specified within one or more <attribute> elements and operations within one or more <operation> elements. The content can also be a pattern, which uses a wildcard *. e.g. <attribute>*</attribute> specifies all attributes on the given MBean. If for an <attribute> element the XML attribute mode="read" is given, then this attribute can be accessed only read-only.

HTTP method restrictions

Finally, access can be restricted based on the HTTP method with which an Jolokia request was received with the <http> element. Method allowed (post or get) are specified with an <method> inner element. The following example restricts the access to POST requests only:

<http>
  <method>post</method>
</http>

It the <http> section is missing completely, any HTTP method can be used.

Cross-Origin Resource Sharing (CORS) restrictions

Jolokia (since version 1.0.3) supports the WHATWG (formerly W3C) specification for Cross-Origin Resource Sharing (also known as "CORS") which allows browser to access resources which are located on a different server than the calling script is loaded from. This specification provides a controlled way to come around the same origin policy. Most contemporary browsers support CORS.

Traditionally, HTML <form> elements were always allowed to send data to other origins. CORS is required when accessing (sending data to and receiving data from) different origins from scripts using fetch() API and XMLHttpRequest API.

Jolokia does not provide any web application even if it provides a JavaScript client that can be used in a web application. What’s also important is that Jolokia may be configured with authentication, but never uses server side sessions to keep the state (including the authentication state).
This means that Jolokia by itself never requires any CORS response headers. Tools like curl, wget or other non-browser HTTP clients are never involved in the CORS protocol.

However Jolokia JavaScript client library can be used in any web application. Because this library uses fetch() API, Jolokia server has to support CORS protocol.

Jolokia provides CORS configuration for two distinct scenarios:

  1. Because <cors> element is part of the policy restrictor, this configuration is used to restrict access to Jolokia from any client (when using <strict-checking>). When the access is not granted, Jolokia returns HTTP 403 status. This is not a real protection when the client like curl can send any Origin header, but it is part of security features when Jolokia is used in the browser. With fetch() and XHR APIs, we can’t really set headers like Origin or Access-Control-Request-Method. Thus in browser environment we can partially restrict access to the server trusting that Origin is a legitimate header.

  2. We also use <cors>/<allow-origin> for actual CORS protocol. In this case, it’s not about restricting access to the server, but about responding with proper Access-Control-* HTTP response headers to let browsers deal with cross-origin requests in a more secure fashion. It’s about protecting the client (browser application) from reading the response of a cross-origin request. CORS response headers allow responses to declare they can be shared with other origins.

Here’s a more detailed explanation of the protection provided by CORS protocol:

Imagine there’s a web application available at http://example.com which uses Jolokia JS client library to access remote Jolokia JVM Agent running at http://jolokia.api.com/jolokia.

The remote Jolokia JVM Agent uses basic authentication, but doesn’t use cookies and sessions. This implies two things:

  • we can’t rely on one of the protection methods against CSRF, which is SameSite=strict cookie policy, because no cookies are involved

  • when the target site has associated (cached) credentials, the credentials are sent with each request to the remote agent

Now imagine a second, malicious web page at http://evil.com. Its JavaScript could try to do:

fetch("http://jolokia.api.com/jolokia/read/java.lang:type=Memory", {
  credentials: "include" // tell the browser to include credentials for cross-origin requests
})
.then(r => r.json())
.then(data => sendToAttacker(data)); // read and steal the response

When the user is properly authenticated in http://example.com page and to http://jolokia.api.com/jolokia API, the credentials for http://jolokia.api.com/jolokia are already associated by the browser with this origin. An attacker cannot steal the credentials, but can force the browser to use them with a crafted request from a malicious page, effectively acting on the user’s behalf without the user being aware of this.

With proper CORS implementation, http://jolokia.api.com/jolokia will not send Access-Control-Allow-Origin: http://evil.com so the malicious page could not get the data value from the response. Even if the response is actually delivered (which may be the most confusing part of CORS protocol), the access to the response is forbidden.

Even if the attacker could trick a real user to navigate to a malicious page which could call the remote Jolokia agent, fetch() API does not allow to replace or remove the Origin header being sent. There’s no way to disable CORS protocol at this level. Unless Jolokia responds with Access-Control-Allow-Origin: http://evil.com, the attacker can not read the data.

And to make the server protected too (remember - CORS is for protecting the client from allowing the data to be stolen), Jolokia may use <strict-checking> option, so the sent Origin (which can not be forged by an attacker) leads to a proper HTTP response with HTTP status code 403 (Forbidden).

To protect against attackers using curl, proper authentication mechanism must be enabled at Jolokia side. CORS won’t help here at all.

By default, when no restrictor is specified, Jolokia allows cross origin access from any host. This can be limited to certain hosts by using <allow-origin> sections within a <cors> sections. These tags can contain the origin URL provided by browsers with the Origin: header literally or a wildcard specification that uses * to match any number of characters. When the browser application is accessible at a non-standard port number, the Origin header will include it, so the patterns used must take this into account.

Since Jolokia 2.1.3 we also support IPv6 origins. These have to be specified with bracket notation like [::ffff:7f00:1].

NOTE

Mind that you can use http://[::ffff:127.0.0.1]/jolokia URI which uses IPv4-mapped IPv6 address, but browser will send Origin: http://[::ffff:7f00:1] header anyway, so this is how the origin should be defined as.

<cors>
  <!-- Allow cross origin access from www.jolokia.org ... -->
  <allow-origin>https://www.jolokia.org</allow-origin>
  <!-- ... and all servers from jmx4perl.org with any protocol and subdomain -->
  <allow-origin>*://*.jmx4perl.org</allow-origin>
  <!-- IP6 addresses are also supported -->
  <allow-origin>*://[::ffff:7f00:1]</allow-origin>
  <!-- Port numbers may also be included -->
  <allow-origin>http://localhost:*</allow-origin>

  <!-- Check for the proper origin on the server side for a form of access control -->
  <strict-checking/>

  <!-- Use if Origin header can use https while the protocol is http (e.g., with TLS proxy) -->
  <ignore-scheme/>
</cors>

Next two subsections provide more details about these two interpretations of "cross origin request handling" by Jolokia. Some information may be repeated, but with more details.

Access restriction

In order to use this configuration for access control, <strict-checking /> has to be specified. If it’s not, there’s no access control based on Origin (or Referer) header performed at the server side.

<allow-origin> elements are used to define patterns for Origins that are allowed to call Jolokia. This is not a generic protection mechanism - we can simply use curl -H’Origin: <any value> or configure any programmatic HTTP client to send any request header.
It may be used for some kind (but still not sufficient) protection for the code running in the browser, where cross-origin requests are always sent with Origin header.

Additionally (because it’s not a security breach), when Origin header is missing, Jolokia takes the origin from the Referer header - this is a proper implementation for requests coming from the browser.

When no <allow-origin> is specified, Jolokia permits access from any origin (which means - for any HTTP requests with any Origin value).

If the option <strict-checking/> is given in this section, then the given patterns are not only used for CORS checking but also every request is checked on the server side whether the Origin: or Referer: header matches one of the given patterns. If neither Origin: nor Referer: is given and strict checking is enabled, then the access is denied. This is useful for protecting against Cross-Site Request Forgery.

Please note that <strict-checking/> might not be good enough because of potential browser bugs which could allow to forge the origin header. Examples of these issues are arbitrary header injection or referer and origin spoofing. User facing application which uses Jolokia has backend should consider to implement additional measures like using the same-site flag on the session cookie.

If the option <ignore-scheme/> is specified (it defaults to false when not used), Origin header using https scheme is not restricted to be used with https protocol only. This option may be required when Jolokia resides behind TLS proxy.

CORS response headers and preflight requests

CORS protocol is about preventing (or allowing) scripts running in one origin to access data from a different origin. Browsers apply Same Origin Policy for some (but not all) requests sent (for example we can use <img src=""> with different origins, but we can’t easily use fetch() API in cross-origin scenario).
It’s all about protecting the client, not the server (where we should protect against Cross-Site Request Forgery attacks).

Because Jolokia includes a JavaScript library, which uses fetch() API, we may want to allow web pages loaded from one origin (a server providing your index.html page) to use it to access remote Jolokia agents.

CORS is about preventing access to data returned by fetch() if there are no proper Access-Control-* headers.
Additionally some requests interpreted as non-simple (with non trivial Content-Type or with additional headers) are sent only after sending an implicit preflight request. Jolokia should never perform authentication on these special requests (that use OPTIONS HTTP method), because browsers never send any credentials with these preflight requests.

Here’s a list of the CORS response headers supported by Jolokia:

  • Access-Control-Allow-Credentials: set to true if Jolokia has authentication enabled

  • Access-Control-Allow-Headers: set to incoming Access-Control-Request-Headers

  • Access-Control-Allow-Methods: set to GET, POST by default, but may return a value specified with <http>/<method>

  • Access-Control-Allow-Origin: set to the incoming Origin header only of check for allowed origins passes (<cors>/<allow-origin>). Mind that with <strict-checking> the CORS request is rejected and CORS response headers are not set

  • Access-Control-Max-Age: set to 2 hours (default for Chrome browsers)

  • Access-Control-Expose-Headers: Jolokia doesn’t set this header at all

Disable listing/searching of selected MBeans

Using <filter> element we can filter out results of list and search operations (even if search parameters include such MBean names). It’s not preventing users to access given MBeans unless specific <deny> element is present, but it may be used to make list/search results shorter.

NOTE

This configuration option is available since Jolokia 2.1.0

Here’s the example:

<filter>
  <mbean>java.lang:type=MemoryPool</mbean>
  <mbean>org.apache.logging.log4j2:*</mbean>
</filter>

The pattern format is not exactly the same as the patter used for javax.management.MBeanServerConnection.queryNames(). Here’s a summary of rules:

  • * is a glob matching any number of characters except :, = or , (that’s specific to the format of ObjectName)

  • when there’s no : in MBean pattern, entire pattern is treated as domain and all MBeans within matching domain are filtered out

  • domain:* is the same as domain pattern and simply means all MBeans within a domain

  • domain:type=SomeType filters out MBeans within domain with type attribute equal to SomeType. For the purpose of MBeanServer.queryNames() the pattern would be domain:type=SomeType,*

  • domain:address=* filters out MBeans within domain with any value of address attribute

  • domain:address=Admin* filters out MBeans within domain with value of address attribute starting with Admin

Jolokia uses the above patterns individually for each ObjectName and uses these methods:

  • javax.management.ObjectName.getDomain() to access the domain

  • javax.management.ObjectName.getKeyPropertyList() and javax.management.ObjectName.getKeyProperty() to filter by attributes

For performance purposes there are some restrictions for using * within a pattern:

  • attribute keys can’t use *

  • there can be only one * within attribute value or domain name (Admin*, *Admin, Adm*in are correct, but *Admi*n* is not)

  • domain:* is simply treated as domain and matches (filters out) all MBeans within a domain

Example for a security policy

The following complete example applies various access restrictions:

  • Access is only allowed for clients coming from localhost

  • Only HTTP Post requests are allowed

  • By default, only read and list requests are allowed.

  • A single exec request is allowed for triggering garbage collection.

  • Read access to the C3P0 connection pool is restricted to forbid fetching the pool’s properties, which in fact contains the DB password as clear text.

  • Access to jdk.management.jfr:type=FlightRecorder MBean is denied (see CVE-2022-41678)

<?xml version="1.0" encoding="utf-8"?>
<restrict>

  <remote>
    <host>127.0.0.1</host>
  </remote>

  <http>
    <method>post</method>
  </http>

  <commands>
    <command>read</command>
    <command>list</command>
  </commands>

  <allow>
    <mbean>
      <name>java.lang:type=Memory</name>
      <operation>gc</operation>
    </mbean>
  </allow>

  <deny>
    <mbean>
      <name>com.mchange.v2.c3p0:type=PooledDataSource,*</name>
      <attribute>properties</attribute>
    </mbean>
    <mbean>
      <name>jdk.management.jfr:type=FlightRecorder</name>
      <attribute>*</attribute>
      <operation>*</operation>
    </mbean>
  </deny>

  <filter>
    <mbean>java.lang:type=MemoryPool</mbean>
    <mbean>org.apache.logging.log4j2:*</mbean>
  </filter>

</restrict>

Policy Location

A great tool which helps in repackaging an agent for inclusion of a jolokia-access.xml policy file is the command line tool jolokia, which comes with the jmx4perl distribution.

But how do the agents lookup the policy file? By default, the agents will lookup for a policy file top-level in the classpath under the name jolokia-access.xml. Hence for the war agent, the policy file must be packaged within the war at WEB-INF/classes/jolokia-access.xml, for all other agents at /jolokia-access.xml. The location can be overwritten with the configuration parameter policyLocation, which has to be set differently depending on the agent type. Please refer to Agents for more details. The value of this init parameter can be any URL which can loaded by the JVM. A special case is an URL with the scheme classpath: which results in a lookup of the policy file within the classpath. As stated above, the default value of this parameter is classpath:/jolokia-access.xml. If a non-classpath URL is provided with this parameter, and the target policy file could not be found then access is completely denied. If a classpath lookup fails then access is globally granted and a warning is given on standard output.

Browser and HTTP Client security

There’s an important aspect of HTTP security worth mentioning. Jolokia Agent is an HTTP server, so it can be used by any HTTP client.

Programmatic HTTP clients (like curl or Apache HTTP Client 5) can be fully controlled by the user/developer. Any HTTP configuration can be used, including any values for any supported (and unsupported) HTTP headers. In order to restrict access for these clients, Jolokia provides means documented in Policy based security section.

Web Browsers should be treated differently and there’s one important reason - developers creating web pages using HTML, CSS and JavaScript do not have full control over the HTTP headers being sent to the server.

This section is added simply because sometimes it’s easiest to access the Jolokia agent by pasting a GET URL into the browser.

Let’s just highlight two important concepts.

Forbidden request headers

When using fetch() API or xhr API, we can set various HTTP headers to be sent with the requests, but we can’t set some forbidden ones. It simply means that some may be treated as trusted (or coming from non-browser clients).

Secure browser contexts

Without going into details and special cases, there are 3 kinds of URLs (sites, origins) that make the context secure or not:

  • https:// addresses are secure

  • localhost addresses are secure

  • non-localhost http:// addresses (IP or name) are not secure.

Security context may limit the availability of some browser APIs (like crypto API) and affect the headers that are sent together with fetch() requests.

Fetch Metadata

Starting from Jolokia 2.6.1, Jolokia handles (by default, but it can be turned off using useFetchMetadata global option) the set of HTTP headers known as Fetch Metadata. There are 4 headers that modern browsers send in secure contexts:

  • Sec-Fetch-Site - indicates a relation between the origin of the page that initiates the request (through CSS urls, <img>, <a> HTML elements or fetch() API calls) and the actual target origin of the request. This allows to detect same or cross origin requests.

  • Sec-Fetch-Mode - indicates a mode of the request, which allows to distinguish between users selecting a bookmarked (or pasted) URL and the browser performing a request to fetch an image for <img> HTML element

  • Sec-Fetch-Dest - more detailed indication of the source of the request. Most importantly empty means the request comes from fetch()/xhr APIS.

  • Sec-Fetch-User - indicates an explicit user action

While this is not a one-stop solution for security, Jolokia can restrict access when it is not a proper one. Definitely Jolokia URLs should not be valid when used beyond fetch() API or direct user navigation.

Jolokia Restrictors

In order to provide fine grained security, Jolokia is using the abstract concept of an Restrictor. It is represented by the Java interface org.jolokia.server.core.service.api.Restrictor and comes with several implementations. The most prominent one is the PolicyRestrictor which is described in Policy based security. This is also the restrictor which is active by default. For special needs, it is possible to provide a custom implementation of this interface for the WAR and OSGi agents. It is recommended to subclass either org.jolokia.server.core.restrictor.AllowAllRestrictor or org.jolokia.server.core.restrictor.DenyAllRestrictor.

For the WAR agent (Jakarta EE Agent (WAR)), a subclass of org.jolokia.server.core.http.AgentServlet should be created which overrides the createRestrictor()

public class RestrictedAgentServlet extends AgentServlet {

  @Override
  protected Restrictor createRestrictor(Configuration pConfig, LogHandler pLogHandler) {
    String policyLocation = pConfig.getConfig(ConfigKey.POLICY_LOCATION);
    return new MyOwnRestrictor(policyLocation, ...);
  }

}

pConfig is a configuration object from which we can get a URL pointing to the policy file, which is either the default value classpath:/jolokia-access.xml or the value specified with the init parameter policyLocation. This servlet can then be easily configured in a custom web.xml the same way as the Jolokia WAR agent.

For programmatic usage there is an even simpler way: AgentServlet provides a constructor which takes an restrictor as argument, so no subclassing is required in this case.

For an OSGi agent (OSGi Agents), org.jolokia.server.core.osgi.OsgiAgentServlet is the proper extension point. It can be subclassed the same way as shown above and allows a restrictor implementation as constructor parameter, too. In contrast to AgentServlet this class is also OSGi exported and can be referenced from other bundles. Additionally, the OSGi agent can also pick up a restrictor as an OSGi service. See OSGi Agents for details.

In Spring Boot, we can simply have a @Bean annotated method that returns an instance of org.jolokia.server.core.service.api.Restrictor and it’ll be used in Spring Boot Actuator Jolokia endpoint:

@Bean
public Restrictor customRestrictor() {
    return new CustomReestrictor(...);
}
This page was built using the Antora default UI. The source code for this UI is licensed under the terms of the MPL-2.0 license. | Copyright © 2010 - 2025 Roland Huß