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

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/main by this push:
     new df21d0d8e3 fix(jdbc,jms): add command ACLs for the jdbc and jms scopes 
(#2864)
df21d0d8e3 is described below

commit df21d0d8e3d52c5a760bf0d0641048b875e91d3f
Author: JB Onofré <[email protected]>
AuthorDate: Sat Sep 12 07:56:04 2026 +0200

    fix(jdbc,jms): add command ACLs for the jdbc and jms scopes (#2864)
    
    * fix(jdbc,jms): add command ACLs for the jdbc and jms scopes
    
    The jdbc and jms command scopes had no org.apache.karaf.command.acl.*
    configuration. The command guard treats an unmatched command as allowed,
    so any authenticated shell user (viewer role included) could run every
    jdbc:* and jms:* command.
    
    jdbc:ds-create and jms:create store a user-supplied URL into a
    pax-jdbc-config / pax-jms-config factory configuration that is turned
    into a live DataSource / ConnectionFactory. Several JDBC drivers run
    code at connection time based on URL parameters (for instance H2
    INIT=RUNSCRIPT), so creating a datasource is equivalent to arbitrary
    code execution. jdbc:execute / jdbc:query run arbitrary SQL, and
    jms:send / jms:consume / jms:move write to or destructively drain
    broker destinations.
    
    Add an ACL for each scope, provided by the jdbc / jms feature via a
    <config> element under a shell conditional (same mechanism as the kar
    and maven scopes):
    
      jdbc: ds-create, ds-delete, execute, query = admin;
            ds-list, ds-info, ds-factories, tables = viewer
      jms:  create, delete, send, consume, move = admin;
            connectionfactories, info, queues, topics, count, browse = viewer
    
    This is consistent with shell:exec and config:install, which are
    already restricted to admin. Add SSH command security itests for both
    scopes.
    
    * fix(jdbc,jms): grant admin/viewer roles in XATest for jdbc/jms shell 
commands
    
    KarafTestSupport.executeCommand() runs without a Subject when no Principal
    is passed, so XATest was being denied by the new jdbc/jms command ACLs with
    a misleading CommandNotFoundException. XATest is currently @Ignore'd, but
    fix it too so it does not regress once it is re-enabled.
---
 .../enterprise/src/main/feature/feature.xml        | 94 ++++++++++++++++++++++
 .../test/java/org/apache/karaf/itests/XATest.java  | 22 ++---
 .../itests/ssh/JdbcSshCommandSecurityTest.java     | 79 ++++++++++++++++++
 .../itests/ssh/JmsSshCommandSecurityTest.java      | 80 ++++++++++++++++++
 manual/src/main/asciidoc/user-guide/security.adoc  |  4 +
 5 files changed, 270 insertions(+), 9 deletions(-)

diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml 
b/assemblies/features/enterprise/src/main/feature/feature.xml
index 4c375baca4..d375de423d 100644
--- a/assemblies/features/enterprise/src/main/feature/feature.xml
+++ b/assemblies/features/enterprise/src/main/feature/feature.xml
@@ -257,6 +257,52 @@ com.atomikos.icatch.log_base_dir=${karaf.data}/atomikos
         <feature prerequisite="true">pax-jdbc-config</feature>
         <bundle 
dependency="true">mvn:org.osgi/org.osgi.service.jdbc/${org.osgi.service.jdbc.version}</bundle>
         
<bundle>mvn:org.apache.karaf.jdbc/org.apache.karaf.jdbc.core/${project.version}</bundle>
+        <conditional>
+            <condition>shell</condition>
+            <config name="org.apache.karaf.command.acl.jdbc">
+################################################################################
+#
+#    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.
+#
+################################################################################
+
+#
+# This configuration file defines the ACLs for commands in the jdbc subshell
+#
+# For an explanation of the syntax of this file, see the file:
+#   org.apache.karaf.command.acl.system.cfg
+#
+# 'ds-create' stores a user-supplied JDBC URL into a pax-jdbc-config factory
+# configuration that is turned into a live DataSource. Several JDBC drivers run
+# code at connection time based on URL parameters (for instance H2 
INIT=RUNSCRIPT),
+# so creating a datasource is equivalent to arbitrary code execution and is
+# restricted to 'admin', consistent with shell:exec and config:install.
+# 'execute' and 'query' run arbitrary SQL and are restricted to 'admin' as 
well.
+# 'ds-delete' removes a datasource and is an admin operation.
+#
+ds-create = admin
+ds-delete = admin
+execute = admin
+query = admin
+ds-list = viewer
+ds-info = viewer
+ds-factories = viewer
+tables = viewer
+            </config>
+        </conditional>
     </feature>
 
     <feature name="jms" description="JMS service and commands" 
version="${project.version}">
@@ -264,6 +310,54 @@ com.atomikos.icatch.log_base_dir=${karaf.data}/atomikos
         <feature>transaction</feature>
         <bundle 
dependency="true">mvn:jakarta.jms/jakarta.jms-api/3.1.0</bundle>
         
<bundle>mvn:org.apache.karaf.jms/org.apache.karaf.jms.core/${project.version}</bundle>
+        <conditional>
+            <condition>shell</condition>
+            <config name="org.apache.karaf.command.acl.jms">
+################################################################################
+#
+#    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.
+#
+################################################################################
+
+#
+# This configuration file defines the ACLs for commands in the jms subshell
+#
+# For an explanation of the syntax of this file, see the file:
+#   org.apache.karaf.command.acl.system.cfg
+#
+# 'create' stores a user-supplied broker URL into a pax-jms-config factory
+# configuration that is turned into a live ConnectionFactory, so it is 
restricted
+# to 'admin', consistent with jdbc:ds-create and config:install. 'delete' 
removes a
+# connection factory. 'send', 'consume' and 'move' write to / destructively 
read
+# from broker destinations. All of these are restricted to 'admin'. The 
read-only
+# commands require the 'viewer' role.
+#
+create = admin
+delete = admin
+send = admin
+consume = admin
+move = admin
+connectionfactories = viewer
+info = viewer
+queues = viewer
+topics = viewer
+count = viewer
+browse = viewer
+            </config>
+        </conditional>
     </feature>
 
     <feature name="application-without-isolation" description="Provide EBA 
archive support" version="${aries.application.version}">
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/XATest.java 
b/itests/test/src/test/java/org/apache/karaf/itests/XATest.java
index a888018314..4d9d9b3290 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/XATest.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/XATest.java
@@ -18,6 +18,7 @@ package org.apache.karaf.itests;
 
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.itests.util.RunIfRule;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -31,6 +32,7 @@ import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
 import org.osgi.framework.Bundle;
 
+import java.security.Principal;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.LinkedList;
@@ -71,16 +73,16 @@ public class XATest extends BaseTest {
 
     private static final long TIMEOUT_MS = 120_000;
 
-    private String awaitCondition(String command, String expected, String 
description) throws Exception {
+    private String awaitCondition(String command, String expected, String 
description, Principal... roles) throws Exception {
         long deadline = System.currentTimeMillis() + TIMEOUT_MS;
-        String output = executeCommand(command);
+        String output = executeCommand(command, roles);
         while (!output.contains(expected)) {
             if (System.currentTimeMillis() > deadline) {
                 throw new AssertionError("Timeout waiting for " + description
                         + ". Last output: " + output);
             }
             Thread.sleep(500);
-            output = executeCommand(command);
+            output = executeCommand(command, roles);
         }
         return output;
     }
@@ -88,10 +90,12 @@ public class XATest extends BaseTest {
     @Ignore("Camel 2.20.1 requires spring-tx [4.1.0,5.0.0) which is not 
available after spring-legacy removal")
     @Test
     public void test() throws Exception {
+        Principal[] roles = { new RolePrincipal("admin"), new 
RolePrincipal("viewer") };
+
         System.out.println("== Starting Artemis broker == ");
         awaitCondition("log:display", "AMQ221007: Server is now live", 
"Artemis broker to start");
         System.out.println("AMQ221007: Server is now live");
-        System.out.println(executeCommand("jms:info artemis"));
+        System.out.println(executeCommand("jms:info artemis", roles));
 
         System.out.println("== Installing H2 database == ");
         featureService.installFeature("jdbc", NO_AUTO_REFRESH);
@@ -99,13 +103,13 @@ public class XATest extends BaseTest {
         featureService.installFeature("pax-jdbc-pool-transx", NO_AUTO_REFRESH);
 
         System.out.println(" ");
-        String dsList = awaitCondition("jdbc:ds-list", "OK", "H2 datasource to 
become available");
+        String dsList = awaitCondition("jdbc:ds-list", "OK", "H2 datasource to 
become available", roles);
         System.out.println(dsList);
 
         System.out.println("== Creating table in H2 ==");
-        System.out.println(executeCommand("jdbc:execute h2 CREATE TABLE IF NOT 
EXISTS messages (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, message 
VARCHAR(1024) NOT NULL, CONSTRAINT primary_key PRIMARY KEY (id))"));
+        System.out.println(executeCommand("jdbc:execute h2 CREATE TABLE IF NOT 
EXISTS messages (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, message 
VARCHAR(1024) NOT NULL, CONSTRAINT primary_key PRIMARY KEY (id))", roles));
 
-        awaitCondition("jdbc:query h2 select * from messages", "MESSAGE", 
"table creation");
+        awaitCondition("jdbc:query h2 select * from messages", "MESSAGE", 
"table creation", roles);
         System.out.println("== Table created ==");
 
         System.out.println("== Installing Camel route ==");
@@ -124,10 +128,10 @@ public class XATest extends BaseTest {
         System.out.println(routeList);
 
         System.out.println("== Sending a message in Artemis broker that should 
be consumed by Camel route and inserted into the H2 database");
-        System.out.println(executeCommand("jms:send artemis MyQueue 
'the-message'"));
+        System.out.println(executeCommand("jms:send artemis MyQueue 
'the-message'", roles));
 
         String output = awaitCondition("jdbc:query h2 select * from messages", 
"the-message",
-                "message to be inserted into H2");
+                "message to be inserted into H2", roles);
 
         System.out.println(output);
 
diff --git 
a/itests/test/src/test/java/org/apache/karaf/itests/ssh/JdbcSshCommandSecurityTest.java
 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/JdbcSshCommandSecurityTest.java
new file mode 100644
index 0000000000..b9cd23a58f
--- /dev/null
+++ 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/JdbcSshCommandSecurityTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed 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.karaf.itests.ssh;
+
+import java.util.EnumSet;
+
+import org.apache.karaf.features.FeaturesService;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+/**
+ * This test exercises the Shell Command ACL for the jdbc scope commands as 
defined in the
+ * {@code org.apache.karaf.command.acl.jdbc} configuration provided by the 
{@code jdbc} feature.
+ *
+ * <p>{@code jdbc:ds-create} stores an arbitrary JDBC URL that pax-jdbc-config 
turns into a live
+ * datasource - several JDBC drivers run code at connection time based on URL 
parameters (for
+ * instance H2 {@code INIT=RUNSCRIPT}) - so it, together with {@code 
jdbc:ds-delete},
+ * {@code jdbc:execute} and {@code jdbc:query} (arbitrary SQL), is restricted 
to the
+ * {@code admin} role. The read-only commands require the {@code viewer} 
role.</p>
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class JdbcSshCommandSecurityTest extends SshCommandTestBase {
+
+    private static int counter = 0;
+
+    @Test
+    public void testJdbcCommandSecurityViaSsh() throws Exception {
+        // Skip on Windows where PTY output can be garbled,
+        // when upgrading to Junit5, this can be replaced with 
@DisabledOnOs(OS.WINDOWS)
+        Assume.assumeFalse(System.getProperty("os.name", 
"").toLowerCase().contains("win"));
+
+        featureService.installFeature("jdbc", 
EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
+
+        String manageruser = "man" + System.nanoTime() + "_" + counter++;
+        String vieweruser = "view" + System.nanoTime() + "_" + counter++;
+
+        addUsers(manageruser, vieweruser);
+
+        // read-only commands are available to a viewer
+        assertCommand(vieweruser, "jdbc:ds-list", Result.OK);
+        assertCommand(vieweruser, "jdbc:ds-factories", Result.OK);
+
+        // ds-create / ds-delete / execute / query are admin-only: a viewer 
and a manager
+        // must not even see the commands
+        assertCommand(vieweruser, "jdbc:ds-create", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jdbc:ds-delete", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jdbc:execute", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jdbc:query", Result.NOT_FOUND);
+
+        assertCommand(manageruser, "jdbc:ds-create", Result.NOT_FOUND);
+        assertCommand(manageruser, "jdbc:execute", Result.NOT_FOUND);
+        assertCommand(manageruser, "jdbc:query", Result.NOT_FOUND);
+
+        // the admin user can see and run the jdbc commands (a bare invocation 
reports a
+        // missing-argument / missing-driver error, not "Command not found")
+        assertCommand("karaf", "jdbc:ds-list", Result.OK);
+        assertCommand("karaf", "jdbc:ds-factories", Result.OK);
+        assertCommand("karaf", "jdbc:ds-create", Result.OK);
+        assertCommand("karaf", "jdbc:execute", Result.OK);
+        assertCommand("karaf", "jdbc:query", Result.OK);
+        assertCommand("karaf", "jdbc:ds-delete", Result.OK);
+    }
+}
diff --git 
a/itests/test/src/test/java/org/apache/karaf/itests/ssh/JmsSshCommandSecurityTest.java
 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/JmsSshCommandSecurityTest.java
new file mode 100644
index 0000000000..cb6fabf96a
--- /dev/null
+++ 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/JmsSshCommandSecurityTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed 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.karaf.itests.ssh;
+
+import java.util.EnumSet;
+
+import org.apache.karaf.features.FeaturesService;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+/**
+ * This test exercises the Shell Command ACL for the jms scope commands as 
defined in the
+ * {@code org.apache.karaf.command.acl.jms} configuration provided by the 
{@code jms} feature.
+ *
+ * <p>{@code jms:create} stores an arbitrary broker URL that pax-jms-config 
turns into a live
+ * connection factory, and {@code jms:send}, {@code jms:consume} and {@code 
jms:move} write to
+ * or destructively read from broker destinations, so these commands together 
with
+ * {@code jms:delete} are restricted to the {@code admin} role. The read-only 
commands require
+ * the {@code viewer} role.</p>
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class JmsSshCommandSecurityTest extends SshCommandTestBase {
+
+    private static int counter = 0;
+
+    @Test
+    public void testJmsCommandSecurityViaSsh() throws Exception {
+        // Skip on Windows where PTY output can be garbled,
+        // when upgrading to Junit5, this can be replaced with 
@DisabledOnOs(OS.WINDOWS)
+        Assume.assumeFalse(System.getProperty("os.name", 
"").toLowerCase().contains("win"));
+
+        featureService.installFeature("jms", 
EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
+
+        String manageruser = "man" + System.nanoTime() + "_" + counter++;
+        String vieweruser = "view" + System.nanoTime() + "_" + counter++;
+
+        addUsers(manageruser, vieweruser);
+
+        // read-only commands are available to a viewer
+        assertCommand(vieweruser, "jms:connectionfactories", Result.OK);
+
+        // create / delete / send / consume / move are admin-only: a viewer 
and a manager
+        // must not even see the commands
+        assertCommand(vieweruser, "jms:create", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jms:delete", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jms:send", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jms:consume", Result.NOT_FOUND);
+        assertCommand(vieweruser, "jms:move", Result.NOT_FOUND);
+
+        assertCommand(manageruser, "jms:create", Result.NOT_FOUND);
+        assertCommand(manageruser, "jms:send", Result.NOT_FOUND);
+        assertCommand(manageruser, "jms:consume", Result.NOT_FOUND);
+        assertCommand(manageruser, "jms:move", Result.NOT_FOUND);
+
+        // the admin user can see and run the jms commands (a bare invocation 
reports a
+        // missing-argument error, not "Command not found")
+        assertCommand("karaf", "jms:connectionfactories", Result.OK);
+        assertCommand("karaf", "jms:create", Result.OK);
+        assertCommand("karaf", "jms:delete", Result.OK);
+        assertCommand("karaf", "jms:send", Result.OK);
+        assertCommand("karaf", "jms:consume", Result.OK);
+        assertCommand("karaf", "jms:move", Result.OK);
+    }
+}
diff --git a/manual/src/main/asciidoc/user-guide/security.adoc 
b/manual/src/main/asciidoc/user-guide/security.adoc
index a542b88994..7a736b02a1 100644
--- a/manual/src/main/asciidoc/user-guide/security.adoc
+++ b/manual/src/main/asciidoc/user-guide/security.adoc
@@ -506,6 +506,10 @@ By default, Apache Karaf defines the following commands 
ACLs:
 * `etc/org.apache.karaf.command.acl.system.cfg` configuration file defines the 
ACL for `system:*` commands.
  Only the users with `admin` role can execute `system:property` and 
`system:shutdown` commands. Users with `manager` role can call 
`system:start-level` above 100, otherwise `admin` role is required. Also users 
with `viewer` role can obtain the current start-level.
  The other `system:*` commands can be executed by any user.
+* `etc/org.apache.karaf.command.acl.jdbc.cfg` configuration file (provided by 
the `jdbc` feature) defines the ACL for `jdbc:*` commands.
+ As `jdbc:ds-create` stores an arbitrary JDBC URL that pax-jdbc-config turns 
into a live datasource (several drivers run code at connection time, for 
instance H2 `INIT=RUNSCRIPT`), and `jdbc:execute` and `jdbc:query` run 
arbitrary SQL, these commands together with `jdbc:ds-delete` are restricted to 
the users with the `admin` role. The read-only `jdbc:*` commands require the 
`viewer` role.
+* `etc/org.apache.karaf.command.acl.jms.cfg` configuration file (provided by 
the `jms` feature) defines the ACL for `jms:*` commands.
+ As `jms:create` stores an arbitrary broker URL that pax-jms-config turns into 
a live connection factory, and `jms:send`, `jms:consume` and `jms:move` write 
to or destructively read from broker destinations, these commands together with 
`jms:delete` are restricted to the users with the `admin` role. The read-only 
`jms:*` commands require the `viewer` role.
 
 You can change these default ACLs, and add your own ACLs for additional 
command scopes (for instance `etc/org.apache.karaf.command.acl.cluster.cfg` for
 Apache Karaf Cellar, `etc/org.apache.karaf.command.acl.camel.cfg` from Apache 
Camel, ...).

Reply via email to