This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new d33c89312c2 CAMEL-24554: camel-jolokia-starter - bind to loopback and 
reject non-loopback browser origin
d33c89312c2 is described below

commit d33c89312c26d596411b0a4ec889f0daa8675d82
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 28 13:41:01 2026 +0200

    CAMEL-24554: camel-jolokia-starter - bind to loopback and reject 
non-loopback browser origin
---
 .../camel-jolokia-starter/src/main/doc/intro.adoc  |  4 +-
 .../camel-jolokia-starter/src/main/doc/usage.adoc  |  6 +-
 .../JolokiaComponentAutoConfiguration.java         |  4 +-
 .../springboot/restrictor/CamelRestrictor.java     | 66 ++++++++++++++++++
 .../JolokiaComponentAutoConfigurationTest.java     |  2 +-
 .../springboot/JolokiaComponentOriginTest.java     | 61 +++++++++++++++++
 .../springboot/restrictor/CamelRestrictorTest.java | 79 ++++++++++++++++++++++
 .../modules/ROOT/pages/starters/jolokia.adoc       | 10 ++-
 8 files changed, 226 insertions(+), 6 deletions(-)

diff --git a/components-starter/camel-jolokia-starter/src/main/doc/intro.adoc 
b/components-starter/camel-jolokia-starter/src/main/doc/intro.adoc
index 6de559101cb..0d1fa1ed47d 100644
--- a/components-starter/camel-jolokia-starter/src/main/doc/intro.adoc
+++ b/components-starter/camel-jolokia-starter/src/main/doc/intro.adoc
@@ -2,4 +2,6 @@ Spring Boot auto-configuration for Camel Jolokia integration.
 
 The Jolokia Starter integrates https://jolokia.org/[Jolokia] agent 
configuration in Spring Boot, wrapping the 
https://jolokia.org/reference/html/manual/spring.html[Jolokia Spring Support] 
with default configurations to let the application work out-of-the-box without 
manually declaring Jolokia servers.
 
-This starter can be considered an alternative to the 
https://jolokia.org/reference/html/manual/agents.html[Jolokia JVM agent]. When 
enabled, it exposes the Jolokia endpoint at `http://0.0.0.0:8778/jolokia`.
+This starter can be considered an alternative to the 
https://jolokia.org/reference/html/manual/agents.html[Jolokia JVM agent]. When 
enabled, it exposes the Jolokia endpoint at `http://127.0.0.1:8778/jolokia`. 
The agent ships no
+authenticator, so it binds to loopback by default; exposing it beyond the host 
is a conscious step via
+`camel.component.jolokia.server-config.host`, and should be paired with 
authentication or a network policy.
diff --git a/components-starter/camel-jolokia-starter/src/main/doc/usage.adoc 
b/components-starter/camel-jolokia-starter/src/main/doc/usage.adoc
index e8aa20ee568..151e261acfb 100644
--- a/components-starter/camel-jolokia-starter/src/main/doc/usage.adoc
+++ b/components-starter/camel-jolokia-starter/src/main/doc/usage.adoc
@@ -13,6 +13,10 @@ camel.component.jolokia.server-config.discoveryEnabled=true
 
 To avoid exposing all JMX MBeans (see 
https://jolokia.org/reference/html/manual/security.html[Security] 
considerations), a default Jolokia 
https://jolokia.org/reference/html/manual/security.html#security-restrictor[Restrictor]
 is provided that allows only Camel related data and some basic information 
from Java.
 
+The restrictor limits which MBeans are reachable, not what may be done to 
them: within the allowed domains, reading and writing attributes and invoking 
operations are all permitted, since managing Camel through Jolokia (starting 
and stopping routes from Hawtio, for example) is what the starter is for. That 
capability is why the agent binds to loopback by default. Browser requests from 
origins outside loopback are rejected, so a remote page the user visits cannot 
drive the agent.
+
+If the endpoint is exposed beyond the host, put authentication or a network 
policy in front of it, or supply a stricter restrictor.
+
 You can disable the restrictor with 
`camel.component.jolokia.use-camel-restrictor=false` or use your own custom one 
with 
`camel.component.jolokia.server-config.restrictorClass=org.example.MyRestrictor`.
 
 An example to extend the provided restrictor:
@@ -71,7 +75,7 @@ logging.level.org.jolokia=TRACE
 
 === Kubernetes Support
 
-The starter provides default configurations for Kubernetes environments. It 
checks for the existence of a certification authority file at 
`/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` and, if present, 
initializes the server using TLS protocol and client authentication. The 
endpoint becomes `https://0.0.0.0:8778/jolokia`.
+The starter provides default configurations for Kubernetes environments. It 
checks for the existence of a certification authority file at 
`/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` and, if present, 
initializes the server using TLS protocol and client authentication. The 
endpoint becomes `https://127.0.0.1:8778/jolokia`; set 
`camel.component.jolokia.server-config.host` to expose it to the cluster.
 
 You can disable this behaviour with 
