Recently I’ve set up AdGuard Home to use as personal DNS resolver + filter. I’ve come across this guide and its’ docker compose file.
It worked perfectly until I’ve discovered that for past ~10 hours my AGH instance has resolved over 3,000,000 instances of the same URL, resulting in query_log.json taking up most of my system’s free disk space.
This is an example of DNS amplification attack, where an attacker used mine (and according to recent posts I’ve seen, other’s) DNS servers to resolve a single domain, resulting in a flood of DNS queries.
Naturally, I was aware that exposing any service/port to the public is a risk (apart from 80/443 which serve this website), and indeed I never intended to make my DNS resolver available to the public.
Having my UFW set to block all but 80, 443 and tailscale, I expected port 53 to be unreachable from outside my tailnet. However, docker bypasses UFW by directly manipulating iptables and thus the port was exposed to the public internet, despite UFW blocking it.
Now, to the docker-compose.yaml
:
ports:
- 53:53/tcp # plain dns over tcp
- 53:53/udp # plain dns over udp
- 8000:80/tcp # http web interface
- 3000:3000/tcp # initial setup web interface
this binds container’s port 53 to the host’s port 53 on 0.0.0.0, allowing access from any interface on this machine, including the public one (eth0 or equivalent).
There are few ways to fix this:
- Bind to tailscale ip: by explicitly specifying ip address to bind to such as
100.x.y.z:53:53/tcp
, we can bind this docker container to accept requests from machine’s tailscale IP only, making it unreachable from the public internet. This, however, creates an issue where client IPs are lost, as AdGuard Home will see all requests coming from internal docker IP, not the original client IP. - Use
network_mode: "host"
: this will make docker container use host’s network stack, allowing it to bind to port 53 on the host directly, but also making it unreachable from the public internet. This bypasses docker’s networking rules and allows UFW rules to apply, while distinguishing between clients. Furthermore, upon initial setup, AdGuard Home can be bound to listen only on tailscale0 interface, creating an extra layer of security.
Further reading on this topic: https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu/51741599#51741599
In conclusion, take care when self-hosting services that can be abused (DNS, email etc) and double-check whether they’re reachable from outside even if you believe they’re not, as docker does not necessarily respect your firewall rules.