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!!! 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) 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 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 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 ...' borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
File was deleted 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 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. borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt
New file @@ -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" } } borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt
New file @@ -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)) } } 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'