This commit is contained in:
Graham McIntire 2026-06-22 16:17:33 -05:00
parent 55451fe285
commit 390eb2d835
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
7 changed files with 6 additions and 131 deletions

View file

@ -49,7 +49,7 @@ ansible-playbook --check playbook.yml
## Architecture
### Playbook Structure
- `playbook.yml` - Main playbook for all configuration (common, firewall, resolvers, DNS, Caddy, general, monitoring, PostgreSQL, prometheus stack, etc.)
- `playbook.yml` - Main playbook for all configuration (common, firewall, DNS, Caddy, general, monitoring, PostgreSQL, prometheus stack, etc.)
- `bootstrap.yml` - Initial host preparation
- `netbox.yml` - Standalone NetBox deployment
@ -67,7 +67,6 @@ Roles follow a standard structure with:
- `caddy` - Web server and reverse proxy
- `firewall` - UFW/firewalld rule management
- `ns` - BIND9 nameserver configuration
- `resolvers` - Unbound DNS resolver setup
- `tailscale` - Tailscale VPN setup
- `monitor` - Icinga2 monitoring master
- `prometheus` / `alertmanager` / `loki` / `grafana` / `node_exporter` - Prometheus monitoring stack
@ -101,7 +100,7 @@ Roles follow a standard structure with:
- Passwordless sudo configured for automation
### Network Services
- DNS resolvers use Unbound
- DNS resolvers are Flatcar VMs managed via OpenTofu + Butane (see ../tofu/)
- Nameservers configured via ns role
- Caddy serves as reverse proxy for web services
- Tailscale provides VPN connectivity

View file

@ -13,8 +13,7 @@ This repository contains Ansible playbooks and roles for managing infrastructure
├── requirements.yml # Collection and role dependencies
├── group_vars/ # Group-specific variables
│ ├── all/ # Global variables
│ ├── bind9_servers/ # BIND9 zone configurations
│ └── resolvers/ # Resolver configurations
│ └── bind9_servers/ # BIND9 zone configurations
├── host_vars/ # Host-specific variables
├── vars/ # Legacy base variables
├── roles/ # Ansible roles (26 roles)
@ -23,7 +22,6 @@ This repository contains Ansible playbooks and roles for managing infrastructure
│ ├── caddy/ # Caddy web server
│ ├── firewall/ # UFW/firewalld management
│ ├── ns/ # BIND9 nameserver
│ ├── resolvers/ # Unbound DNS resolver
│ ├── tailscale/ # Tailscale VPN
│ ├── monitor/ # Icinga2 monitoring
│ ├── prometheus/ # Prometheus monitoring stack
@ -60,7 +58,6 @@ ansible-playbook -t caddy playbook.yml
- `vntx_servers` - VNTX network services (logs, radius, vpn, netbox, unimus, monitor, librenms, uisp)
- `home_servers` - Home network services (skippy, mail, dokku, aprs, staging, prom)
- `bind9_servers` - BIND9 authoritative nameservers
- `resolvers` - Unbound DNS resolvers
- `caddy_servers` - Servers running Caddy web server
- `monitoring_servers` - Icinga2 monitoring master
- `prometheus_servers` - Prometheus monitoring stack
@ -78,7 +75,6 @@ See `hosts` file for the full inventory.
- Tailscale VPN integration
- Caddy web server with automatic SSL
- BIND9 authoritative DNS with DNSSEC
- Unbound DNS resolvers
- Icinga2 monitoring with PagerDuty integration
- Prometheus/Grafana/Loki monitoring stack
- NetBox IPAM/DCIM

View file

