From 399ecccba0a361db79d6ff70ff64a0edd4a73d4f Mon Sep 17 00:00:00 2001
From: Kai Reinhard <K.Reinhard@micromata.de>
Date: Sun, 18 Apr 2021 22:59:09 +0000
Subject: [PATCH] Version 0.5 prepared.

---
 borgbutler-docker/buildDocker.sh                                                        |    2 
 borgbutler-docker/app/entrypoint.sh                                                     |    4 
 /dev/null                                                                               |   87 -----------------
 borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt |   28 +++++
 README.adoc                                                                             |   24 ++++
 borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt      |    2 
 build.gradle                                                                            |    6 
 borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt     |   98 +++++++++++++++++++
 borgbutler-docker/README.adoc                                                           |    6 
 borgbutler-docker/app/Dockerfile                                                        |    2 
 borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt         |    2 
 11 files changed, 163 insertions(+), 98 deletions(-)

diff --git a/README.adoc b/README.adoc
index 92f511a..88b61f8 100644
--- a/README.adoc
+++ b/README.adoc
@@ -69,6 +69,8 @@
 
 You may refer the log file through the web browser or in `$HOME/BorgButler/borgbutler.log`.
 
+For new versions of BorgButler or for changing the running options of your BorgButler container, simply delete the BorgButler docker container by `docker rm borgbutler` and call `docker run` again.
+
 === Starting from Java zip
 You'll need OpenJDK 9+.
 
@@ -139,3 +141,25 @@
 [link=doc/images/screen-logviewer.png]
 image::doc/images/screen-logviewer.png[Log viewer of BorgButler,800]
 
+
+== Trouble shooting
+=== Docker
+==== Increase memory (OutOfMemory)
+1. `docker rm borgbutler`
+2. `docker run -e JAVA_OPTS="-Xmx2G" -v ...`
+If your docker container crashes on heavy usage of large borg archives, check the memory settings of your docker installation.
+
+=== How to download/restore?
+You may restore files or while directories by simply clicking the download icon. If you run BorgButler on localhost as Java process (not docker), after restoring single
+files or directories your system's file browser is opened.
+
+You will find all the restored files in the `restore` subdirectory of your BorgButler home directory.
+
+=== Access to Borg repo fails (lock)
+BorgButler tries to run only one job per repo at the same time. If your log file shows error on `Failed to create/acquire the lock ... lock.exclusive (timeout)` simply restart BorgButler.
+
+
+=== Browsing produces security warnings
+Due to security reasons, BorgButler is only available by the localhost's web browser. For docker
+installations the clients of the private net `172.17.0.*` are allowed.
+For enabling other client ip's, you may use option `-DallowedClientIps=192.168.78.` (docker: `docker run -e JAVA_OPTS="-DallowedeClientIps=192.168.78." -v ...` if your really now what you're doing!!! There is now https available at default!!!
diff --git a/borgbutler-docker/README.adoc b/borgbutler-docker/README.adoc
index 4b79323..4933184 100644
--- a/borgbutler-docker/README.adoc
+++ b/borgbutler-docker/README.adoc
@@ -8,12 +8,12 @@
 ifdef::env-github,env-browser[:outfilesuffix: .adoc]
 
 == Releasing new version
-1. Edit `build.gradle: version=0.4-SNAPSHOT`
-2. Edit `borgbutler-docker/Dockerfile: ARG DEPENDENCY=target/dependency/borgbutler-server-0.4-SNAPSHOT`
+1. Edit `build.gradle: version=0.5`
+2. Edit `borgbutler-docker/Dockerfile: ARG DEPENDENCY=target/dependency/borgbutler-server-0.5`
 
 == Building docker container
 
-1. `gradle clean distZip` in top directory (compiles and builds the project)
+1. `gradle clean dist` in top directory (compiles and builds the project)
 2. `cd borgbutler-docker`
 3. `./buildDocker.sh` (builds the docker container)
 
diff --git a/borgbutler-docker/app/Dockerfile b/borgbutler-docker/app/Dockerfile
index b791fa9..d252cd9 100644
--- a/borgbutler-docker/app/Dockerfile
+++ b/borgbutler-docker/app/Dockerfile
@@ -16,7 +16,7 @@
 USER borgbutler:borgbutler
 
 # Don't put fat jar files in docker images: https://phauer.com/2019/no-fat-jar-in-docker-image/
