This is an automated email from the ASF dual-hosted git repository.
pzampino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 4748771c4 KNOX-3051: Ability to extend classpath with configurable
paths (#971)
4748771c4 is described below
commit 4748771c41a399b72f9cde8c9eea251469053701
Author: hanicz <[email protected]>
AuthorDate: Thu Dec 5 16:51:14 2024 +0100
KNOX-3051: Ability to extend classpath with configurable paths (#971)
* KNOX-3051: Ability to extend classpath with configurable paths
* KNOX-3051: Code cleanup
* KNOX-3051: Rename Extender class, modified Extender to not retain
reference to the properties
---
.../org/apache/knox/gateway/launcher/Command.java | 8 +-
.../launcher/GatewayServerClasspathExtender.java | 75 ++++++++
.../GatewayServerClasspathExtenderTest.java | 176 +++++++++++++++++
.../src/test/resources/gateway-site-test.xml | 210 +++++++++++++++++++++
4 files changed, 467 insertions(+), 2 deletions(-)
diff --git
a/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/Command.java
b/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/Command.java
index f0a3b4945..9cbee6884 100644
---
a/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/Command.java
+++
b/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/Command.java
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.launcher;
import java.io.File;
import java.io.FilenameFilter;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -53,14 +54,17 @@ class Command {
Boolean fork = Boolean.FALSE;
Boolean redirect = Boolean.FALSE; // Controls redirecting stderr to stdout
if forking.
Boolean restream = Boolean.TRUE; // Controls creation of threads to
read/write stdin, stdout, stderr of child if forking.
+ GatewayServerClasspathExtender gatewayServerClasspathExtender;
- Command( File base, Properties config, String[] args ) throws
MalformedURLException {
+ Command( File base, Properties config, String[] args ) throws IOException {
this.base = base;
this.mainArgs = args ;
+ this.gatewayServerClasspathExtender = new GatewayServerClasspathExtender(
base );
consumeConfig( config );
}
- void consumeConfig( Properties config ) throws MalformedURLException {
+ void consumeConfig( Properties config ) throws IOException {
+ gatewayServerClasspathExtender.extendClassPathProperty(config);
mainClass = config.getProperty( MAIN_CLASS );
config.remove( MAIN_CLASS );
mainMethod = config.getProperty( MAIN_METHOD, mainMethod );
diff --git
a/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtender.java
b/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtender.java
new file mode 100644
index 000000000..caae4f530
--- /dev/null
+++
b/gateway-util-launcher/src/main/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtender.java
@@ -0,0 +1,75 @@
+/*
+ * 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.knox.gateway.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GatewayServerClasspathExtender {
+
+ private static final String CLASSPATH_EXTENSION_PROPERTY =
"gateway.server.classpath.extension";
+ private static final String CLASSPATH_PROPERTY_PATTERN =
"<property>\\s*<name>" + CLASSPATH_EXTENSION_PROPERTY +
"</name>\\s*<value>(.*?)</value>\\s*</property>";
+ private static final String CONFIG_FILE = "gateway-site.xml";
+ private static final String CONFIG_PATH = "../conf/" + CONFIG_FILE;
+ private static final String CLASS_PATH_PROPERTY = "class.path";
+ private static final String MAIN_CLASS_PROPERTY = "main.class";
+ private static final String GATEWAY_SERVER_MAIN_CLASS =
"org.apache.knox.gateway.GatewayServer";
+ private static final String[] CLASS_PATH_DELIMITERS = new String[]{",",
";"};
+
+ private final File base;
+ private final Pattern pattern =
Pattern.compile(CLASSPATH_PROPERTY_PATTERN, Pattern.DOTALL);
+
+ public GatewayServerClasspathExtender(File base) {
+ this.base = base;
+ }
+
+ public void extendClassPathProperty(Properties properties) throws
IOException {
+ Path configFilePath = Paths.get(base.getPath(), CONFIG_PATH);
+ if
(GATEWAY_SERVER_MAIN_CLASS.equals(properties.getProperty(MAIN_CLASS_PROPERTY))
&& Files.isReadable(configFilePath)) {
+ String configContent = new
String(Files.readAllBytes(configFilePath), StandardCharsets.UTF_8);
+ extractExtensionPathIntoProperty(configContent, properties);
+ }
+ }
+
+ protected void extractExtensionPathIntoProperty(String configContent,
Properties properties) {
+ final Matcher matcher = pattern.matcher(configContent);
+
+ if (matcher.find()) {
+ StringBuilder newClassPath = new
StringBuilder(matcher.group(1).trim());
+ if (newClassPath.length() > 0) {
+ if (!endsWithDelimiter(newClassPath.toString())) {
+ newClassPath.append(CLASS_PATH_DELIMITERS[1]);
+ }
+
newClassPath.append(properties.getProperty(CLASS_PATH_PROPERTY));
+ properties.setProperty(CLASS_PATH_PROPERTY,
newClassPath.toString());
+ }
+ }
+ }
+
+ private boolean endsWithDelimiter(String path) {
+ return Arrays.stream(CLASS_PATH_DELIMITERS).anyMatch(path::endsWith);
+ }
+}
diff --git
a/gateway-util-launcher/src/test/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtenderTest.java
b/gateway-util-launcher/src/test/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtenderTest.java
new file mode 100644
index 000000000..81dfbff82
--- /dev/null
+++
b/gateway-util-launcher/src/test/java/org/apache/knox/gateway/launcher/GatewayServerClasspathExtenderTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.knox.gateway.launcher;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+
+public class GatewayServerClasspathExtenderTest {
+
+ private Path tempDir;
+ private Path confDir;
+ private Path configFilePath;
+
+ @Before
+ public void setupDirs() throws IOException {
+ tempDir = Files.createTempDirectory("cp_extender_test");
+ confDir = Files.createDirectory(tempDir.resolve("conf"));
+ configFilePath = confDir.resolve("gateway-site.xml");
+ }
+
+ @After
+ public void cleanUpDirs() throws IOException {
+ Files.deleteIfExists(configFilePath);
+ Files.deleteIfExists(confDir);
+ Files.deleteIfExists(tempDir);
+ }
+
+ @Test
+ public void extendClassPathPropertyTest() throws IOException {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ properties.setProperty("main.class",
"org.apache.knox.gateway.GatewayServer");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(confDir.toFile());
+
+ String configContent = this.getConfigContent("/new/classp/*");
+ Files.write(configFilePath,
configContent.getBytes(StandardCharsets.UTF_8));
+ gatewayServerClasspathExtender.extendClassPathProperty(properties);
+
+ assertEquals("/new/classp/*;classpath",
properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extendClassPathPropertyDifferentMainClassTest() throws
IOException {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ properties.setProperty("main.class",
"org.apache.knox.gateway.KnoxCLI");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(confDir.toFile());
+
+ String configContent = this.getConfigContent("/new/classp/*");
+ Files.write(configFilePath,
configContent.getBytes(StandardCharsets.UTF_8));
+ gatewayServerClasspathExtender.extendClassPathProperty(properties);
+
+ assertEquals("classpath", properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyNoDelimTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent = this.getConfigContent("/new/classp/*");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("/new/classp/*;classpath",
properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyXMLFormatTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent = this.getConfigContent("/new/classp/*;");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("/new/classp/*;classpath",
properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyWhitespaceTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent = this.getConfigContent(" /new/classp/*; ");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("/new/classp/*;classpath",
properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyMultipleTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent =
this.getConfigContent("/new/classp/*,../classp");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("/new/classp/*,../classp;classpath",
properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyEmptyTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent = this.getConfigContent("");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("classpath", properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyEmptyWhitespaceTest() {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ String configContent = this.getConfigContent(" ");
+
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent,
properties);
+
+ assertEquals("classpath", properties.getProperty("class.path"));
+ }
+
+ @Test
+ public void extractExtensionPathIntoPropertyNoConfigTest() throws
IOException {
+ Properties properties = new Properties();
+ properties.setProperty("class.path", "classpath");
+ GatewayServerClasspathExtender gatewayServerClasspathExtender = new
GatewayServerClasspathExtender(null);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File file = new
File(classLoader.getResource("gateway-site-test.xml").getFile());
+
+ gatewayServerClasspathExtender.extractExtensionPathIntoProperty(new
String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8), properties);
+
+ assertEquals("classpath", properties.getProperty("class.path"));
+ }
+
+ private String getConfigContent(String extensionValue) {
+ return "<configuration>\n" +
+ " <property>\n" +
+ " <name>gateway.server.classpath.extension</name>\n" +
+ " <value>" + extensionValue + "</value>\n" +
+ " </property>\n" +
+ "</configuration>";
+ }
+}
diff --git a/gateway-util-launcher/src/test/resources/gateway-site-test.xml
b/gateway-util-launcher/src/test/resources/gateway-site-test.xml
new file mode 100644
index 000000000..b442e1998
--- /dev/null
+++ b/gateway-util-launcher/src/test/resources/gateway-site-test.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<configuration>
+
+ <property>
+ <name>gateway.service.alias.impl</name>
+
<value>org.apache.knox.gateway.services.security.impl.RemoteAliasService</value>
+ </property>
+ <property>
+ <name>gateway.port</name>
+ <value>8443</value>
+ <description>The HTTP port for the Gateway.</description>
+ </property>
+
+ <property>
+ <name>gateway.path</name>
+ <value>gateway</value>
+ <description>The default context path for the gateway.</description>
+ </property>
+
+ <property>
+ <name>gateway.gateway.conf.dir</name>
+ <value>deployments</value>
+ <description>The directory within GATEWAY_HOME that contains gateway
topology files and deployments.</description>
+ </property>
+
+ <property>
+ <name>gateway.hadoop.kerberos.secured</name>
+ <value>false</value>
+ <description>Boolean flag indicating whether the Hadoop cluster
protected by Gateway is secured with Kerberos</description>
+ </property>
+
+ <property>
+ <name>java.security.krb5.conf</name>
+ <value>/etc/knox/conf/krb5.conf</value>
+ <description>Absolute path to krb5.conf file</description>
+ </property>
+
+ <property>
+ <name>java.security.auth.login.config</name>
+ <value>/etc/knox/conf/krb5JAASLogin.conf</value>
+ <description>Absolute path to JAAS login config file</description>
+ </property>
+
+ <property>
+ <name>sun.security.krb5.debug</name>
+ <value>false</value>
+ <description>Boolean flag indicating whether to enable debug messages
for krb5 authentication</description>
+ </property>
+
+ <!-- @since 0.10 Websocket configs -->
+ <property>
+ <name>gateway.websocket.feature.enabled</name>
+ <value>false</value>
+ <description>Enable/Disable websocket feature.</description>
+ </property>
+
+ <property>
+ <name>gateway.scope.cookies.feature.enabled</name>
+ <value>false</value>
+ <description>Enable/Disable cookie scoping feature.</description>
+ </property>
+
+ <property>
+ <name>gateway.cluster.config.monitor.ambari.enabled</name>
+ <value>false</value>
+ <description>Enable/disable Ambari cluster configuration
monitoring.</description>
+ </property>
+
+ <property>
+ <name>gateway.cluster.config.monitor.ambari.interval</name>
+ <value>60</value>
+ <description>The interval (in seconds) for polling Ambari for cluster
configuration changes.</description>
+ </property>
+ <!-- @since 2.0.0 WebShell configs -->
+ <!-- must have websocket enabled to use webshell -->
+ <property>
+ <name>gateway.webshell.feature.enabled</name>
+ <value>false</value>
+ <description>Enable/Disable webshell feature.</description>
+ </property>
+ <property>
+ <name>gateway.webshell.max.concurrent.sessions</name>
+ <value>20</value>
+ <description>Maximum number of total concurrent webshell
sessions</description>
+ </property>
+ <property>
+ <name>gateway.webshell.audit.logging.enabled</name>
+ <value>false</value>
+ <description>[Experimental Feature] Enable/Disable webshell command
audit logging.
+ NOTE: Turning this on might log secrets that might be part of
+ command line arguments, please consider this before turning this
on.</description>
+ </property>
+ <property>
+ <name>gateway.webshell.read.buffer.size</name>
+ <value>1024</value>
+ <description>Web Shell buffer size for reading</description>
+ </property>
+
+ <!-- @since 2.0.0 websocket JWT validation configs -->
+ <property>
+ <name>gateway.websocket.JWT.validation.feature.enabled</name>
+ <value>true</value>
+ <description>Enable/Disable websocket JWT validation at websocket
layer.</description>
+ </property>
+
+ <!-- @since 1.5.0 homepage logout -->
+ <property>
+ <name>knox.homepage.logout.enabled</name>
+ <value>true</value>
+ <description>Enable/disable logout from the Knox
Homepage.</description>
+ </property>
+
+ <!-- @since 2.1.0 KnoxSSO Cookie Invalidation -->
+ <property>
+ <name>gateway.knox.token.management.users.can.see.all.tokens</name>
+ <value>admin</value>
+ <description>A comma separated list of user names who can see all
tokens on the Token Management page</description>
+ </property>
+
+ <!-- @since 1.6.0 token management related properties -->
+ <property>
+ <name>gateway.knox.token.eviction.grace.period</name>
+ <value>0</value>
+ <description>A duration (in seconds) beyond a token’s expiration to
wait before evicting its state. This configuration only applies when
server-managed token state is enabled either in gateway-site or at the topology
level.</description>
+ </property>
+
+ <!-- @since 2.1.0 application path aliases -->
+ <property>
+ <name>gateway.application.path.alias.token-generation</name>
+ <value>tokengen</value>
+ </property>
+
+
+ <!-- Knox Admin related config -->
+ <property>
+ <name>gateway.knox.admin.groups</name>
+ <value>admin</value>
+ </property>
+
+ <!-- DEMO LDAP config for Hadoop Group Provider -->
+ <property>
+ <name>gateway.group.config.hadoop.security.group.mapping</name>
+ <value>org.apache.hadoop.security.LdapGroupsMapping</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.bind.user</name>
+ <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.bind.password</name>
+ <value>guest-password</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.url</name>
+ <value>ldap://localhost:33389</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.base</name>
+ <value></value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.search.filter.user</name>
+
<value>(&(|(objectclass=person)(objectclass=applicationProcess))(cn={0}))</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.search.filter.group</name>
+ <value>(objectclass=groupOfNames)</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.search.attr.member</name>
+ <value>member</value>
+ </property>
+ <property>
+
<name>gateway.group.config.hadoop.security.group.mapping.ldap.search.attr.group.name</name>
+ <value>cn</value>
+ </property>
+ <property>
+ <name>gateway.dispatch.whitelist.services</name>
+
<value>DATANODE,HBASEUI,HDFSUI,JOBHISTORYUI,NODEUI,YARNUI,knoxauth</value>
+ <description>The comma-delimited list of service roles for which the
gateway.dispatch.whitelist should be applied.</description>
+ </property>
+ <property>
+ <name>gateway.dispatch.whitelist</name>
+
<value>^https?:\/\/(localhost|127\.0\.0\.1|0:0:0:0:0:0:0:1|::1):[0-9].*$</value>
+ <description>The whitelist to be applied for dispatches associated
with the service roles specified by gateway.dispatch.whitelist.services.
+ If the value is DEFAULT, a domain-based whitelist will be derived from
the Knox host.</description>
+ </property>
+ <property>
+ <name>gateway.xforwarded.header.context.append.servicename</name>
+ <value>LIVYSERVER</value>
+ <description>Add service name to x-forward-context header for the list
of services defined above.</description>
+ </property>
+</configuration>