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:
-
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 likecurlcan send anyOriginheader, but it is part of security features when Jolokia is used in the browser. Withfetch()andXHRAPIs, we can’t really set headers likeOriginorAccess-Control-Request-Method. Thus in browser environment we can partially restrict access to the server trusting thatOriginis a legitimate header. -
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 properAccess-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=strictcookie 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]/jolokiaURI which uses IPv4-mapped IPv6 address, but browser will sendOrigin: 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 totrueif Jolokia has authentication enabled -
Access-Control-Allow-Headers: set to incomingAccess-Control-Request-Headers -
Access-Control-Allow-Methods: set toGET, POSTby default, but may return a value specified with<http>/<method> -
Access-Control-Allow-Origin: set to the incomingOriginheader 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 ofObjectName) -
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 asdomainpattern and simply means all MBeans within a domain -
domain:type=SomeTypefilters out MBeans withindomainwithtypeattribute equal toSomeType. For the purpose ofMBeanServer.queryNames()the pattern would bedomain:type=SomeType,* -
domain:address=*filters out MBeans withindomainwith any value ofaddressattribute -
domain:address=Admin*filters out MBeans withindomainwith value ofaddressattribute starting withAdmin
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()andjavax.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*inare correct, but*Admi*n*is not) -
domain:*is simply treated asdomainand 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
readandlistrequests are allowed. -
A single
execrequest 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=FlightRecorderMBean 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
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 orxhrAPI, 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 orfetch()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 importantlyemptymeans the request comes fromfetch()/xhrAPIS. -
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(...);
}