-ARG DEPENDENCY=target/dependency/borgbutler-server-0.5-SNAPSHOT
+ARG DEPENDENCY=target/dependency/borgbutler-server-0.5
 COPY ${DEPENDENCY}/lib /app/lib
 #COPY ${DEPENDENCY}/META-INF /app/META-INF
 #COPY ${DEPENDENCY}/BOOT-INF/classes /app
diff --git a/borgbutler-docker/app/entrypoint.sh b/borgbutler-docker/app/entrypoint.sh
index 1e92dd7..9afb503 100644
--- a/borgbutler-docker/app/entrypoint.sh
+++ b/borgbutler-docker/app/entrypoint.sh
@@ -47,9 +47,9 @@
 #Trap SIGTERM
 trap cleanup INT SIGTERM
 
-echo "Starting java ${JAVA_OPTS} -cp app/web/*:app/lib/* -DBorgButlerHome=/BorgButler/ -DapplicationHome=/app -DbindAddress=0.0.0.0 -DallowedClientIps=172.17. ${JAVA_MAIN} -q ${JAVA_ARGS}"
+echo "Starting java ${JAVA_OPTS} -cp app/web/*:app/lib/* -DBorgButlerHome=/BorgButler/ -Dserver.address=0.0.0.0 ${JAVA_MAIN} ${JAVA_ARGS}"
 
-java $JAVA_OPTS -cp app/web/*:app/lib/* -DBorgButlerHome=/BorgButler/ -DapplicationHome=/app -Dserver.address=0.0.0.0 -DallowedClientIps=172.17. $JAVA_MAIN -q $JAVA_ARGS &
+java $JAVA_OPTS -cp app/web/*:app/lib/* -DBorgButlerHome=/BorgButler/ -Dserver.address=0.0.0.0 -Ddocker=true $JAVA_MAIN $JAVA_ARGS &
 
 CHILD=$!
 wait $CHILD
diff --git a/borgbutler-docker/buildDocker.sh b/borgbutler-docker/buildDocker.sh
index 7a4f5dd..d451746 100755
--- a/borgbutler-docker/buildDocker.sh
+++ b/borgbutler-docker/buildDocker.sh
@@ -20,4 +20,4 @@
 echo "Run without ssh: 'docker run -v $HOME/BorgButler:/BorgButler -p 127.0.0.1:9042:9042 --name borgbutler kreinhard/borgbutler'"
 echo "Run with ssh: 'docker run -v $HOME/BorgButler:/BorgButler -v $HOME/.ssh:/home/borgbutler/.ssh:ro -p 127.0.0.1:9042:9042 --name borgbutler kreinhard/borgbutler'"
 echo
-echo 'Increase Java memory: docker run -e JAVA_OPTS="-Xms4g -Xmx4g" -v ...'
+echo 'Increase Java memory: docker run -e JAVA_OPTS="-Xmx2g" -v ...'
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
deleted file mode 100644
index 638bf5b..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package de.micromata.borgbutler.server.user;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.*;
-import java.io.IOException;
-
-/**
- * Ensuring the user data inside request threads. For now, it's only a simple implementation (no login required).
- * Only the user's (client's) locale is used.
- * <br>
- * For requests from remote (not localhost) an exception is thrown due to security reasons.
- */
-@Component
-public class UserFilter implements Filter {
-    private Logger log = LoggerFactory.getLogger(UserFilter.class);
-
-    @Override
-    public void init(FilterConfig filterConfig) {
-    }
-
-    @Override
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-        checkClientIp(request);
-        try {
-            UserData userData = UserUtils.getUser();
-            if (userData != null) {
-                log.warn("****************************************");
-                log.warn("***********                   **********");
-                log.warn("*********** SECURITY WARNING! **********");
-                log.warn("***********                   **********");
-                log.warn("*********** Internal error:   **********");
-                log.warn("*********** User already set! **********");
-                log.warn("***********                   **********");
-                log.warn("****************************************");
-                log.warn("Don't deliver this app in dev mode due to security reasons!");
-                String message = "User already given for this request. Rejecting request due to security reasons. Given user: " + userData;
-                log.error(message);
-                throw new IllegalArgumentException(message);
-            }
-            userData = UserManager.instance().getUser("dummy");
-            UserUtils.setUser(userData, request.getLocale());
-            if (log.isDebugEnabled()) log.debug("Request for user: " + userData);
-            //log.info("Request for user: " + userData + ": " + RequestLog.asString((HttpServletRequest) request));
-            chain.doFilter(request, response);
-        } finally {
-            UserUtils.removeUser();
-        }
-    }
-
-    @Override
-    public void destroy() {
-    }
-
-    private void checkClientIp(ServletRequest request) {
-        String remoteAddr = request.getRemoteAddr();
-        boolean allowed = false;
-        String allowedClientIps = System.getProperty("allowedClientIps");
-        if (remoteAddr != null) {
-            if (remoteAddr.equals("127.0.0.1")) {
-                allowed = true;
-            } else {
-                if (allowedClientIps != null && remoteAddr.startsWith(allowedClientIps)) {
-                    allowed = true;
-                }
-            }
-        }
-        if (!allowed) {
-            log.warn("****************************************");
-            log.warn("***********                   **********");
-            log.warn("*********** SECURITY WARNING! **********");
-            log.warn("***********                   **********");
-            log.warn("*********** Externa access:   **********");
-            log.warn("*********** " + remoteAddr + " **********");
-            log.warn("***********                   **********");
-            log.warn("****************************************");
-            if (allowedClientIps == null) {
-                log.warn("Only access from local host yet supported due to security reasons. You may configure client address ranges by -DallowedClientIps=172.17.0.1 or -DallowedClientIps=172.17.");
-            } else {
-                log.warn("Only access from local host and " + allowedClientIps + " (option -DallowedClientIps) yet supported due to security reasons.");
-            }
-            throw new RuntimeException("Server is only available for localhost due to security reasons. A remote access is not yet available.");
-        }
-    }
-}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt
index 95939fe..a009a0c 100644
--- a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt
@@ -12,6 +12,8 @@
     val headlessMode: Boolean = System.getProperty("java.awt.headless") == "true"
     val desktopSupported = !headlessMode && Desktop.isDesktopSupported()
     val desktopSupportsBrowse = desktopSupported && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)
