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

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


The following commit(s) were added to refs/heads/main by this push:
     new 6a5b442c9c disable remote datasource lookup by default
6a5b442c9c is described below

commit 6a5b442c9cf0d69cfd9e695b989a09aa79a878b1
Author: Markus Jung <[email protected]>
AuthorDate: Sat Sep 5 09:49:44 2026 +0200

    disable remote datasource lookup by default
---
 docs/ejbd-transport.adoc                           |  26 ++++
 docs/properties-listing.adoc                       |  10 ++
 .../openejb/server/ejbd/JndiRequestHandler.java    |   6 +
 .../apache/openejb/server/ejbd/AppClientTest.java  |  99 ++++++++-----
 .../server/ejbd/RemoteDataSourceLookupTest.java    | 157 +++++++++++++++++++++
 5 files changed, 259 insertions(+), 39 deletions(-)

diff --git a/docs/ejbd-transport.adoc b/docs/ejbd-transport.adoc
index 93c216b0b9..6642cdf15f 100644
--- a/docs/ejbd-transport.adoc
+++ b/docs/ejbd-transport.adoc
@@ -41,6 +41,32 @@ servlet definition in your `web.xml` and set the url mapping 
to what you
 want (let's say /foo/*). Then use the provider url
 http://<host>:<port>/<webapp context name>/foo
 
+== Remote datasource lookup
+
+Remote datasource lookup is disabled by default. Remote EJB invocations and
+server-side datasource use are unaffected. Clients that previously looked up a
+datasource remotely now receive a naming exception instead of connection 
details.
+
+Administrators who need this feature can set the following server system 
property
+(for example in `conf/system.properties`):
+
+[source,properties]
+----
+openejb.ejbd.datasource-metadata = true
+----
+
+This restores remote datasource lookup, including datasource references. The
+client receives connection details, including database credentials, and opens
+its own JDBC connection. This is a server-wide opt-in, not a per-user or
+per-datasource permission. EJB method authorization does not restrict these
+lookups. Enable it only when all clients able to reach the remote endpoint are
+trusted to receive the exposed database credentials.
+
+The setting applies to both native EJBd and EJBd over HTTP/HTTPS. HTTPS 
protects
+the connection in transit; it does not prevent the client from receiving the
+credentials. Applications that only need remote business operations should
+continue using remote EJB methods with datasource access performed on the 
server.
+
 == Remote communication and serialization
 
 Remotely calling EJBs, independent of using Ejbd or other RMI/IIOP based
diff --git a/docs/properties-listing.adoc b/docs/properties-listing.adoc
index 045e819117..cd26b9fc71 100644
--- a/docs/properties-listing.adoc
+++ b/docs/properties-listing.adoc
@@ -19,6 +19,16 @@ bool
 
 activate or not the remote services when available
 
+openejb.ejbd.datasource-metadata
+
+boolean (default: false)
+
+Allow remote datasource lookup over EJBd, including HTTP/HTTPS. Enabling this
+shares database connection details, including credentials, with clients able to
+reach the endpoint. Applies server-wide, including datasource references; it
+does not add per-user authorization. See link:ejbd-transport.html[Ejbd 
Transport]
+for compatibility and configuration details.
+
 .bind, <service prefix>.port, <service prefix>.disabled, <service
 prefix>.threads
 
diff --git 
a/server/openejb-ejbd/src/main/java/org/apache/openejb/server/ejbd/JndiRequestHandler.java
 
b/server/openejb-ejbd/src/main/java/org/apache/openejb/server/ejbd/JndiRequestHandler.java
index dff0fb1f8d..671fe74831 100644
--- 
a/server/openejb-ejbd/src/main/java/org/apache/openejb/server/ejbd/JndiRequestHandler.java
+++ 
b/server/openejb-ejbd/src/main/java/org/apache/openejb/server/ejbd/JndiRequestHandler.java
@@ -270,6 +270,12 @@ class JndiRequestHandler extends RequestHandler {
             } else if (object == null) {
                 throw new NullPointerException("lookup of '" + name + "' 
returned null");
             } else if (object instanceof DataSource) {
+                // Both connection metadata and references can expose database 
credentials.
+                if 
(!SystemInstance.get().getOptions().get("openejb.ejbd.datasource-metadata", 
false)) {
+                    throw new NamingException("Remote DataSource lookup is 
disabled. An administrator can enable it with "
+                        + "openejb.ejbd.datasource-metadata=true to share 
database connection details, including credentials, "
+                        + "with remote clients.");
+                }
                 if (DataSourceFactory.knows(object)) {
                     try {
                         final DbcpDataSource cf = new DbcpDataSource(object);
diff --git 
a/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/AppClientTest.java
 
b/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/AppClientTest.java
index 1adfa66611..34b7412dc3 100644
--- 
a/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/AppClientTest.java
+++ 
b/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/AppClientTest.java
@@ -41,6 +41,7 @@ import jakarta.ejb.Remote;
 import jakarta.ejb.RemoteHome;
 import javax.naming.Context;
 import javax.naming.InitialContext;
+import javax.naming.NamingException;
 import javax.sql.DataSource;
 import java.rmi.RemoteException;
 import java.util.Properties;
@@ -51,10 +52,19 @@ import java.util.Properties;
 public class AppClientTest extends TestCase {
 
     public void test() throws Exception {
+        checkClient(true);
+    }
+
+    public void testWithoutRemoteDataSources() throws Exception {
+        checkClient(false);
+    }
+
+    private void checkClient(final boolean remoteDataSources) throws Exception 
{
 
         final EjbServer ejbServer = new EjbServer();
 
         final Properties initProps = new Properties();
+        initProps.setProperty("openejb.ejbd.datasource-metadata", 
Boolean.toString(remoteDataSources));
         initProps.setProperty("openejb.deployments.classpath.include", "");
         
initProps.setProperty("openejb.deployments.classpath.filter.descriptors", 
"true");
         OpenEJB.init(initProps, new ServerFederation());
@@ -64,60 +74,71 @@ public class AppClientTest extends TestCase {
         final ServiceDaemon serviceDaemon = new ServiceDaemon(pool, 0, 
"localhost");
         serviceDaemon.start();
 
-        int port = serviceDaemon.getPort();
-
-        final Assembler assembler = 
SystemInstance.get().getComponent(Assembler.class);
-        final ConfigurationFactory config = new ConfigurationFactory();
+        try {
+            int port = serviceDaemon.getPort();
 
-        final EjbModule ejbModule = new EjbModule(new EjbJar("testejbmodule"), 
new OpenejbJar());
-        final EjbJar ejbJar = ejbModule.getEjbJar();
-        ejbJar.addEnterpriseBean(new StatelessBean(Orange.class));
+            final Assembler assembler = 
SystemInstance.get().getComponent(Assembler.class);
+            final ConfigurationFactory config = new ConfigurationFactory();
 
-        final ClassLoader loader = this.getClass().getClassLoader();
+            final EjbModule ejbModule = new EjbModule(new 
EjbJar("testejbmodule"), new OpenejbJar());
+            final EjbJar ejbJar = ejbModule.getEjbJar();
+            ejbJar.addEnterpriseBean(new StatelessBean(Orange.class));
 
-        final ClientModule clientModule = new ClientModule(new 
ApplicationClient(), loader, "orange-client", OrangeAppClient.class.getName(), 
"orange-client");
+            final ClassLoader loader = this.getClass().getClassLoader();
 
-        final AppModule appModule = new AppModule(loader, "testapp");
+            final ClientModule clientModule = new ClientModule(new 
ApplicationClient(), loader, "orange-client", OrangeAppClient.class.getName(), 
"orange-client");
 
-        appModule.getClientModules().add(clientModule);
-        appModule.getEjbModules().add(ejbModule);
+            final AppModule appModule = new AppModule(loader, "testapp");
 
-        assembler.createApplication(config.configureApplication(appModule));
+            appModule.getClientModules().add(clientModule);
+            appModule.getEjbModules().add(ejbModule);
 
-        final Properties props = new Properties();
-        props.put("java.naming.factory.initial", 
"org.apache.openejb.client.RemoteInitialContextFactory");
-        props.put("java.naming.provider.url", "ejbd://127.0.0.1:" + port);
-        props.put("openejb.client.moduleId", "orange-client");
+            
assembler.createApplication(config.configureApplication(appModule));
 
-        Context context = new InitialContext(props);
+            final Properties props = new Properties();
+            props.put("java.naming.factory.initial", 
"org.apache.openejb.client.RemoteInitialContextFactory");
+            props.put("java.naming.provider.url", "ejbd://127.0.0.1:" + port);
+            props.put("openejb.client.moduleId", "orange-client");
 
-        final Object home = context.lookup("comp/env/home");
-        assertTrue(home instanceof OrangeHome);
+            Context context = new InitialContext(props);
 
-        OrangeHome orangeHome = (OrangeHome) home;
-        final OrangeRemote orangeRemote = orangeHome.create();
-        assertEquals("bat", orangeRemote.echo("tab"));
+            final Object home = context.lookup("comp/env/home");
+            assertTrue(home instanceof OrangeHome);
 
-        final Object business = context.lookup("comp/env/business");
-        assertTrue(business instanceof OrangeBusinessRemote);
-        OrangeBusinessRemote orangeBusinessRemote = (OrangeBusinessRemote) 
business;
-        assertEquals("nap", orangeBusinessRemote.echo("pan"));
+            OrangeHome orangeHome = (OrangeHome) home;
+            final OrangeRemote orangeRemote = orangeHome.create();
+            assertEquals("bat", orangeRemote.echo("tab"));
 
-        final Object dataSourceObject = context.lookup("comp/env/datasource");
-        assertTrue(dataSourceObject instanceof DataSource);
-        //        DataSource dataSource = (DataSource) dataSourceObject;
-        //        assertEquals("nap", orangeBusinessRemote.echo("pan"));
+            final Object business = context.lookup("comp/env/business");
+            assertTrue(business instanceof OrangeBusinessRemote);
+            OrangeBusinessRemote orangeBusinessRemote = (OrangeBusinessRemote) 
business;
+            assertEquals("nap", orangeBusinessRemote.echo("pan"));
 
-        props.put("openejb.client.moduleId", "openejb/global");
-        context = new InitialContext(props);
+            if (remoteDataSources) {
+                assertTrue(context.lookup("comp/env/datasource") instanceof 
DataSource);
+            } else {
+                try {
+                    context.lookup("comp/env/datasource");
+                    fail("Remote datasource lookup should be disabled");
+                } catch (final NamingException expected) {
+                    assertTrue(expected.getMessage().contains("Remote 
DataSource lookup is disabled"));
+                }
+            }
 
-        final Object global = 
context.lookup("global/testapp/testejbmodule/Orange!" + 
OrangeBusinessRemote.class.getName());
-        assertTrue(global instanceof OrangeBusinessRemote);
-        OrangeBusinessRemote globalOrangeBusinessRemote = 
(OrangeBusinessRemote) global;
-        assertEquals("nap", globalOrangeBusinessRemote.echo("pan"));
+            props.put("openejb.client.moduleId", "openejb/global");
+            context = new InitialContext(props);
 
-        serviceDaemon.stop();
-        OpenEJB.destroy();
+            final Object global = 
context.lookup("global/testapp/testejbmodule/Orange!" + 
OrangeBusinessRemote.class.getName());
+            assertTrue(global instanceof OrangeBusinessRemote);
+            OrangeBusinessRemote globalOrangeBusinessRemote = 
(OrangeBusinessRemote) global;
+            assertEquals("nap", globalOrangeBusinessRemote.echo("pan"));
+        } finally {
+            try {
+                serviceDaemon.stop();
+            } finally {
+                OpenEJB.destroy();
+            }
+        }
     }
 
     public static interface OrangeHome extends EJBHome {
diff --git 
a/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/RemoteDataSourceLookupTest.java
 
b/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/RemoteDataSourceLookupTest.java
new file mode 100644
index 0000000000..9f440f7811
--- /dev/null
+++ 
b/server/openejb-ejbd/src/test/java/org/apache/openejb/server/ejbd/RemoteDataSourceLookupTest.java
@@ -0,0 +1,157 @@
+/**
+ * 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.openejb.server.ejbd;
+
+import org.apache.openejb.OpenEJB;
+import org.apache.openejb.client.Client;
+import org.apache.openejb.client.DataSourceMetaData;
+import org.apache.openejb.client.JNDIRequest;
+import org.apache.openejb.client.JNDIResponse;
+import org.apache.openejb.client.RequestMethodCode;
+import org.apache.openejb.client.ResponseCodes;
+import org.apache.openejb.client.ServerMetaData;
+import org.apache.openejb.core.ServerFederation;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.server.ServiceDaemon;
+import org.apache.openejb.spi.ContainerSystem;
+import org.junit.Test;
+
+import jakarta.resource.Referenceable;
+import javax.naming.Context;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+import javax.sql.DataSource;
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class RemoteDataSourceLookupTest {
+    private static final String OPTION = "openejb.ejbd.datasource-metadata";
+    private static final String PASSWORD = "synthetic-datasource-password";
+
+    @Test
+    public void disabledByDefault() throws Exception {
+        checkLookup(null);
+    }
+
+    @Test
+    public void explicitlyDisabled() throws Exception {
+        checkLookup("false");
+    }
+
+    @Test
+    public void explicitlyEnabled() throws Exception {
+        checkLookup("true");
+    }
+
+    private void checkLookup(final String option) throws Exception {
+        final Properties properties = new Properties();
+        properties.setProperty("openejb.deployments.classpath", "false");
+        properties.setProperty("openejb.jdbc.datasource-creator", "dbcp");
+        properties.setProperty("LookupDS", "new://Resource?type=DataSource");
+        properties.setProperty("LookupDS.JdbcDriver", "org.hsqldb.jdbcDriver");
+        properties.setProperty("LookupDS.JdbcUrl", 
"jdbc:hsqldb:mem:lookup-test");
+        properties.setProperty("LookupDS.UserName", "lookup-user");
+        properties.setProperty("LookupDS.Password", PASSWORD);
+        properties.setProperty("LookupDS.InitialSize", "0");
+        properties.setProperty("LookupDS.JtaManaged", "false");
+        if (option != null) {
+            properties.setProperty(OPTION, option);
+        }
+
+        ServiceDaemon daemon = null;
+        try {
+            OpenEJB.init(properties, new ServerFederation());
+            final Context local = 
SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext();
+            final Object managed = local.lookup("openejb/Resource/LookupDS");
+            assertTrue(managed instanceof DataSource);
+            // Exercise both the server resource fallback and an 
application-client binding.
+            local.bind("openejb/client/lookup-client/comp/env/jdbc", managed);
+            final AtomicBoolean referenceRead = new AtomicBoolean();
+            final DataSource referenceDataSource = (DataSource) 
Proxy.newProxyInstance(getClass().getClassLoader(),
+                new Class<?>[]{DataSource.class, Referenceable.class}, (proxy, 
method, args) -> {
+                    if ("getReference".equals(method.getName())) {
+                        referenceRead.set(true);
+                        final Reference reference = new 
Reference(DataSource.class.getName());
+                        reference.add(new StringRefAddr("password", PASSWORD));
+                        return reference;
+                    }
+                    if ("toString".equals(method.getName())) {
+                        return "ReferenceDataSource";
+                    }
+                    if ("hashCode".equals(method.getName())) {
+                        return System.identityHashCode(proxy);
+                    }
+                    if ("equals".equals(method.getName())) {
+                        return proxy == args[0];
+                    }
+                    throw new UnsupportedOperationException(method.getName());
+                });
+            local.bind("openejb/Resource/ReferenceDS", referenceDataSource);
+
+            final EjbServer server = new EjbServer();
+            server.init(new Properties());
+            daemon = new ServiceDaemon(server, 0, "127.0.0.1");
+            daemon.start();
+            final ServerMetaData remote = new ServerMetaData(new 
URI("ejbd://127.0.0.1:" + daemon.getPort()));
+
+            // No authentication request is sent: the server must enforce the 
option itself.
+            final JNDIResponse resource = lookup(remote, "LookupDS", null);
+            final JNDIResponse application = lookup(remote, "comp/env/jdbc", 
"lookup-client");
+            final JNDIResponse reference = lookup(remote, "ReferenceDS", null);
+            if ("true".equals(option)) {
+                for (final JNDIResponse response : new 
JNDIResponse[]{resource, application}) {
+                    assertEquals(ResponseCodes.JNDI_DATA_SOURCE, 
response.getResponseCode());
+                    final DataSourceMetaData metadata = (DataSourceMetaData) 
response.getResult();
+                    assertEquals("lookup-user", metadata.getDefaultUserName());
+                    assertEquals(PASSWORD, metadata.getDefaultPassword());
+                }
+                assertEquals(String.valueOf(reference.getResult()), 
ResponseCodes.JNDI_REFERENCE, reference.getResponseCode());
+                assertEquals(PASSWORD, ((Reference) 
reference.getResult()).get("password").getContent());
+                assertTrue(referenceRead.get());
+            } else {
+                for (final JNDIResponse response : new 
JNDIResponse[]{resource, application, reference}) {
+                    assertEquals(ResponseCodes.JNDI_NAMING_EXCEPTION, 
response.getResponseCode());
+                }
+                assertFalse(referenceRead.get());
+            }
+            assertEquals(ResponseCodes.JNDI_NOT_FOUND, lookup(remote, 
"MissingLookupDS", null).getResponseCode());
+            assertTrue(local.lookup("openejb/Resource/LookupDS") instanceof 
DataSource);
+        } finally {
+            try {
+                if (daemon != null) {
+                    daemon.stop();
+                }
+            } finally {
+                OpenEJB.destroy();
+            }
+        }
+    }
+
+    private JNDIResponse lookup(final ServerMetaData server, final String 
name, final String module) throws Exception {
+        final JNDIRequest request = new 
JNDIRequest(RequestMethodCode.JNDI_LOOKUP, name);
+        request.setModuleId(module);
+        final JNDIResponse response = new JNDIResponse();
+        Client.request(request, response, server);
+        return response;
+    }
+}

Reply via email to