This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch tomee-10.x in repository https://gitbox.apache.org/repos/asf/tomee.git
commit 5323aefd02d917e7ac0b0c048b18441912a7f59d Author: Richard Zowalla <[email protected]> AuthorDate: Tue Oct 13 08:48:56 2026 +0200 Warn when tomee-maven-plugin sends credentials over plain HTTP (#3078) Deploy and other command goals default to http; make the cleartext credential exposure visible when targeting a remote host. (cherry picked from commit 5f1dbebd64149dc1f784651a0955573d07f68541) --- .../openejb/maven/plugin/AbstractCommandMojo.java | 27 +++++- .../maven/plugin/AbstractCommandMojoTest.java | 97 ++++++++++++++++++++++ 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractCommandMojo.java b/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractCommandMojo.java index 13f06002ee..281b14f0e8 100644 --- a/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractCommandMojo.java +++ b/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractCommandMojo.java @@ -21,6 +21,7 @@ import org.apache.maven.plugins.annotations.Parameter; import javax.naming.Context; import javax.naming.InitialContext; +import java.util.Locale; import java.util.Map; import java.util.Properties; @@ -35,7 +36,9 @@ public abstract class AbstractCommandMojo extends AbstractAddressMojo { protected String ejbdEndpoint; /** - * Flag to force https usage. + * Flag to force https usage. Without it, the command is sent over plain http + * unless only an https port is configured; a warning is logged when credentials + * would then be sent in clear text to a host other than the loopback one. */ @Parameter(property = "tomee-plugin.command-force-https", defaultValue = "false") protected boolean forceHttps; @@ -65,7 +68,9 @@ public abstract class AbstractCommandMojo extends AbstractAddressMojo { final Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory"); - props.put(Context.PROVIDER_URL, providerUrl()); + final String providerUrl = providerUrl(); + props.put(Context.PROVIDER_URL, providerUrl); + warnIfCleartextCredentials(providerUrl); if (user != null) { props.put(Context.SECURITY_PRINCIPAL, user); } @@ -87,7 +92,23 @@ public abstract class AbstractCommandMojo extends AbstractAddressMojo { } } - private String providerUrl() { + void warnIfCleartextCredentials(final String providerUrl) { + if ((user != null || password != null) && providerUrl.startsWith("http://") && !isLoopback(tomeeHost)) { + getLog().warn("Sending credentials over plain http to '" + tomeeHost + "', they can be intercepted. " + + "Configure an https port (tomee-plugin.https) and set tomee-plugin.command-force-https=true."); + } + } + + private static boolean isLoopback(final String host) { + if (host == null) { + return true; + } + final String h = host.toLowerCase(Locale.ENGLISH); + return "localhost".equals(h) || h.endsWith(".localhost") || h.startsWith("127.") + || "::1".equals(h) || "[::1]".equals(h) || "0:0:0:0:0:0:0:1".equals(h); + } + + String providerUrl() { if (forceHttps || (tomeeHttpPort == null && tomeeHttpsPort != null)) { return "https://" + tomeeHost + ":" + tomeeHttpsPort + ejbdEndpoint; } diff --git a/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/AbstractCommandMojoTest.java b/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/AbstractCommandMojoTest.java new file mode 100644 index 0000000000..f37342a20f --- /dev/null +++ b/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/AbstractCommandMojoTest.java @@ -0,0 +1,97 @@ +/* + * 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.maven.plugin; + +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class AbstractCommandMojoTest { + @Test + public void warnsForCredentialsOverHttpToRemoteHost() { + final List<String> warnings = new ArrayList<>(); + final AbstractCommandMojo mojo = mojo("staging.example.com", "8080", null, warnings); + mojo.user = "admin"; + mojo.password = "secret"; + + final String url = mojo.providerUrl(); + assertEquals("http://staging.example.com:8080/tomee/ejb", url); + mojo.warnIfCleartextCredentials(url); + assertEquals(1, warnings.size()); + assertTrue(warnings.get(0).contains("staging.example.com")); + } + + @Test + public void noWarningForLoopbackHost() { + for (final String host : new String[]{"localhost", "127.0.0.1", "::1"}) { + final List<String> warnings = new ArrayList<>(); + final AbstractCommandMojo mojo = mojo(host, "8080", null, warnings); + mojo.user = "admin"; + mojo.password = "secret"; + mojo.warnIfCleartextCredentials(mojo.providerUrl()); + assertTrue(host, warnings.isEmpty()); + } + } + + @Test + public void noWarningWithoutCredentials() { + final List<String> warnings = new ArrayList<>(); + final AbstractCommandMojo mojo = mojo("staging.example.com", "8080", null, warnings); + mojo.warnIfCleartextCredentials(mojo.providerUrl()); + assertTrue(warnings.isEmpty()); + } + + @Test + public void noWarningOverHttps() { + final List<String> warnings = new ArrayList<>(); + final AbstractCommandMojo mojo = mojo("staging.example.com", "8080", "8443", warnings); + mojo.forceHttps = true; + mojo.user = "admin"; + mojo.password = "secret"; + + final String url = mojo.providerUrl(); + assertEquals("https://staging.example.com:8443/tomee/ejb", url); + mojo.warnIfCleartextCredentials(url); + assertTrue(warnings.isEmpty()); + } + + private static AbstractCommandMojo mojo(final String host, final String httpPort, final String httpsPort, + final List<String> warnings) { + final AbstractCommandMojo mojo = new AbstractCommandMojo() { + @Override + public void execute() { + // no-op + } + }; + mojo.tomeeHost = host; + mojo.tomeeHttpPort = httpPort; + mojo.tomeeHttpsPort = httpsPort; + mojo.ejbdEndpoint = "/tomee/ejb"; + mojo.setLog(new SystemStreamLog() { + @Override + public void warn(final CharSequence content) { + warnings.add(content.toString()); + } + }); + return mojo; + } +}
