diff --git a/containers/unbound/docker-compose.yml b/containers/unbound/docker-compose.yml index 60364de..ac73073 100755 --- a/containers/unbound/docker-compose.yml +++ b/containers/unbound/docker-compose.yml @@ -5,7 +5,7 @@ networks: services: unbound-service: #LIBREPORTAL|SERVICE_TAG_1|unbound-service container_name: unbound-service - image: mvance/unbound:latest #LIBREPORTAL|UNBOUND_VERSION_TAG|latest + image: madnuttah/unbound:latest #LIBREPORTAL|UNBOUND_VERSION_TAG|latest restart: unless-stopped hostname: unbound # GLUETUN_OFF_BEGIN @@ -13,10 +13,20 @@ services: - "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1 - "PORTS_DATA_2" #LIBREPORTAL|PORTS_TAG_2|PORTS_DATA_2 # GLUETUN_OFF_END + # No traefik labels: Unbound has no web interface, it is a resolver other + # apps point at. The category/title labels are the ones the portal reads. + labels: + libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA + libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA volumes: - SOCKET_DATA #LIBREPORTAL|SOCKET_TAG|SOCKET_DATA - /etc/ssl/certs:/etc/ssl/certs:ro - - ./etc/unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro + # A drop-in, not a replacement: the image's own unbound.conf carries + # `include-toplevel: /usr/local/unbound/conf.d/*.conf`, so ours adds to a + # working recursive config maintained upstream instead of overriding it. + # (The old image wanted a full config at /opt/unbound/etc/unbound — a + # different path entirely, which is why this could not be a tag swap.) + - "./conf.d:/usr/local/unbound/conf.d:ro" environment: - TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA healthcheck: diff --git a/containers/unbound/resources/libreportal.conf b/containers/unbound/resources/libreportal.conf new file mode 100644 index 0000000..08d0251 --- /dev/null +++ b/containers/unbound/resources/libreportal.conf @@ -0,0 +1,74 @@ +# LibrePortal drop-in for Unbound. +# +# Loaded by the image's own unbound.conf via +# include-toplevel: "/usr/local/unbound/conf.d/*.conf" +# so this ADDS to the upstream defaults rather than replacing them. That +# matters: the base config is a working recursive resolver maintained by the +# image author, and anything we replace wholesale we would then own forever. +# +# Recursive by design. Unbound here talks to the root and authoritative +# servers directly, so no third-party resolver ever sees your queries — that is +# the entire reason to run it in front of a blocker. If you would rather forward +# to an encrypted upstream instead, see the forward-zone block at the bottom. + +server: + # Listen on all interfaces INSIDE the container; Docker decides what is + # exposed. The image already runs unprivileged on 5335, which is why this + # can sit alongside Pi-hole or AdGuard without fighting over port 53. + interface: 0.0.0.0@5335 + + # Answer only for private networks. Docker's own bridge ranges are included + # because a query from Pi-hole arrives via the docker gateway, not from the + # blocker's own address. Everything else is refused: an open resolver on a + # public IP gets conscripted into DNS amplification attacks within hours. + access-control: 127.0.0.0/8 allow + access-control: 10.0.0.0/8 allow + access-control: 172.16.0.0/12 allow + access-control: 192.168.0.0/16 allow + access-control: 0.0.0.0/0 refuse + + # Validate DNSSEC and refuse to answer if validation fails. + harden-dnssec-stripped: yes + harden-glue: yes + harden-below-nxdomain: yes + harden-referral-path: yes + + # Do not leak anything about the resolver or the queries it has seen. + hide-identity: yes + hide-version: yes + qname-minimisation: yes + rrset-roundrobin: yes + + # Modest, predictable footprint — this commonly runs on a small VPS + # alongside everything else, so it is deliberately not tuned for a + # thousand-client network. + num-threads: 1 + msg-cache-size: 32m + rrset-cache-size: 64m + cache-min-ttl: 300 + cache-max-ttl: 86400 + prefetch: yes + serve-expired: yes + + # Private addresses must never be returned for public names (DNS rebinding). + private-address: 10.0.0.0/8 + private-address: 172.16.0.0/12 + private-address: 192.168.0.0/16 + private-address: 169.254.0.0/16 + private-address: fd00::/8 + private-address: fe80::/10 + +# --------------------------------------------------------------------------- +# OPTIONAL: forward instead of recurse. +# +# Uncommenting this turns Unbound from a recursive resolver into an encrypted +# forwarder — queries go to Quad9 over TLS rather than to the root servers. +# That is a real trade: you gain a filtered, fast upstream and you give up the +# "nobody sees my queries" property that is the point of recursion. Pick one +# deliberately; do not run both expecting the benefits of each. +# --------------------------------------------------------------------------- +#forward-zone: +# name: "." +# forward-tls-upstream: yes +# forward-addr: 9.9.9.9@853#dns.quad9.net +# forward-addr: 149.112.112.112@853#dns.quad9.net diff --git a/containers/unbound/resources/unbound.conf b/containers/unbound/resources/unbound.conf deleted file mode 100755 index 3bfda1f..0000000 --- a/containers/unbound/resources/unbound.conf +++ /dev/null @@ -1,22 +0,0 @@ -server: - interface: 0.0.0.0@53 - access-control: 10.0.0.0/8 allow - access-control: 172.16.0.0/12 allow - access-control: 192.168.0.0/16 allow - access-control: 127.0.0.1/32 allow - -# >>> libreportal-monitoring >>> -#remote-control: -# control-enable: yes -# control-interface: 0.0.0.0 -# control-port: 8953 -# control-use-cert: no -# <<< libreportal-monitoring <<< - -forward-zone: - name: "." - #forward-addr: 10.100.0.1@53 # Bind9 for local domain - #forward-addr: 10.100.0.31@53 # Local Pi-Hole - forward-addr: 10.100.0.3@53 # Local AdGuard - forward-addr: 9.9.9.9@853 # Quad9 DoT - forward-ssl-upstream: yes \ No newline at end of file diff --git a/containers/unbound/scripts/unbound_install_hooks.sh b/containers/unbound/scripts/unbound_install_hooks.sh index 1d7926c..007c496 100644 --- a/containers/unbound/scripts/unbound_install_hooks.sh +++ b/containers/unbound/scripts/unbound_install_hooks.sh @@ -1,15 +1,26 @@ #!/bin/bash -# Unbound install hooks — drop unbound.conf into etc/ and toggle the -# monitoring marker in it (the driver already handled docker-compose.yml). +# Unbound install hooks — place the LibrePortal drop-in config. +# +# The image ships a working recursive unbound.conf that ends with +# include-toplevel: "/usr/local/unbound/conf.d/*.conf" +# so our file is added to it rather than replacing it. Nothing here needs to +# know how to configure a resolver; we contribute access-control, hardening and +# cache sizing, and upstream keeps owning the parts that change between +# Unbound releases. unbound_install_post_compose() { local app_name="$1" - local result - result=$(copyResource "$app_name" "unbound.conf" "etc" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1) - checkSuccess "Copying unbound.conf to containers folder." + # A previous attempt that started the container before this file existed + # leaves conf.d as a Docker-created stub, and the copy below would then land + # inside it. Same failure that kept Nextcloud's nginx from starting. + repairStubDirForFile "$containers_dir$app_name/conf.d/libreportal.conf" "loud" - monitoringToggleAppConfig "$app_name" "etc/unbound.conf" + local result + result=$(copyResource "$app_name" "libreportal.conf" "conf.d" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1) + checkSuccess "Copying libreportal.conf to $containers_dir$app_name/conf.d" + + monitoringToggleAppConfig "$app_name" "conf.d/libreportal.conf" } diff --git a/containers/unbound/unbound.config b/containers/unbound/unbound.config index b528fb9..14d2e7f 100755 --- a/containers/unbound/unbound.config +++ b/containers/unbound/unbound.config @@ -33,7 +33,7 @@ CFG_UNBOUND_MONITORING=false # CFG_UNBOUND_CATEGORY="networking" CFG_UNBOUND_TITLE="Unbound" -CFG_UNBOUND_DESCRIPTION="DNS Resolver" +CFG_UNBOUND_DESCRIPTION="Recursive DNS Resolver" CFG_UNBOUND_LONG_DESCRIPTION="Unbound is a validating, recursive, and caching DNS resolver that provides secure and fast DNS resolution" CFG_UNBOUND_URL="https://github.com/NLnetLabs/unbound" CFG_UNBOUND_ACTIONS="configure|install|restart|shutdown|uninstall" @@ -62,6 +62,6 @@ CFG_UNBOUND_NETWORK=default # - webui: if true, this port serves the main web interface # - description: human-readable description of the service # -CFG_UNBOUND_PORT_1="unbound-service|dns-tcp|5335:53|private|tcp|false|false|false|DNS Resolver (TCP)|" -CFG_UNBOUND_PORT_2="unbound-service|dns-udp|5335:53|private|udp|false|false|false|DNS Resolver (UDP)|" +CFG_UNBOUND_PORT_1="unbound-service|dns-tcp|5335:5335|private|tcp|false|false|false|DNS Resolver (TCP) — point Pi-hole/AdGuard here|" +CFG_UNBOUND_PORT_2="unbound-service|dns-udp|5335:5335|private|udp|false|false|false|DNS Resolver (UDP) — point Pi-hole/AdGuard here|" CFG_UNBOUND_PORT_3="unbound-exporter|metrics|9167:9167|disabled|tcp|false|false|false|Metrics Exporter (sidecar, docker-network only)|"