+    @JvmStatic
+    val dockerMode = System.getProperty("docker") == "true"
 
     @JvmStatic
     val userManagement = UserManagement.SINGLE
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt
index 26ff078..f1c4e56 100644
--- a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt
@@ -61,7 +61,7 @@
             if (remoteAddr.contains(",")) {
                 // sometimes the header is of form client ip,proxy 1 ip,proxy 2 ip,...,proxy n ip,
                 // we just want the client
-                remoteAddr = remoteAddr.split(',')[0].trim({ it <= ' ' })
+                remoteAddr = remoteAddr.split(',')[0].trim { it <= ' ' }
             }
             try {
                 // If ip4/6 address string handed over, simply does pattern validation.
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt
new file mode 100644
index 0000000..b7ea64c
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt
@@ -0,0 +1,98 @@
+package de.micromata.borgbutler.server.user
+
+import de.micromata.borgbutler.server.RunningMode.dockerMode
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+import java.io.IOException
+import javax.servlet.*
+
+/**
+ * Ensuring the user data inside request threads. For now, it's only a simple implementation (no login required).
+ * Only the user's (client's) locale is used.
+ * <br></br>
+ * For requests from remote (not localhost) an exception is thrown due to security reasons.
+ */
+@Component
+class UserFilter : Filter {
+    private val log = LoggerFactory.getLogger(UserFilter::class.java)
+    override fun init(filterConfig: FilterConfig) {}
+
+    @Throws(IOException::class, ServletException::class)
+    override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
+        checkClientIp(request)
+        try {
+            var userData = UserUtils.getUser()
+            if (userData != null) {
+                log.warn("****************************************")
+                log.warn("***********                   **********")
+                log.warn("*********** SECURITY WARNING! **********")
+                log.warn("***********                   **********")
+                log.warn("*********** Internal error:   **********")
+                log.warn("*********** User already set! **********")
+                log.warn("***********                   **********")
+                log.warn("****************************************")
+                log.warn("Don't deliver this app in dev mode due to security reasons!")
+                val message =
+                    "User already given for this request. Rejecting request due to security reasons. Given user: $userData"
+                log.error(message)
+                throw IllegalArgumentException(message)
+            }
+            userData = UserManager.instance().getUser("dummy")
+            UserUtils.setUser(userData, request.locale)
+            if (log.isDebugEnabled) log.debug("Request for user: $userData")
+            //log.info("Request for user: " + userData + ": " + RequestLog.asString((HttpServletRequest) request));
+            chain.doFilter(request, response)
+        } finally {
+            UserUtils.removeUser()
+        }
+    }
+
+    override fun destroy() {}
+    private fun checkClientIp(request: ServletRequest) {
+        val remoteAddr = request.remoteAddr
+        if (check(remoteAddr)) {
+            return
+        }
+        log.warn("****************************************")
+        log.warn("***********                   **********")
+        log.warn("*********** SECURITY WARNING! **********")
+        log.warn("***********                   **********")
+        log.warn("*********** External access:  **********")
+        log.warn("*********** $remoteAddr **********")
+        log.warn("***********                   **********")
+        log.warn("****************************************")
+        if (allowedClientIps == null) {
+            log.warn("Only access from local host yet supported due to security reasons. You may configure client address ranges by -DallowedClientIps=172.17.0.1 or -DallowedClientIps=172.17.")
+        } else {
+            log.warn("Only access from local host and '${allowedClientIps.joinToString { it }}' (option -DallowedClientIps) yet supported due to security reasons.")
+        }
+        log.info("Access denied for client with remote address: $remoteAddr")
+        throw RuntimeException("Server is only available for localhost due to security reasons. A remote access is not yet available.")
+    }
+
+    internal fun check(remoteAddr: String?): Boolean {
+        remoteAddr ?: return false
+        if (remoteAddr == "127.0.0.1") {
+            return true
+        }
+        if (dockerMode && remoteAddr.startsWith("172.17.0.")) {
+            // Docker host uses ip address 171.17.0
+            return true
+        }
+        return allowedClientIps?.any { remoteAddr.startsWith(it) } == true
+    }
+
+    private val allowedClientIps =
+        System.getProperty(SYSTEM_PROPERTY_ALLOWED_CLIENT_IPS)?.split(",", ";", ":", " ")
+            ?.filter { it.isNotBlank() && it.indexOf('.') > 0 }?.map { it.trim { it <= ' ' } }
+
+    init {
+        if (!allowedClientIps.isNullOrEmpty()) {
+            log.warn("Configured and allowed client ips are: ${allowedClientIps.joinToString { it }}")
+        }
+    }
+
+    companion object {
+        internal const val SYSTEM_PROPERTY_ALLOWED_CLIENT_IPS = "allowedClientIps"
+    }
+}
diff --git a/borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt b/borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt
new file mode 100644
index 0000000..bebfccf
--- /dev/null
+++ b/borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt
@@ -0,0 +1,28 @@
+package de.micromata.borgbutler.server.user
+
+import de.micromata.borgbutler.server.BorgVersion
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+class UserFilterTest {
+    @Test
+    fun checkRemoteAddressTest() {
+        check(null, null, false)
+        check("127.0.0.1", null, true)
+        check("127.0.0.1", "172.0.", true)
+        check("127.0.0.1", "192.168.", true)
+        check("192.168.1.1", "192.168.", true)
+        check("192.168.1.1", "192.168. 192.178.5", true)
+        check("192.178.5.1", "192.168. 192.178.5", true)
+        check("192.178.6.1", "192.168. 192.178.5", false)
+    }
+
+    fun check(remoteAddress: String?, allowedClientIps: String?, expected: Boolean) {
+        if (allowedClientIps != null) {
+            System.setProperty(UserFilter.SYSTEM_PROPERTY_ALLOWED_CLIENT_IPS, allowedClientIps)
+        } else {
+            System.clearProperty(UserFilter.SYSTEM_PROPERTY_ALLOWED_CLIENT_IPS)
+        }
+        Assertions.assertEquals(expected, UserFilter().check(remoteAddress))
+    }
+}
diff --git a/build.gradle b/build.gradle
index 443a1a7..8d87b85 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,14 +12,14 @@
 
 allprojects {
     group = 'de.micromata.borgbutler'
-    version = '0.5-SNAPSHOT'
+    version = '0.5'
 }
 
 subprojects {
     apply plugin: 'java'
     apply plugin: 'org.jetbrains.kotlin.jvm'
-    sourceCompatibility = 1.9 // Needed: since 1.9 i18n properties in UTF-8 format.
-    targetCompatibility = 1.9
+    sourceCompatibility = 9 // Needed: since 1.9 i18n properties in UTF-8 format.
+    targetCompatibility = 9
 
     tasks.withType(JavaCompile) {
         options.compilerArgs << '-Xlint:unchecked'

--
Gitblit v1.10.0