=====================================================================
             ___       __  _ __
  ___  _____/ (_)___  / /_(_) /__
 / _ \/ ___/ / / __ \/ __/ / //_/
/  __/ /__/ / / /_/ / /_/ / ,<
\___/\___/_/_/ .___/\__/_/_/|_|
            /_/
=====================================================================

DNS Guardrails with dnscrypt-proxy

2024-01-23 | #tailscale #networking #docker #containers #dns #linux

Intro

Over the holidays we got our two younger children HP laptops for them to do their school work on and to have a proper computer. While the schools Google Classroom login effectively adds restrictions to Chrome, I still wanted to have some guardrails on their Internet access as well as ad-blocking.

The first thing I did was replace the Windows S install that came on the laptops with Linux Mint[1] as I've always enjoyed the Cinnamon desktop environment[2] and it has a low enough learning curve that the kids could easily pick it up. After installing a few apps and games (OpenRCT2[3]) from the Software Center and setting their own passwords, they were up and running and surfing the world-wide-web.

=> 1: https://www.linuxmint.com/ | 2: https://projects.linuxmint.com/cinnamon/ | 3: https://openrct2.org/

Finally I added Tailscale[4] to both laptops to put them on my tailnet[5]. This has benefits of accessing tailnet-only services, easier remote access, and leveraging the dnscrypt-proxy on OpenBSD[6] I setup a few years ago for DNS.

=> 4: https://tailscale.com/ | 5: https://tailscale.com/kb/1136/tailnet | 6: https://www.ecliptik.com/Running-dnscrypt-proxy-on-OpenBSD/

Guardrails

My original DNS config worked well, but I wanted to add some guardrails specifically for the kids laptops,

  1. Cloudflare for Families[7]

  1. Ad Blocking[8]

  1. YouTube Restricted Mode[9] via Cloaking[10]

  1. Accessible only from the Tailscale

=> 7: https://blog.cloudflare.com/introducing-1-1-1-1-for-families/ | 8: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Public-blocklist | 9: https://support.google.com/a/answer/6212415 | 10: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Public-blocklist

First I tried using the existing dnscrypt-proxy[11] to provide a different set of DNS resolvers depending on the source IP, but this wasn't possible. Eventually I came up with a seperate DNS infrastructure in a container for the laptops to use,

=> 11: https://github.com/DNSCrypt/dnscrypt-proxy

Container Stack

FROM debian:trixie-slim

ENV DEBIAN_FRONTEND noninteractive

RUN apt update && \

apt install -y dnscrypt-proxy \

  ca-certificates \

&& apt clean

WORKDIR /tmp

ENTRYPOINT [ "/usr/sbin/dnscrypt-proxy" ]

CMD [ "-config", "/etc/dnscrypt-proxy/dnscrypt-proxy.toml" ]

docker compose[12] is used to bring up the stack, which includes a `tailscale` container to provide network and access to other devices on the tailnet. Configuration files are monted read-only from the current directly and some volumes to maintain state across restarts.

=> https://docs.docker.com/compose/ 12: https://docs.docker.com/compose/
version: '3.9'
services:
  tailscale:
    container_name: tailscale-dnscrypt
    hostname: dnscrypt-proxy
    image: ghcr.io/tailscale/tailscale
    stdin_open: true
    environment:
      - TS_AUTH_KEY=${TS_AUTH_KEY}
      - TS_USERSPACE=true
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_SOCKET=/var/run/tailscale/tailscaled.sock
    volumes:
      - dnscryptvarlib:/var/lib
    restart: unless-stopped
  dnscrypt-proxy:
    build: .
    stdin_open: true
    volumes:
      - ./dnscrypt-proxy.toml:/etc/dnscrypt-proxy/dnscrypt-proxy.toml:ro
      - ./blocklist.txt:/etc/dnscrypt-proxy/blocklist.txt:ro
      - ./cloaking-rules.txt:/etc/dnscrypt-proxy/cloaking-rules.txt:ro
      - ./domains-allowlist.txt:/etc/dnscrypt-proxy/domains-allowlist.txt:ro
      - keys:/etc/dnscrypt-proxy/keys
    restart: unless-stopped
    network_mode: 'service:tailscale'
