Skip to content

Commit

Permalink
Cleanup and modernization
Browse files Browse the repository at this point in the history
  • Loading branch information
dubo-dubon-duponey committed Feb 22, 2024
1 parent 848c352 commit 341adda
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 128 deletions.
41 changes: 22 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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="[email protected]"
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="[email protected]"
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
131 changes: 63 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.

Expand All @@ -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
```
Expand All @@ -64,26 +70,30 @@ docker run -d \

```bash
docker run -d \
--env "DOMAIN=dev-null.farcloser.world"
--env "[email protected]"
--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 "[email protected]" \
--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
```
Expand All @@ -92,12 +102,15 @@ docker run -d \

```bash
docker run -d \
--env "DOMAIN=dev-null.farcloser.world"
--env "[email protected]"
--env "DNS_FORWARD_ENABLED=false" \
--env "DNS_OVER_TLS_ENABLED=true" \
--env "DNS_OVER_TLS_DOMAIN=dev-null.farcloser.world" \
--env "[email protected]"
--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
```
Expand All @@ -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 protected] \
--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: `[email protected]`) 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: `[email protected]`) 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.
Expand Down
63 changes: 44 additions & 19 deletions context/runtime/boot/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
Loading

0 comments on commit 341adda

Please sign in to comment.