mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +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
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
Every script under /usr/local/bin must be a repo file. install_cert_sync wrote /usr/local/bin/sync-dot-cert.sh from a heredoc with the domain, Caddy container and cert dir baked in. The script now lives at linux_configuration/scripts/sync-dot-cert.sh, takes those three as arguments (defaults = the installer's constants), and is installed with install -m 755. dot-cert-sync.service and the first sync pass the arguments, so --domain reaches the timer too. Test asserts the installed file is the tracked one byte for byte and runs the script itself against a docker shim (reload only on change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yhTkYV6ZeuAbQYvykFGFc Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
153 lines
5.3 KiB
Bash
Executable File
153 lines
5.3 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.
|
|
# The script is the tracked scripts/sync-dot-cert.sh, installed as-is; the
|
|
# values it used to have baked in are now its arguments (sync_script_args).
|
|
sudo install -d -m 700 "$CERT_DIR"
|
|
[[ -f $SYNC_SCRIPT_SRC ]] || die "tracked sync script missing: $SYNC_SCRIPT_SRC"
|
|
sudo install -m 755 "$SYNC_SCRIPT_SRC" "$SYNC_SCRIPT"
|
|
}
|
|
|
|
# The arguments every caller of $SYNC_SCRIPT passes, so a non-default --domain
|
|
# reaches the timer's runs too.
|
|
sync_script_args() {
|
|
printf '%s %s %s' "$DOMAIN" "$CADDY_CONTAINER" "$CERT_DIR"
|
|
}
|
|
|
|
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 $(sync_script_args)
|
|
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"
|
|
}
|