rebreak-monorepo/ops/nginx/dns.rebreak.org.conf
chahinebrini db7875fb34 feat(ops/mdm): AdGuard ClientID handshake — nginx + watcher
End-to-end DoH-to-backend wiring for Mac auto-activation:

  Mac → dns.rebreak.org/dns-query/<token> → nginx → AdGuard
  → querylog.json (CP field) → watcher.py → POST /handshake → backend

- ops/nginx/dns.rebreak.org.conf: vhost with `location ^~ /dns-query`
  prefix-match (not exact). proxy_pass without trailing slash preserves
  the full path so AdGuard parses the ClientID natively.
- watcher.py: NDJSON tail with inode-based rotation safety, per-token
  60s in-memory cooldown, urllib (no external deps), graceful 401/404/5xx
- rebreak-handshake-watcher.service: systemd unit, EnvironmentFile with
  chmod 600 (HANDSHAKE_SECRET never in git), NoNewPrivileges + PrivateTmp
- DOH_CLIENTID_HANDSHAKE.md: architecture + flow diagram + risk table
- RUNBOOK.md: status/logs/restart commands + deploy ordering

Not yet deployed. Verify-checklist before `nginx -s reload`:
  1. confirm AdGuard DoH port (config assumes 127.0.0.1:3000)
  2. confirm TLS cert exists for dns.rebreak.org
  3. snapshot current nginx config
  4. `nginx -t` dry-run
  5. functional curl + grep CP in querylog before starting watcher
2026-05-15 22:41:38 +02:00

71 lines
2.9 KiB
Plaintext

# nginx vhost: dns.rebreak.org
# Deployed on: rebreak-mdm (178.105.101.137)
# TLS termination for AdGuard Home DoH endpoint.
#
# CRITICAL: location uses prefix-match (^~) NOT exact-match (=).
# AdGuard Home parses the ClientID from the URL path natively:
# /dns-query -> normal DoH query (no ClientID)
# /dns-query/<cid> -> DoH query, AdGuard extracts <cid> into QueryLog.ClientID
#
# The full path MUST be forwarded to AdGuard unchanged (no $uri stripping).
# AdGuard reads the path segment after /dns-query/ as the ClientID.
# Stripping it (e.g. proxy_pass http://.../ without path) would break CID detection.
#
# AdGuard Home listens on 127.0.0.1:3000 (HTTPS UI) and plain DNS-over-HTTPS
# on a dedicated port. Verify actual DoH port on server:
# docker exec adguardhome cat /opt/adguardhome/conf/AdGuardHome.yaml | grep -A5 dns:
# Common defaults: port 3000 for UI+DoH combined, or separate port 5353/8053.
# Adjust proxy_pass port below to match actual AdGuard DoH port.
#
# Current assumption: AdGuard DoH on 127.0.0.1:3000 (same as UI, AdGuard's default).
# If AdGuard runs in docker: verify with `docker ps | grep adguard`.
server {
listen 80;
server_name dns.rebreak.org;
return 301 https://dns.rebreak.org$request_uri;
}
server {
listen 443 ssl http2;
server_name dns.rebreak.org;
ssl_certificate /etc/letsencrypt/live/dns.rebreak.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dns.rebreak.org/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ── DoH endpoint — prefix match, path forwarded unchanged ───────────────
# ^~ wins over regex locations. Catches both:
# /dns-query (plain DoH)
# /dns-query/abc1 (DoH with ClientID — AdGuard parses the suffix)
#
# proxy_pass terminates without trailing slash so $request_uri is appended
# as-is, preserving /dns-query/<cid> verbatim.
location ^~ /dns-query {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# DoH requests are short-lived; tight timeouts are fine.
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
# ── Health check (for monitoring / GH Actions deploy verify) ────────────
location /health {
return 200 "OK\n";
add_header Content-Type text/plain;
}
# ── Block everything else ────────────────────────────────────────────────
location / {
return 404;
}
}