You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/08/21 07:37:44 UTC
[isis] branch master updated: ISIS-3128: [Security] make it a config option as to whether allow remote access to the H2WebConsole
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new 9fcab9816d ISIS-3128: [Security] make it a config option as to whether allow remote access to the H2WebConsole
9fcab9816d is described below
commit 9fcab9816dac37e0f07ffe3f5c4f47df9cec8694
Author: andi-huber <ah...@apache.org>
AuthorDate: Sun Aug 21 09:37:36 2022 +0200
ISIS-3128: [Security] make it a config option as to whether allow remote
access to the H2WebConsole
- changes default behavior: don't allow
---
.../apache/isis/core/config/IsisConfiguration.java | 27 +++++
.../h2console/ui/services/H2ManagerMenu.java | 16 ++-
.../h2console/ui/webmodule/H2WebServerWrapper.java | 128 +++++++++++++++++++++
.../h2console/ui/webmodule/WebModuleH2Console.java | 51 +++++---
4 files changed, 198 insertions(+), 24 deletions(-)
diff --git a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
index 69cc8d67b3..e408ce2a5f 100644
--- a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
+++ b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
@@ -62,6 +62,7 @@ import org.apache.isis.applib.services.userui.UserMenu;
import org.apache.isis.applib.value.semantics.TemporalValueSemantics.TemporalEditingPattern;
import org.apache.isis.commons.internal.base._NullSafe;
import org.apache.isis.commons.internal.context._Context;
+import org.apache.isis.core.config.IsisConfiguration.Viewer;
import org.apache.isis.core.config.metamodel.facets.DefaultViewConfiguration;
import org.apache.isis.core.config.metamodel.facets.EditingObjectsConfiguration;
import org.apache.isis.core.config.metamodel.facets.PublishingPolicies.ActionPublishingPolicy;
@@ -1732,6 +1733,32 @@ public class IsisConfiguration {
}
}
+ private final Prototyping prototyping = new Prototyping();
+ @Data
+ public static class Prototyping {
+
+ private final H2Console h2Console = new H2Console();
+ @Data
+ public static class H2Console {
+ /**
+ * Whether to allow remote access to the H2 Web-Console,
+ * which is a potential security risk when no web-admin password is set.
+ * <p>
+ * Corresponds to Spring Boot 'spring.h2.console.settings.web-allow-others'.
+ */
+ private boolean webAllowRemoteAccess = false;
+
+ /**
+ * Whether to generate a random password for access to the H2 Web-Console advanced features.
+ * <p>
+ * If a password is generated, it is logged to the logging subsystem (Log4j2).
+ * <p>
+ * Recommended (<code>true</code>) when {@link #isWebAllowRemoteAccess()} is also <code>true</code>.
+ */
+ private boolean generateRandomWebAdminPassword = true;
+ }
+ }
+
private final Viewer viewer = new Viewer();
@Data
diff --git a/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/services/H2ManagerMenu.java b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/services/H2ManagerMenu.java
index 47c58e4813..659eaed5bf 100644
--- a/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/services/H2ManagerMenu.java
+++ b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/services/H2ManagerMenu.java
@@ -18,6 +18,8 @@
*/
package org.apache.isis.testing.h2console.ui.services;
+import java.util.Optional;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -68,13 +70,17 @@ public class H2ManagerMenu {
cssClassFa = "database",
sequence = "500.800")
public LocalResourcePath openH2Console() {
- if(webModule==null) {
- return null;
- }
- return webModule.getLocalResourcePathIfEnabled();
+ return getPathToH2Console().orElse(null);
}
@MemberSupport public boolean hideOpenH2Console() {
- return webModule==null || webModule.getLocalResourcePathIfEnabled()==null;
+ return getPathToH2Console().isEmpty();
+ }
+
+ // -- HELPER
+
+ private Optional<LocalResourcePath> getPathToH2Console() {
+ return Optional.ofNullable(webModule)
+ .map(WebModuleH2Console::getLocalResourcePathIfEnabled);
}
}
diff --git a/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/H2WebServerWrapper.java b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/H2WebServerWrapper.java
new file mode 100644
index 0000000000..3772cd149d
--- /dev/null
+++ b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/H2WebServerWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * 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.isis.testing.h2console.ui.webmodule;
+
+import java.util.function.Consumer;
+
+import org.h2.server.web.ConnectionInfo;
+import org.h2.server.web.WebServer;
+import org.h2.server.web.WebServlet;
+
+import org.apache.isis.commons.internal._Constants;
+import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.commons.internal.reflection._Reflect;
+
+import lombok.NonNull;
+import lombok.SneakyThrows;
+import lombok.val;
+
+/**
+ * Provides programmatic access to otherwise protected H2 {@link WebServer} configuration.
+ */
+public interface H2WebServerWrapper {
+
+ /**
+ * Update the connection information setting.
+ */
+ void setConnectionInfo(ConnectionInfo connectionInfo);
+
+ /**
+ * Whether to allow other computers to connect.
+ */
+ void setAllowOthers(boolean b);
+
+ /**
+ * Whether other computers are allowed to connect.
+ */
+ boolean getAllowOthers();
+
+ /**
+ * Web Admin Password.
+ */
+ void setAdminPassword(String password);
+
+ // -- UTILITY
+
+ @SneakyThrows
+ static void withH2WebServerWrapperDo(
+ final @NonNull WebServlet webServlet,
+ final @NonNull Consumer<H2WebServerWrapper> onConfiguration) {
+ try {
+ val serverWrapper = H2WebServerWrapper.wrap(webServlet);
+ onConfiguration.accept(serverWrapper);
+ } catch (Throwable cause) {
+ // if for any reason wrapping fails, we fail hard to harden against potential security issues
+ throw _Exceptions.unrecoverable(cause, "Unable to customize settings for H2 console");
+ }
+ }
+
+ // -- HELPER
+
+ @SneakyThrows
+ private static H2WebServerWrapper wrap(final @NonNull WebServlet webServlet) {
+ return new H2WebServerWrapper() {
+
+ final WebServer webServer = (WebServer) _Reflect.getFieldOn(
+ WebServlet.class.getDeclaredField("server"),
+ webServlet);
+
+ @SneakyThrows
+ @Override
+ public void setConnectionInfo(final ConnectionInfo connectionInfo) {
+ val updateSettingMethod = WebServer.class.getDeclaredMethod("updateSetting",
+ ConnectionInfo.class);
+ _Reflect.invokeMethodOn(updateSettingMethod, webServer, connectionInfo);
+ }
+
+ @SneakyThrows
+ @Override
+ public void setAllowOthers(boolean b) {
+ val method = WebServer.class.getDeclaredMethod("setAllowOthers",
+ boolean.class);
+ _Reflect.invokeMethodOn(method, webServer, b);
+
+ // just so we verify reflection works
+ _Assert.assertEquals(b, getAllowOthers());
+ }
+
+ @SneakyThrows
+ @Override
+ public boolean getAllowOthers() {
+ val method = WebServer.class.getDeclaredMethod("getAllowOthers",
+ _Constants.emptyClasses);
+ return (boolean)_Reflect.invokeMethodOn(method, webServer,
+ _Constants.emptyObjects)
+ .getValue().get();
+ }
+
+ @SneakyThrows
+ @Override
+ public void setAdminPassword(String password) {
+ val method = WebServer.class.getDeclaredMethod("setAdminPassword",
+ String.class);
+ _Reflect.invokeMethodOn(method, webServer, password);
+ }
+
+ };
+
+ }
+
+
+}
diff --git a/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/WebModuleH2Console.java b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/WebModuleH2Console.java
index dc2afe80f2..ce3d6084cf 100644
--- a/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/WebModuleH2Console.java
+++ b/testing/h2console/ui/src/main/java/org/apache/isis/testing/h2console/ui/webmodule/WebModuleH2Console.java
@@ -25,7 +25,6 @@ import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import org.h2.server.web.ConnectionInfo;
-import org.h2.server.web.WebServer;
import org.h2.server.web.WebServlet;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
@@ -36,10 +35,11 @@ import org.apache.isis.applib.services.inject.ServiceInjector;
import org.apache.isis.applib.value.LocalResourcePath;
import org.apache.isis.commons.collections.Can;
import org.apache.isis.commons.internal.base._Strings;
-import org.apache.isis.commons.internal.reflection._Reflect;
+import org.apache.isis.core.config.IsisConfiguration;
import org.apache.isis.core.config.datasources.DataSourceIntrospectionService;
import org.apache.isis.core.config.datasources.DataSourceIntrospectionService.DataSourceInfo;
import org.apache.isis.core.config.environment.IsisSystemEnvironment;
+import org.apache.isis.core.security.authentication.standard.RandomCodeGenerator;
import org.apache.isis.core.webapp.modules.WebModuleAbstract;
import org.apache.isis.core.webapp.modules.WebModuleContext;
@@ -78,27 +78,32 @@ public class WebModuleH2Console extends WebModuleAbstract {
this.applicable = isPrototyping()
&& isH2MemConnectionUsed(datasourceIntrospector);
- this.localResourcePathIfEnabled = applicable ? new LocalResourcePath(CONSOLE_PATH) : null;
+ this.localResourcePathIfEnabled = applicable
+ ? new LocalResourcePath(CONSOLE_PATH)
+ : null;
}
@Getter
private final String name = "H2Console";
-
@Override
public Can<ServletContextListener> init(final ServletContext ctx) throws ServletException {
registerServlet(ctx, SERVLET_NAME, H2WebServlet.class)
.ifPresent(servletReg -> {
servletReg.addMapping(CONSOLE_PATH + "/*");
- servletReg.setInitParameter("webAllowOthers", "true");
+
+ //[ISIS-3128] presence of "webAllowOthers" is a potential security risk
+ // setting this later based on configuration below ...
+ //servletReg.setInitParameter("webAllowOthers", "true");
+
});
return Can.empty(); // registers no listeners
}
@Override
- public boolean isApplicable(WebModuleContext ctx) {
+ public boolean isApplicable(final WebModuleContext ctx) {
return applicable;
}
@@ -110,6 +115,9 @@ public class WebModuleH2Console extends WebModuleAbstract {
private static String jdbcUrl;
+ @Inject private IsisConfiguration isisConfiguration;
+ @Inject private RandomCodeGenerator randomCodeGenerator;
+
@Override
public void init() {
super.init();
@@ -130,25 +138,30 @@ public class WebModuleH2Console extends WebModuleAbstract {
val webServlet = this;
- try {
-
- val serverField = WebServlet.class.getDeclaredField("server");
- val updateSettingMethod = WebServer.class.getDeclaredMethod("updateSetting",
- ConnectionInfo.class);
-
- val webServer = (WebServer) _Reflect.getFieldOn(serverField, webServlet);
+ H2WebServerWrapper.withH2WebServerWrapperDo(webServlet, h2WebServerWrapper->{
+ h2WebServerWrapper.setConnectionInfo(connectionInfo);
+ h2WebServerWrapper.setAllowOthers(isWebAllowRemoteAccess());
+ if(isGenerateRandomWebAdminPassword()) {
+ val webAdminPass = randomCodeGenerator.generateRandomCode();
+ log.info("webAdminPass: {}", webAdminPass);
+ h2WebServerWrapper.setAdminPassword(webAdminPass);
+ }
+ });
- _Reflect.invokeMethodOn(updateSettingMethod, webServer, connectionInfo);
+ }
- } catch (Exception ex) {
- log.error("Unable to set a custom ConnectionInfo for H2 console", ex);
- }
+ public static void configure(final String jdbcUrl) {
+ H2WebServlet.jdbcUrl = jdbcUrl;
+ }
+ private boolean isWebAllowRemoteAccess() {
+ return isisConfiguration.getPrototyping().getH2Console().isWebAllowRemoteAccess();
}
- public static void configure(String jdbcUrl) {
- H2WebServlet.jdbcUrl = jdbcUrl;
+ private boolean isGenerateRandomWebAdminPassword() {
+ return isisConfiguration.getPrototyping().getH2Console().isGenerateRandomWebAdminPassword();
}
+
}
// -- HELPER