volumes:
  dnscryptvarlib:
  keys:

dnscrypt-proxy

The dnscrypt-proxy configuration uses the cloudflare-family source and ad-blocking using a blocklist.txt generated with generate-domains-blocklist.py[13]. All logs go to /dev/stdout so they appear in docker compose logs.

=> 13: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Combining-Blocklists

Use cloudflare DNS

server_names = ['cloudflare-family']

Listen on local and LAN addresses for DNS

listen_addresses = ['127.0.0.1:53']

max_clients = 250

user_name = '_dnscrypt-proxy'

Enable ipv4 and ipv6

ipv4_servers = true

ipv6_servers = false

Allow TCP and UDP

force_tcp = false

timeout = 2500

keepalive = 30

Logging

log_level = 2

use_syslog = true

Certs

cert_refresh_delay = 240

dnscrypt_ephemeral_keys = true

tls_disable_session_tickets = true

Cache

cache = true

Cloaking

cloaking_rules = '/etc/dnscrypt-proxy/cloaking-rules.txt'

Query logging, commented out unless for troubleshooting

[query_log]

file = '/dev/stdout'

format = 'tsv'

Sources for resolvers and relays

[sources]

[sources.'public-resolvers']

urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3

/public-resolvers.md']

minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'

cache_file = '/var/tmp/public-resolvers.md'

refresh_delay = 72

Blocking configuration

[blocked_names]

Path to the file of blocking rules (absolute, or relative to the same directory as the executable file)

blocked_names_file = '/etc/dnscrypt-proxy/blocklist.txt'

log_file = '/dev/stdout'

log_format = 'tsv'

Allow configuration

[allowed_names]

allowed_names_file = '/etc/dnscrypt-proxy/domains-allowlist.txt

Cloaking[14] is used so all YouTube requests resolve to `restrict.youtube.com`.

=> https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Public-blocklist 14: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Public-blocklist
www.youtube.com restrict.youtube.com
m.youtube.com  restrict.youtube.com
youtubei.googleapis.com restrict.youtube.com
youtube.googleapis.com restrict.youtube.com
www.youtube-nocookie.com restrict.youtube.com

Exposing via Tailscale

Since I only want DNS available to devices on my tailnet, and not publicly available, there's a tailscale container in the docker-compose.yml that provides networking to the dnscrypt-proxy container using network_mode.

Set this up by creating an auth key[15] for your tailnet and then putting it into a .env file that docker compose will source in and set as the TS_AUTH_KEY variable.

=> 15: https://tailscale.com/kb/1085/auth-keys

TS_AUTH_KEY=tskey-auth-xxxxxxxxxxx

## Enabling on Linux Mint

My tailnet uses Magic DNS[16] which sets the nameserver for all devices on a tailnet to `100.100.100.100`, but since this is a DNS server specific to a subset of systems we want to use the IP of the `dnscrypt-proxy` device instead.

=> https://tailscale.com/kb/1081/magicdns 16: https://tailscale.com/kb/1081/magicdns

After bringing up the stack with `docker compose up`, the `tailscale` container will authenticate to the tailnet and have an Tailscale IP (eg `100.112.129.40`). This IP is then added to the laptops `/etc/resolv.conf`,

nameserver 100.112.129.40

search tailnet-3831.ts.net

Proxy Information
Original URL
gemini://rawtext.club/~ecliptik/_posts/2024-01-23-DNS-Guardrails-with-dnscrypt-proxy.gmi
Status Code
Success (20)
Meta
text/gemini; lang=en
Capsule Response Time
396.084142 milliseconds
Gemini-to-HTML Time
2.86663 milliseconds

This content has been proxied by September (3851b).