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