@ -1,72 +0,0 @@
# DoH on resolver1/resolver2 with Caddy-managed TLS
**Date:** 2026-05-14
**Status:** In Progress
## Goal
Serve DNS-over-HTTPS (RFC 8484) from `resolver1.vntx.net` and `resolver2.vntx.net`,
with Let's Encrypt certificate lifecycle handled by Caddy. Also add AAAA records for
the resolvers so they are reachable over IPv6.
## Architecture
Each resolver host runs two cooperating services:
```
Internet (DoH client)
| HTTPS :443 (resolverN.vntx.net/dns-query)
v
Caddy (new on host) <- obtains/renews LE cert via HTTP-01 on :80
| HTTP/2 cleartext (h2c), 127.0.0.1:8080/dns-query
v
Unbound (existing) :53 plain DNS (unchanged)
:8080 DoH, http-notls-downstream
```
- Caddy owns :443 and :80, manages the LE cert for `resolverN.vntx.net`.
- Unbound serves DoH as cleartext HTTP/2 on loopback only — no certs touch Unbound.
- Plain DNS on :53 is untouched; this change is purely additive.
## Access control
DoH clients reach Caddy, which forwards from 127.0.0.1 — so Unbound's source-IP
`access-control` cannot see the real client. Caddy therefore enforces the client
allow-list with a `remote_ip` matcher; non-allowed clients get 403. The allow-list
lives in one place: `resolver_allowed_netblocks` in `group_vars/resolvers/main.yml`,
consumed by both the Caddy matcher and Unbound's `access-control` (for :53).
## Changes
### IPv6 / DNS
- `roles/ns/templates/zone.j2` — emit AAAA records when a host has `ipv6:`.
- `group_vars/bind9_servers/vntx.net.yml` — add `ipv6:` to resolver1/2 + resolver-01/02
(`2606:1c80::240`, `2606:1c80::250`).
### Resolver group vars
- `group_vars/resolvers/main.yml` — add `resolver_allowed_netblocks`, add 80/443 to
`firewall_allow_rules`, set `caddy_template: Caddyfile-resolver.j2`.
### Unbound
- `roles/resolvers/templates/unbound.conf.j2` — add loopback DoH listener
(`interface: 127.0.0.1@8080`, `https-port: 8080`, `http-endpoint`,
`http-notls-downstream: yes`); drive `access-control` from
`resolver_allowed_netblocks`.
### Caddy role
- `roles/caddy/tasks/main.yml` — add RedHat (COPR) install path; support a
`caddy_template` variable; ensure `/var/log/caddy` exists.
- `roles/caddy/templates/Caddyfile-resolver.j2` — new shared template for resolvers.
### Playbook
- `playbook.yml` — add the `caddy` role to the `resolvers` play.
## Verification
- `ansible-playbook --syntax-check playbook.yml`
- After apply: `unbound -V | grep nghttp2` (DoH build support), `ss -tlnp` shows
unbound on 127.0.0.1:8080 and caddy on :443/:80.
- `curl -H 'accept: application/dns-message' https://resolver1.vntx.net/dns-query?dns=...`
from an allowed netblock; 403 from outside.
- `dig AAAA resolver1.vntx.net` returns `2606:1c80::240`.
</invoke>

View file

@ -1,4 +0,0 @@
---
ansible_host: 204.110.191.240
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'

View file

@ -1,4 +0,0 @@
---
ansible_host: 204.110.191.250
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'

View file

@ -1,35 +0,0 @@
# Managed by Ansible (roles/caddy) - shared by the [resolvers] group.
# Terminates TLS for the DNS-over-HTTPS endpoint and reverse-proxies to the
# local Unbound DoH listener (cleartext HTTP/2 on 127.0.0.1:8080).
{
admin off
persist_config off
}
{{ inventory_hostname }} {
# Restrict DoH to the same netblocks Unbound allows for plain DNS.
# Caddy forwards from 127.0.0.1, so Unbound cannot see the real client;
# the allow-list is enforced here instead.
@allowed remote_ip {% for nb in resolver_allowed_netblocks %}{{ nb }} {% endfor %}
handle /dns-query {
handle @allowed {
reverse_proxy 127.0.0.1:8080 {
transport http {
versions h2c 2
}
}
}
respond "Forbidden" 403
}
handle {
respond "DNS-over-HTTPS endpoint: /dns-query" 200
}
log {
output file /var/log/caddy/{{ inventory_hostname }}.log
format json
}
}

View file

@ -1,17 +1,12 @@
---
# Telegraf agent: collects Unbound stats via unbound-control and exposes them
# as a Prometheus /metrics endpoint for the prom.w5isp.com server to scrape.
# (unbound-control runs fine as the unprivileged telegraf user because the
# resolvers' control interface is plaintext on localhost.)
# Telegraf agent defaults. For Unbound stats on Flatcar resolvers, see
# tofu/flatcar-resolver.bu (Docker-based Telegraf deployed via Butane).
telegraf_interval: "30s"
telegraf_unbound_binary: /usr/sbin/unbound-control
# Prometheus output — Prometheus scrapes http://<resolver>:<port>/metrics.
telegraf_prometheus_port: 9273
telegraf_prometheus_listen: "0.0.0.0:{{ telegraf_prometheus_port }}"
# Only this source network may reach the metrics port (the routed scrape
# path from prom.w5isp.com). Keeps Unbound stats off the public internet.
# Source network allowed to reach the metrics port.
telegraf_scrape_allow_cidr: "204.110.188.0/22"
# InfluxData package repository (EL/RedHat).