Multi-backend DNS configuration for .fips domain (#58)

Replace the resolvectl-only fips-dns.service with a detection script
that configures whichever DNS resolver is available:

1. systemd dns-delegate (systemd >= 258, declarative drop-in)
2. systemd-resolved via resolvectl (most systemd distros)
3. dnsmasq (standalone)
4. NetworkManager with dnsmasq plugin
5. Warning with manual instructions if none found

Service reloads are non-fatal — config is written and the backend
is recorded even if the reload fails, preventing state file cleanup
issues under set -e.

Teardown reads the recorded backend from /run/fips/dns-backend and
reverses the configuration, or cleans up all possible backends if
the state file is missing.

Includes a Docker-based test harness (testing/dns-resolver/test.sh)
covering all five backends across Debian 12, Debian 13, Fedora,
and bare systems.

Fixes #52.
This commit is contained in:
Johnathan Corgan
2026-04-11 18:45:17 +01:00
committed by GitHub
parent 774e33fd27
commit fe205e74de
10 changed files with 635 additions and 39 deletions
+141
View File
@@ -0,0 +1,141 @@
#!/bin/bash
# fips-dns-setup — Configure DNS routing for the .fips domain.
#
# Detects the system's DNS resolver and configures it to forward .fips
# queries to the FIPS DNS responder on 127.0.0.1:5354.
#
# Backends (tried in order):
# 1. systemd dns-delegate (systemd >= 258, declarative drop-in)
# 2. systemd-resolved via resolvectl (most systemd distros)
# 3. dnsmasq (standalone)
# 4. NetworkManager with dnsmasq plugin
# 5. Warning with manual instructions
set -e
FIPS_DNS_ADDR="127.0.0.1"
FIPS_DNS_PORT="5354"
FIPS_INTERFACE="fips0"
DNS_DELEGATE_DIR="/etc/systemd/dns-delegate"
DNS_DELEGATE_FILE="${DNS_DELEGATE_DIR}/fips.dns-delegate"
DNSMASQ_CONF="/etc/dnsmasq.d/fips.conf"
NM_DNSMASQ_CONF="/etc/NetworkManager/dnsmasq.d/fips.conf"
# Record which backend was configured for teardown.
STATE_DIR="/run/fips"
STATE_FILE="${STATE_DIR}/dns-backend"
log() { echo "fips-dns: $*"; }
save_backend() {
mkdir -p "$STATE_DIR"
echo "$1" > "$STATE_FILE"
}
# Wait for fips0 interface (up to 30s).
wait_for_interface() {
for i in $(seq 1 30); do
ip link show "$FIPS_INTERFACE" >/dev/null 2>&1 && return 0
sleep 1
done
log "ERROR: $FIPS_INTERFACE interface not found after 30s"
return 1
}
# Get systemd version number.
systemd_version() {
systemctl --version 2>/dev/null | head -1 | grep -oE '[0-9]+' | head -1
}
# Check if a systemd service is active.
is_active() {
systemctl is-active --quiet "$1" 2>/dev/null
}
# Backend 1: systemd dns-delegate (>= 258)
try_dns_delegate() {
local ver
ver=$(systemd_version)
[ -z "$ver" ] && return 1
[ "$ver" -lt 258 ] && return 1
is_active systemd-resolved.service || return 1
log "Configuring via dns-delegate (systemd $ver)"
mkdir -p "$DNS_DELEGATE_DIR"
cat > "$DNS_DELEGATE_FILE" <<EOF
[Delegate]
DNS=${FIPS_DNS_ADDR}:${FIPS_DNS_PORT}
Domains=fips
EOF
systemctl restart systemd-resolved
save_backend "dns-delegate"
return 0
}
# Backend 2: resolvectl
try_resolvectl() {
command -v resolvectl >/dev/null 2>&1 || return 1
is_active systemd-resolved.service || return 1
log "Configuring via resolvectl"
resolvectl dns "$FIPS_INTERFACE" "${FIPS_DNS_ADDR}:${FIPS_DNS_PORT}"
resolvectl domain "$FIPS_INTERFACE" "~fips"
save_backend "resolvectl"
return 0
}
# Backend 3: standalone dnsmasq
try_dnsmasq() {
is_active dnsmasq.service || return 1
[ -d /etc/dnsmasq.d ] || return 1
log "Configuring via dnsmasq"
cat > "$DNSMASQ_CONF" <<EOF
# FIPS .fips domain forwarding (managed by fips-dns.service)
server=/fips/${FIPS_DNS_ADDR}#${FIPS_DNS_PORT}
EOF
systemctl reload dnsmasq || log "WARNING: dnsmasq reload failed (config written, may need manual reload)"
save_backend "dnsmasq"
return 0
}
# Backend 4: NetworkManager with dnsmasq plugin
try_nm_dnsmasq() {
is_active NetworkManager.service || return 1
# Check if NM is configured to use dnsmasq
local nm_dns
nm_dns=$(grep -sh '^dns=' /etc/NetworkManager/NetworkManager.conf \
/etc/NetworkManager/conf.d/*.conf 2>/dev/null | tail -1 | cut -d= -f2)
[ "$nm_dns" = "dnsmasq" ] || return 1
[ -d /etc/NetworkManager/dnsmasq.d ] || return 1
log "Configuring via NetworkManager dnsmasq plugin"
cat > "$NM_DNSMASQ_CONF" <<EOF
# FIPS .fips domain forwarding (managed by fips-dns.service)
server=/fips/${FIPS_DNS_ADDR}#${FIPS_DNS_PORT}
EOF
nmcli general reload || log "WARNING: NetworkManager reload failed (config written, may need manual reload)"
save_backend "nm-dnsmasq"
return 0
}
# --- Main ---
wait_for_interface || exit 1
try_dns_delegate && exit 0
try_resolvectl && exit 0
try_dnsmasq && exit 0
try_nm_dnsmasq && exit 0
log "WARNING: No supported DNS resolver detected."
log "To resolve .fips domains, configure your DNS resolver to forward"
log "the .fips domain to ${FIPS_DNS_ADDR} port ${FIPS_DNS_PORT}."
log ""
log "Examples:"
log " systemd-resolved: sudo apt install systemd-resolved"
log " dnsmasq: echo 'server=/fips/${FIPS_DNS_ADDR}#${FIPS_DNS_PORT}' | sudo tee /etc/dnsmasq.d/fips.conf"
save_backend "none"
exit 0
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# fips-dns-teardown — Remove DNS routing for the .fips domain.
#
# Reverses whatever fips-dns-setup configured. Reads the backend from
# the state file, or cleans up all possible backends if state is missing.
set -e
FIPS_INTERFACE="fips0"
DNS_DELEGATE_FILE="/etc/systemd/dns-delegate/fips.dns-delegate"
DNSMASQ_CONF="/etc/dnsmasq.d/fips.conf"
NM_DNSMASQ_CONF="/etc/NetworkManager/dnsmasq.d/fips.conf"
STATE_FILE="/run/fips/dns-backend"
log() { echo "fips-dns: $*"; }
is_active() {
systemctl is-active --quiet "$1" 2>/dev/null
}
teardown_dns_delegate() {
[ -f "$DNS_DELEGATE_FILE" ] || return 0
log "Removing dns-delegate config"
rm -f "$DNS_DELEGATE_FILE"
is_active systemd-resolved.service && systemctl restart systemd-resolved
return 0
}
teardown_resolvectl() {
command -v resolvectl >/dev/null 2>&1 || return 0
is_active systemd-resolved.service || return 0
ip link show "$FIPS_INTERFACE" >/dev/null 2>&1 || return 0
log "Reverting resolvectl config"
resolvectl revert "$FIPS_INTERFACE" 2>/dev/null || true
return 0
}
teardown_dnsmasq() {
[ -f "$DNSMASQ_CONF" ] || return 0
log "Removing dnsmasq config"
rm -f "$DNSMASQ_CONF"
is_active dnsmasq.service && systemctl reload dnsmasq || true
return 0
}
teardown_nm_dnsmasq() {
[ -f "$NM_DNSMASQ_CONF" ] || return 0
log "Removing NetworkManager dnsmasq config"
rm -f "$NM_DNSMASQ_CONF"
is_active NetworkManager.service && nmcli general reload 2>/dev/null || true
return 0
}
# --- Main ---
backend=""
if [ -f "$STATE_FILE" ]; then
backend=$(cat "$STATE_FILE")
fi
case "$backend" in
dns-delegate) teardown_dns_delegate ;;
resolvectl) teardown_resolvectl ;;
dnsmasq) teardown_dnsmasq ;;
nm-dnsmasq) teardown_nm_dnsmasq ;;
none) ;; # Nothing was configured
*)
# State unknown — clean up everything
teardown_dns_delegate
teardown_resolvectl
teardown_dnsmasq
teardown_nm_dnsmasq
;;
esac
rm -f "$STATE_FILE"
exit 0
+3 -13
View File
@@ -1,22 +1,12 @@
[Unit]
Description=Configure DNS routing for .fips domain
After=fips.service systemd-resolved.service
After=fips.service
Requires=fips.service
ConditionPathExists=/run/systemd/resolve
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c '\
for i in $(seq 1 30); do \
ip link show fips0 >/dev/null 2>&1 && break; \
sleep 1; \
done; \
if ! ip link show fips0 >/dev/null 2>&1; then \
echo "fips0 interface not found after 30s" >&2; \
exit 1; \
fi; \
/usr/bin/resolvectl dns fips0 127.0.0.1:5354; \
/usr/bin/resolvectl domain fips0 ~fips'
ExecStart=/usr/lib/fips/fips-dns-setup
ExecStop=/usr/lib/fips/fips-dns-teardown
RemainAfterExit=yes
[Install]
+1 -3
View File
@@ -18,9 +18,7 @@ case "$1" in
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
systemctl enable fips.service 2>/dev/null || true
if systemctl is-active --quiet systemd-resolved.service 2>/dev/null; then
systemctl enable fips-dns.service 2>/dev/null || true
fi
systemctl enable fips-dns.service 2>/dev/null || true
# On upgrade, restart services that were running before
if [ -n "$2" ]; then
+5
View File
@@ -13,6 +13,11 @@ case "$1" in
# Remove runtime directory
rm -rf /run/fips/
# Remove DNS config files that fips-dns-setup may have created
rm -f /etc/systemd/dns-delegate/fips.dns-delegate
rm -f /etc/dnsmasq.d/fips.conf
rm -f /etc/NetworkManager/dnsmasq.d/fips.conf
# Remove fips system group
if getent group fips >/dev/null 2>&1; then
groupdel fips 2>/dev/null || true
+3 -13
View File
@@ -1,22 +1,12 @@
[Unit]
Description=Configure DNS routing for .fips domain
After=fips.service systemd-resolved.service
After=fips.service
Requires=fips.service
ConditionPathExists=/run/systemd/resolve
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c '\
for i in $(seq 1 30); do \
ip link show fips0 >/dev/null 2>&1 && break; \
sleep 1; \
done; \
if ! ip link show fips0 >/dev/null 2>&1; then \
echo "fips0 interface not found after 30s" >&2; \
exit 1; \
fi; \
/usr/bin/resolvectl dns fips0 127.0.0.1:5354; \
/usr/bin/resolvectl domain fips0 ~fips'
ExecStart=/usr/lib/fips/fips-dns-setup
ExecStop=/usr/lib/fips/fips-dns-teardown
RemainAfterExit=yes
[Install]
+6 -9
View File
@@ -100,8 +100,11 @@ fi
install -m 0644 "${SCRIPT_DIR}/fips.service" "${SYSTEMD_DIR}/fips.service"
install -m 0644 "${SCRIPT_DIR}/fips-dns.service" "${SYSTEMD_DIR}/fips-dns.service"
install -d -m 0755 /usr/lib/fips
install -m 0755 "${SCRIPT_DIR}/../common/fips-dns-setup" /usr/lib/fips/fips-dns-setup
install -m 0755 "${SCRIPT_DIR}/../common/fips-dns-teardown" /usr/lib/fips/fips-dns-teardown
systemctl daemon-reload
echo "systemd units installed."
echo "systemd units and DNS scripts installed."
# --- Configure runtime directory group ownership ---
# systemd creates /run/fips/ with RuntimeDirectory, but we need the
@@ -116,14 +119,8 @@ echo "tmpfiles.d entry created for /run/fips/ ownership."
# --- Enable service ---
systemctl enable fips.service
if systemctl is-active --quiet systemd-resolved.service 2>/dev/null; then
systemctl enable fips-dns.service
echo "Services enabled (will start on boot)."
else
echo "fips.service enabled (will start on boot)."
echo " Note: fips-dns.service not enabled (systemd-resolved is not running)."
echo " To enable .fips DNS later: sudo systemctl enable --now fips-dns.service"
fi
systemctl enable fips-dns.service
echo "Services enabled (will start on boot)."
# Restart if they were running before
if $was_active; then
+7 -1
View File
@@ -42,8 +42,14 @@ fi
rm -f /etc/systemd/system/fips.service
rm -f /etc/systemd/system/fips-dns.service
rm -rf /usr/lib/fips/
systemctl daemon-reload
echo "systemd units removed."
echo "systemd units and DNS scripts removed."
# Clean up DNS config files that fips-dns-setup may have created
rm -f /etc/systemd/dns-delegate/fips.dns-delegate
rm -f /etc/dnsmasq.d/fips.conf
rm -f /etc/NetworkManager/dnsmasq.d/fips.conf
# --- Remove tmpfiles.d entry ---