From 341adda698d742a8a27132b7a313ff723f881442 Mon Sep 17 00:00:00 2001 From: dubo-dubon-duponey Date: Thu, 22 Feb 2024 14:57:00 -0800 Subject: [PATCH] Cleanup and modernization --- Dockerfile | 41 +++--- README.md | 131 +++++++++--------- context/runtime/boot/entrypoint.sh | 63 ++++++--- .../config/coredns-no-tls-forward-mdns.conf | 30 ++++ .../config/coredns-no-tls-forward.conf | 15 +- .../config/coredns-no-tls-recursive.conf | 2 +- .../runtime/config/coredns-tls-forward.conf | 12 +- .../runtime/config/coredns-tls-recursive.conf | 6 +- 8 files changed, 172 insertions(+), 128 deletions(-) create mode 100644 context/runtime/config/coredns-no-tls-forward-mdns.conf diff --git a/Dockerfile b/Dockerfile index cc0b680..8612f6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,10 +13,8 @@ FROM $FROM_REGISTRY/$FROM_IMAGE_TOOLS FROM --platform=$BUILDPLATFORM $FROM_REGISTRY/$FROM_IMAGE_BUILDER AS fetcher-lego ARG GIT_REPO=github.com/go-acme/lego -#ARG GIT_VERSION=v4.5.3 -#ARG GIT_COMMIT=3675fe68aed2c6c99d1f92eb02133ecd9af7b2be -ARG GIT_VERSION=v4.14.0 -ARG GIT_COMMIT=838eff2c024085ec0b5d37e3c2496d5261240763 +ARG GIT_VERSION=v4.15.0 +ARG GIT_COMMIT=46fe435c2c2e447ae48df712eca8278bbca8986e ENV WITH_BUILD_SOURCE="./cmd/lego" ENV WITH_BUILD_OUTPUT="lego" @@ -188,32 +186,37 @@ FROM $FROM_REGISTRY/$FROM_IMAGE_RUNTIME # Get relevant bits from builder COPY --from=builder --chown=$BUILD_UID:root /dist / -ENV DOMAIN="" -ENV EMAIL="dubo-dubon-duponey@farcloser.world" -ENV UPSTREAM_SERVER_1="" -ENV UPSTREAM_SERVER_2="" -ENV UPSTREAM_NAME="" -ENV STAGING="" - -ENV DNS_PORT=1053 -ENV TLS_PORT=1853 -ENV HTTPS_PORT=1443 -ENV GRPC_PORT=5553 +ENV DNS_OVER_TLS_ENABLED=false +ENV DNS_OVER_TLS_DOMAIN="" +ENV DNS_OVER_TLS_PORT=853 +ENV DNS_OVER_TLS_LEGO_PORT=443 +ENV DNS_OVER_TLS_LEGO_EMAIL="dubo-dubon-duponey@farcloser.world" +ENV DNS_OVER_TLS_LE_USE_STAGING=false + +ENV DNS_FORWARD_ENABLED=true +ENV DNS_FORWARD_UPSTREAM_NAME="cloudflare-dns.com" +ENV DNS_FORWARD_UPSTREAM_IP_1="tls://1.1.1.1" +ENV DNS_FORWARD_UPSTREAM_IP_2="tls://1.0.0.1" + +ENV DNS_PORT=53 +ENV DNS_OVER_GRPC_PORT=553 +ENV DNS_STUFF_MDNS=false + ENV METRICS_PORT=9253 # NOTE: this will not be updated at runtime and will always EXPOSE default values # Either way, EXPOSE does not do anything, except function as a documentation helper EXPOSE $DNS_PORT/udp -EXPOSE $TLS_PORT/tcp -EXPOSE $HTTPS_PORT/tcp -EXPOSE $GRPC_PORT/tcp +EXPOSE $DNS_OVER_TLS_PORT/tcp +EXPOSE $DNS_OVER_TLS_LEGO_PORT/tcp +EXPOSE $DNS_OVER_GRPC_PORT/tcp EXPOSE $METRICS_PORT/tcp # Lego just needs /certs to work VOLUME /certs ENV HEALTHCHECK_URL="127.0.0.1:$DNS_PORT" -ENV HEALTHCHECK_QUESTION=healthcheck-dns.farcloser.world +ENV HEALTHCHECK_QUESTION=dns.autonomous.healthcheck.farcloser.world ENV HEALTHCHECK_TYPE=udp HEALTHCHECK --interval=120s --timeout=30s --start-period=10s --retries=1 CMD dns-health || exit 1 diff --git a/README.md b/README.md index ce61fec..d5547f2 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,34 @@ # What -Docker image for CoreDNS, with sample configuration for various "DNS over TLS" scenarios. +Easy to use CoreDNS container with reasonable defaults. This is based on [CoreDNS](https://coredns.io/), and [Let's Encrypt](https://letsencrypt.org/) (via [Lego](https://github.com/go-acme/lego)). This is useful in the following scenarios: - 1. you want to encrypt all your laptop DNS traffic (forwarded to Cloudflare, Quad9, Google, or any other DoT public resolver) - 1. you want to run your own DNS/DoT recursive service - 1. anything else you can do with CoreDNS +1. you want to run a *local* DNS server on your LAN (or your laptop) that will forward requests with encryption to an upstream resolver + ( like Cloudflare, Quad9, Google, or any other DoT public resolver) +1. you want to run your own DNS over TLS (recursive) service +1. other stuff + +Before running this publicly on the internet, you should think twice though, and make sure you understand the implications. ## Image features -* multi-architecture: +* multi-architecture (publishing): * [x] linux/amd64 - * [x] linux/386 * [x] linux/arm64 + +* multi-architecture (not publishing anymore) + * [x] linux/386 * [x] linux/arm/v7 * [x] linux/arm/v6 * [x] linux/ppc64le * [x] linux/s390x +* * hardened: * [x] image runs read-only - * [x] image runs with no capabilities but NET_BIND_SERVICE + * [x] image runs with no capabilities but NET_BIND_SERVICE, which you could remove if you use unprivileged ports * [x] process runs as a non-root user, disabled login, no shell * lightweight * [x] based on our slim [Debian Bullseye](https://github.com/dubo-dubon-duponey/docker-debian) @@ -35,8 +41,7 @@ This is useful in the following scenarios: ## Run -You can run either a forwarding server (that will send requests to an upstream), or a recursive one -(only available on AMD64 in the provided image - or ARM64 if you rebuild the image on an ARM64 node). +You can run either a forwarding server (that will send requests to an upstream), or a recursive one. Then you can either expose a traditional DNS server, a TLS server, or both. @@ -48,12 +53,13 @@ Examples: ```bash docker run -d \ - --env "UPSTREAM_NAME=cloudflare-dns.com" - --env "UPSTREAM_SERVER_1=tls://1.1.1.1" - --env "UPSTREAM_SERVER_2=tls://1.0.0.1" + --env "DNS_FORWARD_UPSTREAM_NAME=cloudflare-dns.com" \ + --env "DNS_FORWARD_UPSTREAM_IP_1=tls://1.1.1.1" \ + --env "DNS_FORWARD_UPSTREAM_IP_2=tls://1.0.0.1" \ --net bridge \ - --publish 53:1053/udp \ + --publish 53:53/udp \ --cap-drop ALL \ + --cap-add NET_BIND_SERVICE \ --read-only \ docker.io/dubodubonduponey/dns ``` @@ -64,26 +70,30 @@ docker run -d \ ```bash docker run -d \ - --env "DOMAIN=dev-null.farcloser.world" - --env "EMAIL=dubo-dubon-duponey@farcloser.world" - --env "UPSTREAM_NAME=cloudflare-dns.com" - --env "UPSTREAM_SERVER_1=tls://1.1.1.1" - --env "UPSTREAM_SERVER_2=tls://1.0.0.1" + --env "DNS_FORWARD_UPSTREAM_NAME=cloudflare-dns.com" \ + --env "DNS_FORWARD_UPSTREAM_IP_1=tls://1.1.1.1" \ + --env "DNS_FORWARD_UPSTREAM_IP_2=tls://1.0.0.1" \ + --env "DNS_OVER_TLS_ENABLED=true" \ + --env "DNS_OVER_TLS_DOMAIN=dev-null.farcloser.world" \ + --env "DNS_OVER_TLS_LEGO_EMAIL=dubo-dubon-duponey@farcloser.world" \ --net bridge \ - --publish 443:1443/tcp \ - --publish 853:1853/tcp \ + --publish 443:443/tcp \ + --publish 853:853/tcp \ --cap-drop ALL \ + --cap-add NET_BIND_SERVICE \ --read-only \ docker.io/dubodubonduponey/dns ``` -### Traditional DNS server, recursive +### Recursive DNS server ```bash docker run -d \ + --env "DNS_FORWARD_ENABLED=false" \ --net bridge \ - --publish 53:1053/udp \ + --publish 53:53/udp \ --cap-drop ALL \ + --cap-add NET_BIND_SERVICE \ --read-only \ docker.io/dubodubonduponey/dns ``` @@ -92,12 +102,15 @@ docker run -d \ ```bash docker run -d \ - --env "DOMAIN=dev-null.farcloser.world" - --env "EMAIL=dubo-dubon-duponey@farcloser.world" + --env "DNS_FORWARD_ENABLED=false" \ + --env "DNS_OVER_TLS_ENABLED=true" \ + --env "DNS_OVER_TLS_DOMAIN=dev-null.farcloser.world" \ + --env "DNS_OVER_TLS_LEGO_EMAIL=dubo-dubon-duponey@farcloser.world" --net bridge \ - --publish 443:1443/tcp \ - --publish 853:1853/tcp \ + --publish 443:443/tcp \ + --publish 853:853/tcp \ --cap-drop ALL \ + --cap-add NET_BIND_SERVICE \ --read-only \ docker.io/dubodubonduponey/dns ``` @@ -113,77 +126,59 @@ If you want to customize your CoreDNS config, mount a volume into `/config` on t (and customize one of the files to your needs). ```bash -chown -R 1000:nogroup "[host_path_for_config]" +chown -R 2000:nogroup "[host_path_for_config]" docker run -d \ --volume [host_path_for_config]:/config:ro \ - --net bridge \ - --publish 53:1053/udp \ - --publish 443:1443/tcp \ - --publish 853:1853/tcp \ - --cap-drop ALL \ - --read-only \ - docker.io/dubodubonduponey/dns + ... ``` ### Networking -If you want to use another networking mode but `bridge` (and run the service on privileged ports), you have to run the container as `root`, grant the appropriate `cap` and set the ports: +You can control the various ports used by the service if you wish to: ```bash docker run -d \ - --env DOMAIN=something.mydomain.com \ - --env EMAIL=me@mydomain.com \ - --net host \ --env DNS_PORT=53 \ - --env TLS_PORT=853 \ - --env HTTPS_PORT=443 \ - --cap-add CAP_NET_BIND_SERVICE \ - --user root \ - --cap-drop ALL \ - --read-only \ - docker.io/dubodubonduponey/dns + --env DNS_OVER_TLS_PORT=853 \ + --env DNS_OVER_TLS_LEGO_PORT=443 \ + ... ``` ### Configuration reference The default setup use CoreDNS config files in `/config` that sets-up different scenarios based on the value of environment variables. -The `/certs` folder is used to store LetsEncrypt certificates (it's a volume by default, which you may want to mount), in case you configure a TLS server (through using the DOMAIN variable). +The `/certs` folder is used to store LetsEncrypt certificates (it's a volume by default, which you may want to mount), in case you configure a DNS-over-TLS server. #### Runtime You may specify the following environment variables at runtime: - * DOMAIN (eg: `something.mydomain.com`) controls the domain name of your server if you want a TLS server - * EMAIL (eg: `me@mydomain.com`) controls the email used to issue your server certificate - * STAGING controls whether you want to use LetsEncrypt staging environment (useful when debugging so not to burn your quota) - * UPSTREAM_NAME (eg: `cloudflare-dns.com`) controls the server name of the (TLS) upstream if you want a forwarding server - * UPSTREAM_SERVER_1 and UPSTREAM_SERVER_2 (eg: `tls://1.1.1.1`) controls the upstream forward addresses +For DoT: + * DNS_OVER_TLS_ENABLED: enable the DoT service + * DNS_OVER_TLS_DOMAIN (eg: `something.mydomain.com`) controls the domain name of your server + * DNS_OVER_TLS_LEGO_PORT: port that lego will use to listen on for LetsEncrypt response + * DNS_OVER_TLS_LEGO_EMAIL (eg: `me@mydomain.com`) controls the email used to issue your server certificate + * DNS_OVER_TLS_LE_USE_STAGING controls whether you want to use LetsEncrypt staging environment (useful when debugging so not to burn your quota) -You can also tweak the following for control over which internal ports are being used (useful if intend to run with host/macvlan, see above) +For forwarding: + * DNS_FORWARD_ENABLED: enable (default) or disable forwarding mode + * DNS_FORWARD_UPSTREAM_NAME: if you forward to a DoT server, the domain name of that service + * DNS_FORWARD_UPSTREAM_IP_1: the ip of the upstream + * DNS_FORWARD_UPSTREAM_IP_2: the backup ip of the upstream - * DNS_PORT (default to 1053) - * HTTPS_PORT (default to 1443) - * TLS_PORT (default to 1853) - * GRPC_PORT (default to 5553) - * METRICS_PORT (default to 9253) +You can also tweak the following: -Of course using any privileged port for these requires CAP_NET_BIND_SERVICE and a root user. + * DNS_PORT (default to 53) + * DNS_OVER_GRPC_PORT (default to 553) + * DNS_STUFF_MDNS: convenient little trick to respond for certain mDNS queries over traditional DNS + * METRICS_PORT for Prometheuse (default to 9253) -Note that these environment variables are used solely in the default configuration files. -If you are rolling your own, it's up to you to use them or not. +Of course using any privileged port for these requires CAP_NET_BIND_SERVICE. Finally, any additional arguments provided when running the image will get fed to the `coredns` binary. -### Unbound and recursive server - -Unbound support requires CGO, which requires the target platform to be the same as the build platform. - -Our images are built on linux/amd64. - -If you want to run on arm64, you have to rebuild it yourself on an arm64 node. - ### Prometheus The default configuration files expose a Prometheus metrics endpoint on port 9253. diff --git a/context/runtime/boot/entrypoint.sh b/context/runtime/boot/entrypoint.sh index bde0f20..b5e3f1f 100755 --- a/context/runtime/boot/entrypoint.sh +++ b/context/runtime/boot/entrypoint.sh @@ -8,54 +8,79 @@ source "$root/helpers.sh" helpers::dir::writable /certs -DOMAIN="${DOMAIN:-}" -EMAIL="${EMAIL:-}" -HTTPS_PORT="${HTTPS_PORT:-}" -STAGING="${STAGING:-}" -UPSTREAM_NAME="${UPSTREAM_NAME:-}" +# DNS over tls settings +DNS_OVER_TLS_ENABLED="${DNS_OVER_TLS_ENABLED:-}" +DNS_OVER_TLS_DOMAIN="${DNS_OVER_TLS_DOMAIN:-}" +DNS_OVER_TLS_PORT="${DNS_OVER_TLS_PORT:-}" +DNS_OVER_TLS_LEGO_PORT="${DNS_OVER_TLS_LEGO_PORT:-}" +DNS_OVER_TLS_LEGO_EMAIL="${DNS_OVER_TLS_LEGO_EMAIL:-}" +DNS_OVER_TLS_LE_USE_STAGING="${DNS_OVER_TLS_LE_USE_STAGING:-}" + +# Forward settings +DNS_FORWARD_ENABLED="${DNS_FORWARD_ENABLED:-}" +DNS_FORWARD_UPSTREAM_NAME="${DNS_FORWARD_UPSTREAM_NAME:-}" +DNS_FORWARD_UPSTREAM_IP_1="${DNS_FORWARD_UPSTREAM_IP_1:-}" +DNS_FORWARD_UPSTREAM_IP_2="${DNS_FORWARD_UPSTREAM_IP_2:-}" + +# Other DNS settings +DNS_PORT="${DNS_PORT:-}" +DNS_OVER_GRPC_PORT="${DNS_OVER_GRPC_PORT:-}" +DNS_STUFF_MDNS="${DNS_STUFF_MDNS:-}" + +# Metrics settings +METRICS_PORT="${METRICS_PORT:-}" certs::renew(){ local domain="$1" local email="$2" - local staging="$3" + local port="$3" + local staging="$4" local command="renew --days=45" - [ ! "$staging" ] || staging="--server=https://acme-staging-v02.api.letsencrypt.org/directory" + [ "$staging" != true ] \ + && staging= \ + || staging="--server=https://acme-staging-v02.api.letsencrypt.org/directory" [ -e "/certs/certificates/$domain.key" ] || command="run" printf >&2 "Running command: %s" "lego --domains=\"$domain\" \ --accept-tos --email=\"$email\" --path=/certs --tls $staging --pem \ - --tls.port=:${HTTPS_PORT} \ + --tls.port=:$port \ ${command}" lego --domains="$domain" \ - --accept-tos --email="$email" --path=/certs --tls ${staging} --pem \ - --tls.port=:"${HTTPS_PORT}" \ + --accept-tos \ + --email="$email" \ + --path=/certs \ + --tls $staging --pem \ + --tls.port=:"$port" \ ${command} } loop(){ while true; do - certs::renew "$1" "$2" "$3" + sleep 86400 + certs::renew "$@" # signal coredns to reload config - technically happens once a day, which is not optimal, but fine kill -s SIGUSR1 1 - sleep 86400 done } +no_tls=-no # If we have a domain, get certificates for that, and the appropriate config -if [ "$DOMAIN" ]; then - # Initial registration - certs::renew "$DOMAIN" "$EMAIL" "$STAGING" +if [ "$DNS_OVER_TLS_ENABLED" == true ]; then + no_tls= + + # Initial registration, blocking + certs::renew "$DNS_OVER_TLS_DOMAIN" "$DNS_OVER_TLS_LEGO_EMAIL" "$DNS_OVER_TLS_PORT" "$DNS_OVER_TLS_LE_USE_STAGING" # Now run in the background to renew 45 days before expiration - loop "$DOMAIN" "$EMAIL" "$STAGING" & + loop "$DNS_OVER_TLS_DOMAIN" "$DNS_OVER_TLS_LEGO_EMAIL" "$DNS_OVER_TLS_PORT" "$DNS_OVER_TLS_LE_USE_STAGING" & fi # Choose config based on environment values -[ "$DOMAIN" ] && no_tls= || no_tls=-no -[ "$UPSTREAM_NAME" ] && mode=forward || mode=recursive +[ "$DNS_FORWARD_ENABLED" == true ] && mode=forward || mode=recursive +[ "$DNS_STUFF_MDNS" == true ] && mod=-mdns || mod= # Get coredns started -exec coredns -conf /config/coredns${no_tls}-tls-${mode}.conf "$@" +exec coredns -conf /config/coredns${no_tls}-tls-${mode}${mod}.conf "$@" diff --git a/context/runtime/config/coredns-no-tls-forward-mdns.conf b/context/runtime/config/coredns-no-tls-forward-mdns.conf new file mode 100644 index 0000000..e5624ce --- /dev/null +++ b/context/runtime/config/coredns-no-tls-forward-mdns.conf @@ -0,0 +1,30 @@ +# experimental: suck up mdns names in +local { + mdns local 1 +} + +# Classic DNS on 53, forwarding to an upstream +.:{$DNS_PORT} { + hosts { + fallthrough + } + + prometheus :{$METRICS_PORT} + + forward . {$DNS_FORWARD_UPSTREAM_IP_1} {$DNS_FORWARD_UPSTREAM_IP_2} { + tls_servername {$DNS_FORWARD_UPSTREAM_NAME} + health_check 5s + } + + cache 3600 + + log + errors + + reload +} + +# GRPC healthcheck endpoint +grpc://.:{$DNS_OVER_GRPC_PORT} { + whoami +} diff --git a/context/runtime/config/coredns-no-tls-forward.conf b/context/runtime/config/coredns-no-tls-forward.conf index 5abc973..f9de036 100644 --- a/context/runtime/config/coredns-no-tls-forward.conf +++ b/context/runtime/config/coredns-no-tls-forward.conf @@ -1,8 +1,3 @@ -# XXX experimental: suck up mdns names in -local { - mdns local 1 -} - # Classic DNS on 53, forwarding to an upstream .:{$DNS_PORT} { hosts { @@ -11,8 +6,8 @@ local { prometheus :{$METRICS_PORT} - forward . {$UPSTREAM_SERVER_1} {$UPSTREAM_SERVER_2} { - tls_servername {$UPSTREAM_NAME} + forward . {$DNS_FORWARD_UPSTREAM_IP_1} {$DNS_FORWARD_UPSTREAM_IP_2} { + tls_servername {$DNS_FORWARD_UPSTREAM_NAME} health_check 5s } @@ -24,11 +19,7 @@ local { reload } -#duboduboduponey.me { -# mdns duboduboduponey.me -#} - # GRPC healthcheck endpoint -grpc://.:{$GRPC_PORT} { +grpc://.:{$DNS_OVER_GRPC_PORT} { whoami } diff --git a/context/runtime/config/coredns-no-tls-recursive.conf b/context/runtime/config/coredns-no-tls-recursive.conf index aa2ad17..c0f39de 100644 --- a/context/runtime/config/coredns-no-tls-recursive.conf +++ b/context/runtime/config/coredns-no-tls-recursive.conf @@ -17,6 +17,6 @@ } # GRPC healthcheck endpoint -grpc://.:{$GRPC_PORT} { +grpc://.:{$DNS_OVER_GRPC_PORT} { whoami } diff --git a/context/runtime/config/coredns-tls-forward.conf b/context/runtime/config/coredns-tls-forward.conf index 9bf4f33..462f5b0 100644 --- a/context/runtime/config/coredns-tls-forward.conf +++ b/context/runtime/config/coredns-tls-forward.conf @@ -6,8 +6,8 @@ prometheus :{$METRICS_PORT} - forward . {$UPSTREAM_SERVER_1} {$UPSTREAM_SERVER_2} { - tls_servername {$UPSTREAM_NAME} + forward . {$DNS_FORWARD_UPSTREAM_IP_1} {$DNS_FORWARD_UPSTREAM_IP_2} { + tls_servername {$DNS_FORWARD_UPSTREAM_NAME} health_check 5s } @@ -20,7 +20,7 @@ } # DoT on 853, forwarding to an upstream -tls://.:{$TLS_PORT} { +tls://.:{$DNS_OVER_TLS_PORT} { tls /certs/certificates/{$DOMAIN}.crt /certs/certificates/{$DOMAIN}.key /certs/certificates/{$DOMAIN}.issuer.crt hosts { @@ -29,8 +29,8 @@ tls://.:{$TLS_PORT} { prometheus :{$METRICS_PORT} - forward . {$UPSTREAM_SERVER_1} {$UPSTREAM_SERVER_2} { - tls_servername {$UPSTREAM_NAME} + forward . {$DNS_FORWARD_UPSTREAM_IP_1} {$DNS_FORWARD_UPSTREAM_IP_2} { + tls_servername {$DNS_FORWARD_UPSTREAM_NAME} health_check 5s } @@ -43,6 +43,6 @@ tls://.:{$TLS_PORT} { } # GRPC healthcheck endpoint -grpc://.:{$GRPC_PORT} { +grpc://.:{$DNS_OVER_GRPC_PORT} { whoami } diff --git a/context/runtime/config/coredns-tls-recursive.conf b/context/runtime/config/coredns-tls-recursive.conf index d3ee180..4d029fc 100644 --- a/context/runtime/config/coredns-tls-recursive.conf +++ b/context/runtime/config/coredns-tls-recursive.conf @@ -17,8 +17,8 @@ } # DoT on 853, forwarding to an upstream -tls://.:{$TLS_PORT} { - tls /certs/certificates/{$DOMAIN}.crt /certs/certificates/{$DOMAIN}.key /certs/certificates/{$DOMAIN}.issuer.crt +tls://.:{$DNS_OVER_TLS_PORT} { + tls /certs/certificates/{$DNS_OVER_TLS_DOMAIN}.crt /certs/certificates/{$DNS_OVER_TLS_DOMAIN}.key /certs/certificates/{$DNS_OVER_TLS_DOMAIN}.issuer.crt hosts { fallthrough @@ -37,6 +37,6 @@ tls://.:{$TLS_PORT} { } # GRPC healthcheck endpoint -grpc://.:{$GRPC_PORT} { +grpc://.:{$DNS_OVER_GRPC_PORT} { whoami }