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;
+ }
+}