mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cqv4ckcaStUFpUkcJKmmNz
161 lines
5.5 KiB
Bash
Executable File
161 lines
5.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# Certificate sync, stunnel and unit installation for the DoT resolver.
|
|
#
|
|
# Sourced by setup_dot_resolver.sh; split out to keep it under the 250-line cap.
|
|
# Sourced rather than run, so it inherits the caller's strict mode and
|
|
# the helper functions and variables defined above the source line.
|
|
|
|
install_cert_sync() {
|
|
# Caddy renews on its own schedule; stunnel reads files from disk and must
|
|
# be told. A timer copies the current cert out of the container and
|
|
# reloads stunnel only when the content actually changed.
|
|
sudo install -d -m 700 "$CERT_DIR"
|
|
sudo tee "$SYNC_SCRIPT" >/dev/null <<EOF
|
|
#!/bin/bash
|
|
# Copy the Caddy-managed certificate for $DOMAIN out to stunnel.
|
|
# Generated by $SCRIPT_NAME - edit that instead.
|
|
set -euo pipefail
|
|
src="$(cert_source_dir)"
|
|
tmp="\$(mktemp -d)"
|
|
trap 'rm -rf "\$tmp"' EXIT
|
|
docker cp "$CADDY_CONTAINER:\$src/$DOMAIN.crt" "\$tmp/cert.pem"
|
|
docker cp "$CADDY_CONTAINER:\$src/$DOMAIN.key" "\$tmp/key.pem"
|
|
cat "\$tmp/cert.pem" "\$tmp/key.pem" >"\$tmp/combined.pem"
|
|
if ! cmp -s "\$tmp/combined.pem" "$CERT_DIR/combined.pem"; then
|
|
install -m 600 "\$tmp/combined.pem" "$CERT_DIR/combined.pem"
|
|
systemctl reload-or-restart stunnel-dot.service
|
|
echo "certificate updated for $DOMAIN"
|
|
fi
|
|
EOF
|
|
sudo chmod 755 "$SYNC_SCRIPT"
|
|
}
|
|
|
|
install_stunnel_conf() {
|
|
sudo tee "$STUNNEL_CONF" >/dev/null <<EOF
|
|
; DNS-over-TLS front end for the local dnsmasq blocklist.
|
|
; Generated by $SCRIPT_NAME - edit that instead.
|
|
foreground = yes
|
|
syslog = yes
|
|
[dns-over-tls]
|
|
accept = $BIND_ADDR:$DOT_PORT
|
|
connect = 127.0.0.1:53
|
|
cert = $CERT_DIR/combined.pem
|
|
; DoT is TLS 1.2+ only in practice; Android requires a publicly trusted cert.
|
|
sslVersionMin = TLSv1.2
|
|
EOF
|
|
}
|
|
|
|
install_units() {
|
|
sudo tee /etc/systemd/system/stunnel-dot.service >/dev/null <<EOF
|
|
[Unit]
|
|
Description=DNS-over-TLS front end for dnsmasq blocklist
|
|
After=network-online.target dnsmasq.service wg-quick@wg0.service
|
|
Wants=network-online.target
|
|
; Wants, not Requires: stunnel only forwards to 127.0.0.1:53 per connection,
|
|
; and a Requires= turned one failed dnsmasq start at boot into a DoT outage
|
|
; that lasted until someone restarted stunnel by hand.
|
|
Wants=dnsmasq.service
|
|
; Binding to the WireGuard address fails if the interface is not up yet.
|
|
After=sys-devices-virtual-net-wg0.device
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/stunnel $STUNNEL_CONF
|
|
ExecReload=/bin/kill -HUP \$MAINPID
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo tee /etc/systemd/system/dot-cert-sync.service >/dev/null <<EOF
|
|
[Unit]
|
|
Description=Copy the Caddy certificate for $DOMAIN to stunnel
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$SYNC_SCRIPT
|
|
EOF
|
|
|
|
sudo tee /etc/systemd/system/dot-cert-sync.timer >/dev/null <<EOF
|
|
[Unit]
|
|
Description=Daily certificate sync for the DoT resolver
|
|
|
|
[Timer]
|
|
OnCalendar=daily
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
sudo systemctl daemon-reload
|
|
}
|
|
|
|
open_firewall() {
|
|
# Binding to the WireGuard address is NOT sufficient on its own. The
|
|
# input chain has `policy drop` and admits only specific ports over wg0
|
|
# (ssh), so a DoT connection from the phone is dropped even though the
|
|
# tunnel itself is up and ICMP replies fine. That combination is
|
|
# confusing to debug -- ping works, the port does not -- so the rule is
|
|
# added explicitly, scoped to the tunnel interface.
|
|
if [[ "$BIND_ADDR" != "0.0.0.0" ]]; then
|
|
if command -v nft >/dev/null 2>&1 && sudo nft list table inet filter >/dev/null 2>&1; then
|
|
# Through the firewall's owner, which persists ALLOW_DOT and renders
|
|
# the rule: an 'nft add rule' here vanished at the next re-apply.
|
|
if sudo bash "${WG_SCRIPT:-${SCRIPT_DIR}/setup_wireguard_ssh.sh}" allow-dot; then
|
|
log "Permitted TCP $DOT_PORT on wg0 only (persisted)"
|
|
else
|
|
log "WARNING: could not open $DOT_PORT on wg0 (setup_wireguard_ssh.sh allow-dot)"
|
|
fi
|
|
fi
|
|
log "No router port-forward needed - $BIND_ADDR is WireGuard-only."
|
|
return
|
|
fi
|
|
if command -v nft >/dev/null 2>&1 && sudo nft list table inet filter >/dev/null 2>&1; then
|
|
if ! sudo nft list table inet filter | grep -q "dport $DOT_PORT"; then
|
|
# Rate limited, because a publicly bound DoT endpoint is an open
|
|
# resolver: TCP-only so not usable for classic UDP amplification,
|
|
# but still someone else's free DNS and a standing attack surface.
|
|
# A phone issues a handful of lookups a second at worst; 25/second
|
|
# with a burst of 50 is far above real use and far below abuse.
|
|
sudo nft add rule inet filter input tcp dport "$DOT_PORT" \
|
|
limit rate 25/second burst 50 packets accept ||
|
|
log "WARNING: could not add rate-limited nftables rule for $DOT_PORT"
|
|
log "Permitted TCP $DOT_PORT (rate limited 25/s, burst 50)"
|
|
fi
|
|
fi
|
|
log "Ensure your router forwards TCP $DOT_PORT to this host."
|
|
}
|
|
|
|
run_check() {
|
|
local ok=0
|
|
systemctl is-active --quiet stunnel-dot.service ||
|
|
{
|
|
log "FAIL stunnel-dot.service not active"
|
|
ok=1
|
|
}
|
|
ss -lnt 2>/dev/null | grep -qE ":$DOT_PORT\b" ||
|
|
{
|
|
log "FAIL nothing listening on $DOT_PORT"
|
|
ok=1
|
|
}
|
|
# $CERT_DIR is mode 700 and owned by root because it holds a private key,
|
|
# so an unprivileged [[ -s ]] reports "missing" for a file that is
|
|
# present. Test as root, or --check reports a false failure on a healthy
|
|
# install.
|
|
sudo test -s "$CERT_DIR/combined.pem" ||
|
|
{
|
|
log "FAIL certificate missing at $CERT_DIR/combined.pem"
|
|
ok=1
|
|
}
|
|
if command -v kdig >/dev/null 2>&1; then
|
|
kdig +tls "@$BIND_ADDR" -p "$DOT_PORT" +short example.com >/dev/null 2>&1 ||
|
|
{
|
|
log "FAIL DoT query did not answer"
|
|
ok=1
|
|
}
|
|
fi
|
|
[[ $ok -eq 0 ]] && log "OK - DoT resolver healthy on $DOMAIN:$DOT_PORT"
|
|
return "$ok"
|
|
}
|