`camel.component.jolokia.kubernetes-discover=false`.
 
diff --git 
a/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfiguration.java
 
b/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfiguration.java
index 84ee8eade77..dc46bf78d14 100644
--- 
a/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfiguration.java
+++ 
b/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfiguration.java
@@ -84,7 +84,9 @@ public class JolokiaComponentAutoConfiguration {
        public SpringJolokiaConfigHolder camelConfigHolder() {
                LOG.debug("jolokia configuration from properties");
                final SpringJolokiaConfigHolder springJolokiaConfigHolder = new 
SpringJolokiaConfigHolder();
-               setDefaultConfigValue("host", "0.0.0.0");
+               // bind to loopback like the Jolokia JVM agent does; the agent 
ships no authenticator, so exposing it
+               // beyond the host has to be a conscious step via 
camel.component.jolokia.server-config.host
+               setDefaultConfigValue("host", "127.0.0.1");
                setDefaultConfigValue("autoStart", "true");
                if (configuration.isUseCamelRestrictor()
                                && 
!configuration.getServerConfig().containsKey(ConfigKey.RESTRICTOR_CLASS.getKeyValue()))
 {
diff --git 
a/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictor.java
 
b/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictor.java
index 62d2fea82c9..2dfe0c44363 100644
--- 
a/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictor.java
+++ 
b/components-starter/camel-jolokia-starter/src/main/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictor.java
@@ -20,11 +20,16 @@ import 
org.jolokia.server.core.restrictor.AllowAllRestrictor;
 
 import javax.management.ObjectName;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.List;
 import java.util.function.Function;
 
 public class CamelRestrictor extends AllowAllRestrictor {
 
+       private static final String IPV6_LOOPBACK_COMPRESSED = "::1";
+       private static final String IPV6_LOOPBACK_EXPANDED = "0:0:0:0:0:0:0:1";
+
        private final List<String> allowedDomains = List.of("org.apache.camel", 
"java.lang", "java.nio", "jboss.threads");
 
        private Function<ObjectName, Boolean> objectNameEvaluator =
@@ -50,6 +55,67 @@ public class CamelRestrictor extends AllowAllRestrictor {
                return !objectNameEvaluator.apply(name);
        }
 
+       /**
+        * Rejects browser requests from non-loopback origins.
+        * <p/>
+        * {@link AllowAllRestrictor} permits every origin, which leaves the 
agent open to being driven by a remote page
+        * the user happens to visit - the port being on loopback is no 
protection against that. Requests from loopback
+        * origins are allowed so local browser clients such as Hawtio continue 
to work. A request that carries no Origin
+        * or Referer header is also allowed, so ordinary non-browser clients 
such as curl and the Jolokia CLI are
+        * unaffected. This follows the loopback-origin policy used by the 
Camel Quarkus Jolokia restrictor.
+        *
+        * @param  pOrigin         the Origin or Referer header of the request, 
or <tt>null</tt> when absent
+        * @param  pOnlyWhenStrictCheckingIsEnabled whether Jolokia asks to 
apply the check only in strict mode
+        * @return                 <tt>true</tt> if the request may proceed
+        */
+       @Override
+       public boolean isOriginAllowed(String pOrigin, boolean 
pOnlyWhenStrictCheckingIsEnabled) {
+               if (pOrigin == null) {
+                       return true;
+               }
+
+               try {
+                       return isLoopbackHost(new URI(pOrigin).getHost());
+               } catch (URISyntaxException e) {
+                       return false;
+               }
+       }
+
+       private static boolean isLoopbackHost(String host) {
+               if (host == null || host.isEmpty()) {
+                       return false;
+               }
+
+               if (host.startsWith("[") && host.endsWith("]")) {
+                       host = host.substring(1, host.length() - 1);
+               }
+
+               if ("localhost".equalsIgnoreCase(host) || 
"localhost.localdomain".equalsIgnoreCase(host)) {
+                       return true;
+               }
+
+               if (IPV6_LOOPBACK_COMPRESSED.equals(host) || 
IPV6_LOOPBACK_EXPANDED.equals(host)) {
+                       return true;
+               }
+
+               String[] octets = host.split("\\.", -1);
+               if (octets.length != 4 || !"127".equals(octets[0])) {
+                       return false;
+               }
+
+               try {
+                       for (int i = 1; i < octets.length; i++) {
+                               int octet = Integer.parseInt(octets[i]);
+                               if (octet < 0 || octet > 255) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               } catch (NumberFormatException e) {
+                       return false;
+               }
+       }
+
        /**
         * Provides the list of allowed domains from JMX.
         * @return List of String, the list of the allowed domains.
diff --git 
a/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfigurationTest.java
 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfigurationTest.java
index 59d1f2bd19c..36821028d58 100644
--- 
a/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfigurationTest.java
+++ 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentAutoConfigurationTest.java
@@ -76,7 +76,7 @@ public class JolokiaComponentAutoConfigurationTest extends 
JolokiaComponentTestB
                                .extracting("configHolder").isNotNull()
                                .extracting("config")
                                
.asInstanceOf(InstanceOfAssertFactories.map(String.class, String.class))
-                               .containsEntry("host", "0.0.0.0")
+                               .containsEntry("host", "127.0.0.1")
                                .containsEntry("autoStart", "true")
                                .containsEntry("restrictorClass", 
CamelRestrictor.class.getCanonicalName())
                                .containsEntry("discoveryEnabled", "true");
diff --git 
a/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentOriginTest.java
 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentOriginTest.java
new file mode 100644
index 00000000000..f17ee5413f9
--- /dev/null
+++ 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/JolokiaComponentOriginTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.jolokia.springboot;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(classes = { CamelAutoConfiguration.class, 
JolokiaComponentAutoConfiguration.class },
+                  properties = "camel.component.jolokia.serverConfig.port=0")
+class JolokiaComponentOriginTest extends JolokiaComponentTestBase {
+
+    @Test
+    void allowsRequestFromTheJolokiaServersOwnOrigin() throws Exception {
+        String serverOrigin = "http://127.0.0.1:"; + 
agent.getAddress().getPort();
+        URI endpoint = URI.create(serverOrigin + "/jolokia");
+        HttpClient client = HttpClient.newHttpClient();
+
+        HttpResponse<String> commandLineResponse = client.send(
+                HttpRequest.newBuilder(endpoint)
+                        .header("Content-Type", "application/json")
+                        
.POST(HttpRequest.BodyPublishers.ofString("{\"type\":\"version\"}"))
+                        .build(),
+                HttpResponse.BodyHandlers.ofString());
+        assertThat(commandLineResponse.statusCode()).isEqualTo(200);
+        assertThat(commandLineResponse.body()).contains("\"status\":200");
+
+        HttpResponse<String> browserResponse = client.send(
+                HttpRequest.newBuilder(endpoint)
+                        .header("Content-Type", "application/json")
+                        .header("Origin", serverOrigin)
+                        
.POST(HttpRequest.BodyPublishers.ofString("{\"type\":\"version\"}"))
+                        .build(),
+                HttpResponse.BodyHandlers.ofString());
+
+        assertThat(browserResponse.body())
+                .as("a same-origin browser request should not be rejected")
+                .contains("\"status\":200");
+    }
+}
diff --git 
a/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictorTest.java
 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictorTest.java
new file mode 100644
index 00000000000..4da3567d870
--- /dev/null
+++ 
b/components-starter/camel-jolokia-starter/src/test/java/org/apache/camel/component/jolokia/springboot/restrictor/CamelRestrictorTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.jolokia.springboot.restrictor;
+
+import org.junit.jupiter.api.Test;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CamelRestrictorTest {
+
+       private final CamelRestrictor restrictor = new CamelRestrictor();
+
+       private static ObjectName name(String value) throws 
MalformedObjectNameException {
+               return new ObjectName(value);
+       }
+
+       @Test
+       void rejectsCrossOriginBrowserRequests() {
+               // AllowAllRestrictor permits every origin, which lets a page 
the user visits drive the agent -
+               // binding to loopback is no protection against that
+               
assertThat(restrictor.isOriginAllowed("http://evil.example.com";, 
false)).isFalse();
+               
assertThat(restrictor.isOriginAllowed("http://evil.example.com";, 
true)).isFalse();
+       }
+
+       @Test
+       void allowsLoopbackBrowserOrigins() {
+               assertThat(restrictor.isOriginAllowed("http://localhost:8080";, 
false)).isTrue();
+               assertThat(restrictor.isOriginAllowed("http://127.0.0.1:8778";, 
false)).isTrue();
+               
assertThat(restrictor.isOriginAllowed("http://127.255.255.255:8778";, 
false)).isTrue();
+               assertThat(restrictor.isOriginAllowed("http://[::1]:8778";, 
false)).isTrue();
+               
assertThat(restrictor.isOriginAllowed("http://[0:0:0:0:0:0:0:1]:8778";, 
true)).isTrue();
+       }
+
+       @Test
+       void rejectsMalformedAndNonLoopbackOrigins() {
+               assertThat(restrictor.isOriginAllowed("not a URI", 
false)).isFalse();
+               
assertThat(restrictor.isOriginAllowed("http://localhost.example.com";, 
false)).isFalse();
+               assertThat(restrictor.isOriginAllowed("http://128.0.0.1:8778";, 
false)).isFalse();
+       }
+
+       @Test
+       void allowsRequestsCarryingNoOrigin() {
+               // curl and the Jolokia CLI send no Origin or Referer header
+               assertThat(restrictor.isOriginAllowed(null, false)).isTrue();
+               assertThat(restrictor.isOriginAllowed(null, true)).isTrue();
+       }
+
+       @Test
+       void stillLimitsMBeansToTheAllowedDomains() throws Exception {
+               
assertThat(restrictor.isAttributeReadAllowed(name("org.apache.camel:type=context"),
 "CamelId")).isTrue();
+               
assertThat(restrictor.isAttributeReadAllowed(name("com.example:type=Secret"), 
"value")).isFalse();
+               
assertThat(restrictor.isObjectNameHidden(name("com.example:type=Secret"))).isTrue();
+       }
+
+       @Test
+       void managementOfCamelMBeansRemainsPossible() throws Exception {
+               // the starter exists to manage Camel through Jolokia, so 
operations on the Camel domain stay allowed;
+               // that capability is why the agent binds to loopback by default
+               
assertThat(restrictor.isOperationAllowed(name("org.apache.camel:type=context"), 
"stop")).isTrue();
+               
assertThat(restrictor.isOperationAllowed(name("com.example:type=Secret"), 
"reveal")).isFalse();
+       }
+}
diff --git a/docs/spring-boot/modules/ROOT/pages/starters/jolokia.adoc 
b/docs/spring-boot/modules/ROOT/pages/starters/jolokia.adoc
index 37594b9854a..128b3eeb207 100644
--- a/docs/spring-boot/modules/ROOT/pages/starters/jolokia.adoc
+++ b/docs/spring-boot/modules/ROOT/pages/starters/jolokia.adoc
@@ -7,7 +7,9 @@ Spring Boot auto-configuration for Camel Jolokia integration.
 
 The Jolokia Starter integrates https://jolokia.org/[Jolokia] agent 
configuration in Spring Boot, wrapping the 
https://jolokia.org/reference/html/manual/spring.html[Jolokia Spring Support] 
with default configurations to let the application work out-of-the-box without 
manually declaring Jolokia servers.
 
-This starter can be considered an alternative to the 
https://jolokia.org/reference/html/manual/agents.html[Jolokia JVM agent]. When 
enabled, it exposes the Jolokia endpoint at `http://0.0.0.0:8778/jolokia`.
+This starter can be considered an alternative to the 
https://jolokia.org/reference/html/manual/agents.html[Jolokia JVM agent]. When 
enabled, it exposes the Jolokia endpoint at `http://127.0.0.1:8778/jolokia`. 
The agent ships no
+authenticator, so it binds to loopback by default; exposing it beyond the host 
is a conscious step via
+`camel.component.jolokia.server-config.host`, and should be paired with 
authentication or a network policy.
 
 == Maven coordinates
 
@@ -36,6 +38,10 @@ camel.component.jolokia.server-config.discoveryEnabled=true
 
 To avoid exposing all JMX MBeans (see 
https://jolokia.org/reference/html/manual/security.html[Security] 
considerations), a default Jolokia 
https://jolokia.org/reference/html/manual/security.html#security-restrictor[Restrictor]
 is provided that allows only Camel related data and some basic information 
from Java.
 
+The restrictor limits which MBeans are reachable, not what may be done to 
them: within the allowed domains, reading and writing attributes and invoking 
operations are all permitted, since managing Camel through Jolokia (starting 
and stopping routes from Hawtio, for example) is what the starter is for. That 
capability is why the agent binds to loopback by default. Browser requests from 
origins outside loopback are rejected, so a remote page the user visits cannot 
drive the agent.
+
+If the endpoint is exposed beyond the host, put authentication or a network 
policy in front of it, or supply a stricter restrictor.
+
 You can disable the restrictor with 
`camel.component.jolokia.use-camel-restrictor=false` or use your own custom one 
with 
`camel.component.jolokia.server-config.restrictorClass=org.example.MyRestrictor`.
 
 An example to extend the provided restrictor:
@@ -94,7 +100,7 @@ logging.level.org.jolokia=TRACE
 
 === Kubernetes Support
 
-The starter provides default configurations for Kubernetes environments. It 
checks for the existence of a certification authority file at 
`/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` and, if present, 
initializes the server using TLS protocol and client authentication. The 
endpoint becomes `https://0.0.0.0:8778/jolokia`.
+The starter provides default configurations for Kubernetes environments. It 
checks for the existence of a certification authority file at 
`/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` and, if present, 
initializes the server using TLS protocol and client authentication. The 
endpoint becomes `https://127.0.0.1:8778/jolokia`; set 
`camel.component.jolokia.server-config.host` to expose it to the cluster.
 
 You can disable this behaviour with 
`camel.component.jolokia.kubernetes-discover=false`.
 

Reply via email to