diff --git a/Containers/docker-socket-proxy/Dockerfile b/Containers/docker-socket-proxy/Dockerfile index 21176d35..6f72f7fd 100644 --- a/Containers/docker-socket-proxy/Dockerfile +++ b/Containers/docker-socket-proxy/Dockerfile @@ -1,43 +1,17 @@ -# Inspiration: https://github.com/Tecnativa/docker-socket-proxy/blob/master/Dockerfile FROM haproxy:2.8.2-alpine3.18 USER root - +ENV NEXTCLOUD_HOST nextcloud-aio-nextcloud RUN set -ex; \ - apk add --no-cache tzdata; \ - chmod 777 -R /run/; \ - chmod 777 -R /var/lib/haproxy + apk add --no-cache \ + ca-certificates \ + tzdata \ + bind-tools; \ + chmod -R 777 /tmp -EXPOSE 2375 -ENV ALLOW_RESTARTS=1 \ - AUTH=1 \ - BUILD=0 \ - COMMIT=0 \ - CONFIGS=0 \ - CONTAINERS=1 \ - DISTRIBUTION=0 \ - EVENTS=0 \ - EXEC=0 \ - GRPC=0 \ - IMAGES=1 \ - INFO=1 \ - LOG_LEVEL=info \ - NETWORKS=1 \ - NODES=0 \ - PING=1 \ - PLUGINS=0 \ - POST=1 \ - SECRETS=0 \ - SERVICES=0 \ - SESSION=0 \ - SOCKET_PATH=/var/run/docker.sock \ - SWARM=0 \ - SYSTEM=0 \ - TASKS=0 \ - VERSION=1 \ - VOLUMES=1 -COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg +COPY --chmod=775 *.sh / +COPY --chmod=664 haproxy.cfg /haproxy.cfg -USER root - -HEALTHCHECK CMD nc -z 127.0.0.1 2375 || exit 1 +ENTRYPOINT ["/start.sh"] +HEALTHCHECK CMD /healthcheck.sh +LABEL com.centurylinklabs.watchtower.enable="false" diff --git a/Containers/docker-socket-proxy/haproxy.cfg b/Containers/docker-socket-proxy/haproxy.cfg index 8143c832..024983b5 100644 --- a/Containers/docker-socket-proxy/haproxy.cfg +++ b/Containers/docker-socket-proxy/haproxy.cfg @@ -1,72 +1,41 @@ # Inspiration: https://github.com/Tecnativa/docker-socket-proxy/blob/master/haproxy.cfg -global - log stdout format raw daemon "${LOG_LEVEL}" - - pidfile /run/haproxy.pid - maxconn 4000 - - # Turn on stats unix socket - # server-state-file /var/lib/haproxy/server-state - defaults - mode http - log global - option httplog - option dontlognull - option http-server-close - option redispatch - retries 3 - timeout http-request 10s - timeout queue 1m timeout connect 10s - timeout client 10m - timeout server 10m - timeout http-keep-alive 10s - timeout check 10s - maxconn 3000 + timeout client 10s + timeout server 10s - # Allow seamless reloads - # load-server-state-from-file global - - # Use provided example error pages - errorfile 400 /usr/local/etc/haproxy/errors/400.http - errorfile 403 /usr/local/etc/haproxy/errors/403.http - errorfile 408 /usr/local/etc/haproxy/errors/408.http - errorfile 500 /usr/local/etc/haproxy/errors/500.http - errorfile 502 /usr/local/etc/haproxy/errors/502.http - errorfile 503 /usr/local/etc/haproxy/errors/503.http - errorfile 504 /usr/local/etc/haproxy/errors/504.http - -backend dockerbackend - server dockersocket $SOCKET_PATH - -frontend dockerfrontend +frontend http + mode http bind :2375 - http-request deny unless METH_GET || { env(POST) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool } - http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool } + http-request deny unless { src 127.0.0.1 } || { src ::1 } || { src NC_IPV4_PLACEHOLDER } || { src NC_IPV6_PLACEHOLDER } + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+/((json)|(start)|(stop)) } METH_GET + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/nc_app_[a-zA-Z0-9_.-]+ } METH_DELETE + + # ACL to restrict container name to nc_app_[a-zA-Z0-9_.-]+ + acl nc_app_container_name url_param(name) -m reg -i "^nc_app_[a-zA-Z0-9_.-]+" + + # ACL to restrict the number of Mounts to 1 + acl one_mount_volume req.body -m reg -i "\"Mounts\"\s*:\s*\[\s*(?:(?!\"Mounts\"\s*:\s*\[)[^}]*)}[^}]*\]" + # ACL to deny if there are any binds + acl binds_present req.body -m reg -i "\"HostConfig\"\s*:.*\"Binds\"\s*:" + # ACL to restrict the type of Mounts to volume + acl type_not_volume req.body -m reg -i "\"Mounts\":\s*\[[^\]]*(\"Type\":\s*\"(?!volume\b)\w+\"[^\]]*)+\]" + http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create } nc_app_container_name !one_mount_volume binds_present type_not_volume METH_POST + + # ACL to restrict container creation, that it has HostConfig.Privileged only set to false + acl no_privileged_flag req.body -m reg -i "\"HostConfig\":\s?{[^}]*\"Privileged\":\s?false" + # ACL to allow mount volume with strict pattern for name: nc_app_[a-zA-Z0-9_.-]+_data + acl nc_app_volume_data_only req.body -m reg -i "\"Mounts\":\s?\[\s?{[^}]*\"Source\":\s?\"nc_app_[a-zA-Z0-9_.-]+_data\"" + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create } nc_app_container_name no_privileged_flag nc_app_volume_data_only METH_POST + + acl nc_app_volume_data req.body -m reg -i "\"Name\":\s?\"nc_app_[a-zA-Z0-9_.-]+_data\"" + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes/create } nc_app_volume_data METH_POST + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes/nc_app_[a-zA-Z0-9_.-]+_data } METH_DELETE + http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images/create } METH_POST http-request deny default_backend dockerbackend + +backend dockerbackend + mode http + server dockersocket /var/run/docker.sock diff --git a/Containers/docker-socket-proxy/healthcheck.sh b/Containers/docker-socket-proxy/healthcheck.sh new file mode 100644 index 00000000..867d9a5e --- /dev/null +++ b/Containers/docker-socket-proxy/healthcheck.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +nc -z "$NEXTCLOUD_HOST" 9000 || exit 0 +if [ "$(wget http://127.0.0.1:2375/v1.41/_ping -qO -)" != "OK" ]; then + exit 1 +fi diff --git a/Containers/docker-socket-proxy/start.sh b/Containers/docker-socket-proxy/start.sh new file mode 100644 index 00000000..18840a7c --- /dev/null +++ b/Containers/docker-socket-proxy/start.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Only start container if nextcloud is accessible +while ! nc -z "$NEXTCLOUD_HOST" 9000; do + echo "Waiting for Nextcloud to start..." + sleep 5 +done + +set -x +IPv4_ADDRESS_NC="$(dig nextcloud-aio-nextcloud IN A +short | grep '^[0-9.]\+$' | sort | head -n1)" +HAPROXYFILE="$(sed "s|NC_IPV4_PLACEHOLDER|$IPv4_ADDRESS_NC|" /haproxy.cfg)" +echo "$HAPROXYFILE" > /tmp/haproxy.cfg + +IPv6_ADDRESS_NC="$(dig nextcloud-aio-nextcloud AAAA +short | grep '^[0-9a-f:]\+$' | sort | head -n1)" +HAPROXYFILE="$(sed "s# || { src NC_IPV6_PLACEHOLDER }##g" /tmp/haproxy.cfg)" +# shellcheck disable=SC2001 +HAPROXYFILE="$(echo "$HAPROXYFILE" | sed "s|NC_IPV6_PLACEHOLDER|$IPv6_ADDRESS_NC|")" +echo "$HAPROXYFILE" > /tmp/haproxy.cfg +set +x + +haproxy -f /tmp/haproxy.cfg -db diff --git a/php/containers.json b/php/containers.json index a380e355..1df76249 100644 --- a/php/containers.json +++ b/php/containers.json @@ -661,8 +661,7 @@ "restart": "unless-stopped", "read_only": true, "tmpfs": [ - "/run/", - "/var/lib/haproxy" + "/tmp" ] } ]