mirror of https://github.com/micromata/borgbackup-butler.git

Kai Reinhard
19.59.2021 399ecccba0a361db79d6ff70ff64a0edd4a73d4f
Version 0.5 prepared.
1 files deleted
2 files added
8 files modified
261 ■■■■■ changed files
README.adoc 24 ●●●●● patch | view | raw | blame | history
borgbutler-docker/README.adoc 6 ●●●● patch | view | raw | blame | history
borgbutler-docker/app/Dockerfile 2 ●●● patch | view | raw | blame | history
borgbutler-docker/app/entrypoint.sh 4 ●●●● patch | view | raw | blame | history
borgbutler-docker/buildDocker.sh 2 ●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java 87 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt 2 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt 2 ●●● patch | view | raw | blame | history
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/user/UserFilter.kt 98 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/test/kotlin/de/micromata/borgbutler/server/user/UserFilterTest.kt 28 ●●●●● patch | view | raw | blame | history
build.gradle 6 ●●●● patch | view | raw | blame | history
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'