mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
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:
@@ -74,6 +74,8 @@ assets = [
|
|||||||
["packaging/common/hosts", "/etc/fips/hosts", "644"],
|
["packaging/common/hosts", "/etc/fips/hosts", "644"],
|
||||||
["packaging/debian/fips.service", "/lib/systemd/system/fips.service", "644"],
|
["packaging/debian/fips.service", "/lib/systemd/system/fips.service", "644"],
|
||||||
["packaging/debian/fips-dns.service", "/lib/systemd/system/fips-dns.service", "644"],
|
["packaging/debian/fips-dns.service", "/lib/systemd/system/fips-dns.service", "644"],
|
||||||
|
["packaging/common/fips-dns-setup", "/usr/lib/fips/fips-dns-setup", "755"],
|
||||||
|
["packaging/common/fips-dns-teardown", "/usr/lib/fips/fips-dns-teardown", "755"],
|
||||||
["packaging/debian/fips.tmpfiles", "/usr/lib/tmpfiles.d/fips.conf", "644"],
|
["packaging/debian/fips.tmpfiles", "/usr/lib/tmpfiles.d/fips.conf", "644"],
|
||||||
["target/release/fips-gateway", "/usr/bin/", "755"],
|
["target/release/fips-gateway", "/usr/bin/", "755"],
|
||||||
["packaging/debian/fips-gateway.service", "/lib/systemd/system/fips-gateway.service", "644"],
|
["packaging/debian/fips-gateway.service", "/lib/systemd/system/fips-gateway.service", "644"],
|
||||||
|
|||||||
Executable
+141
@@ -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
|
||||||
Executable
+77
@@ -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
|
||||||
@@ -1,22 +1,12 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Configure DNS routing for .fips domain
|
Description=Configure DNS routing for .fips domain
|
||||||
After=fips.service systemd-resolved.service
|
After=fips.service
|
||||||
Requires=fips.service
|
Requires=fips.service
|
||||||
ConditionPathExists=/run/systemd/resolve
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/bash -c '\
|
ExecStart=/usr/lib/fips/fips-dns-setup
|
||||||
for i in $(seq 1 30); do \
|
ExecStop=/usr/lib/fips/fips-dns-teardown
|
||||||
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'
|
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ case "$1" in
|
|||||||
if [ -d /run/systemd/system ]; then
|
if [ -d /run/systemd/system ]; then
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable fips.service 2>/dev/null || true
|
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
|
||||||
systemctl enable fips-dns.service 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# On upgrade, restart services that were running before
|
# On upgrade, restart services that were running before
|
||||||
if [ -n "$2" ]; then
|
if [ -n "$2" ]; then
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ case "$1" in
|
|||||||
# Remove runtime directory
|
# Remove runtime directory
|
||||||
rm -rf /run/fips/
|
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
|
# Remove fips system group
|
||||||
if getent group fips >/dev/null 2>&1; then
|
if getent group fips >/dev/null 2>&1; then
|
||||||
groupdel fips 2>/dev/null || true
|
groupdel fips 2>/dev/null || true
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Configure DNS routing for .fips domain
|
Description=Configure DNS routing for .fips domain
|
||||||
After=fips.service systemd-resolved.service
|
After=fips.service
|
||||||
Requires=fips.service
|
Requires=fips.service
|
||||||
ConditionPathExists=/run/systemd/resolve
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/bash -c '\
|
ExecStart=/usr/lib/fips/fips-dns-setup
|
||||||
for i in $(seq 1 30); do \
|
ExecStop=/usr/lib/fips/fips-dns-teardown
|
||||||
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'
|
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -100,8 +100,11 @@ fi
|
|||||||
|
|
||||||
install -m 0644 "${SCRIPT_DIR}/fips.service" "${SYSTEMD_DIR}/fips.service"
|
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 -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
|
systemctl daemon-reload
|
||||||
echo "systemd units installed."
|
echo "systemd units and DNS scripts installed."
|
||||||
|
|
||||||
# --- Configure runtime directory group ownership ---
|
# --- Configure runtime directory group ownership ---
|
||||||
# systemd creates /run/fips/ with RuntimeDirectory, but we need the
|
# 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 ---
|
# --- Enable service ---
|
||||||
|
|
||||||
systemctl enable fips.service
|
systemctl enable fips.service
|
||||||
if systemctl is-active --quiet systemd-resolved.service 2>/dev/null; then
|
systemctl enable fips-dns.service
|
||||||
systemctl enable fips-dns.service
|
echo "Services enabled (will start on boot)."
|
||||||
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
|
|
||||||
|
|
||||||
# Restart if they were running before
|
# Restart if they were running before
|
||||||
if $was_active; then
|
if $was_active; then
|
||||||
|
|||||||
@@ -42,8 +42,14 @@ fi
|
|||||||
|
|
||||||
rm -f /etc/systemd/system/fips.service
|
rm -f /etc/systemd/system/fips.service
|
||||||
rm -f /etc/systemd/system/fips-dns.service
|
rm -f /etc/systemd/system/fips-dns.service
|
||||||
|
rm -rf /usr/lib/fips/
|
||||||
systemctl daemon-reload
|
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 ---
|
# --- Remove tmpfiles.d entry ---
|
||||||
|
|
||||||
|
|||||||
Executable
+390
@@ -0,0 +1,390 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Test fips-dns-setup across different Linux resolver backends.
|
||||||
|
#
|
||||||
|
# Each scenario runs a systemd-based Docker container, creates a dummy
|
||||||
|
# fips0 interface, runs the setup script, verifies the detected backend
|
||||||
|
# and generated config, runs teardown, and verifies cleanup.
|
||||||
|
#
|
||||||
|
# Usage: ./test.sh [scenario ...]
|
||||||
|
# No args = run all scenarios.
|
||||||
|
# Named args = run only those (e.g., ./test.sh debian12-resolved dnsmasq)
|
||||||
|
#
|
||||||
|
# Requirements: Docker with privileged container support.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
SETUP_SCRIPT="$REPO_ROOT/packaging/common/fips-dns-setup"
|
||||||
|
TEARDOWN_SCRIPT="$REPO_ROOT/packaging/common/fips-dns-teardown"
|
||||||
|
|
||||||
|
# Timeout for systemd boot inside container
|
||||||
|
BOOT_TIMEOUT=30
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
SKIP=0
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Helpers
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
log() { echo "=== $*"; }
|
||||||
|
pass() { echo " PASS: $*"; PASS=$((PASS + 1)); }
|
||||||
|
fail() { echo " FAIL: $*"; FAIL=$((FAIL + 1)); }
|
||||||
|
skip() { echo " SKIP: $*"; SKIP=$((SKIP + 1)); }
|
||||||
|
|
||||||
|
cleanup_container() {
|
||||||
|
local name="$1"
|
||||||
|
docker rm -f "$name" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build an image from an inline Dockerfile.
|
||||||
|
build_image() {
|
||||||
|
local tag="$1"
|
||||||
|
shift
|
||||||
|
echo "$@" | docker build -t "$tag" -f - "$REPO_ROOT" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start a systemd container in the background.
|
||||||
|
start_systemd_container() {
|
||||||
|
local name="$1" image="$2"
|
||||||
|
cleanup_container "$name"
|
||||||
|
docker run -d --name "$name" \
|
||||||
|
--privileged \
|
||||||
|
--cgroupns=host \
|
||||||
|
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
||||||
|
--tmpfs /run --tmpfs /run/lock \
|
||||||
|
"$image" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for systemd to reach a bootable state inside the container.
|
||||||
|
wait_for_systemd() {
|
||||||
|
local name="$1"
|
||||||
|
for _i in $(seq 1 "$BOOT_TIMEOUT"); do
|
||||||
|
if docker exec "$name" systemctl is-system-running --wait 2>/dev/null | grep -qE 'running|degraded'; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " WARNING: systemd did not reach running state in ${BOOT_TIMEOUT}s (may still work)"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create dummy fips0 interface inside the container.
|
||||||
|
create_fips0() {
|
||||||
|
local name="$1"
|
||||||
|
docker exec "$name" ip link add fips0 type dummy 2>/dev/null
|
||||||
|
docker exec "$name" ip link set fips0 up 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy scripts into the container and run setup.
|
||||||
|
run_setup() {
|
||||||
|
local name="$1"
|
||||||
|
docker cp "$SETUP_SCRIPT" "$name:/usr/local/bin/fips-dns-setup"
|
||||||
|
docker cp "$TEARDOWN_SCRIPT" "$name:/usr/local/bin/fips-dns-teardown"
|
||||||
|
docker exec "$name" chmod +x /usr/local/bin/fips-dns-setup /usr/local/bin/fips-dns-teardown
|
||||||
|
# Exit code may be non-zero due to service reload failures in containers.
|
||||||
|
# We test detection and config generation, not service operation.
|
||||||
|
docker exec "$name" /usr/local/bin/fips-dns-setup 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
run_teardown() {
|
||||||
|
local name="$1"
|
||||||
|
docker exec "$name" /usr/local/bin/fips-dns-teardown 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
get_backend() {
|
||||||
|
local name="$1"
|
||||||
|
docker exec "$name" cat /run/fips/dns-backend 2>/dev/null || echo "(missing)"
|
||||||
|
}
|
||||||
|
|
||||||
|
file_exists() {
|
||||||
|
local name="$1" path="$2"
|
||||||
|
docker exec "$name" test -f "$path" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Scenarios
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test_debian12_resolved() {
|
||||||
|
local name="fips-dns-test-deb12-resolved"
|
||||||
|
local image="fips-dns-test:debian12-resolved"
|
||||||
|
log "Debian 12 + systemd-resolved"
|
||||||
|
|
||||||
|
build_image "$image" "$(cat <<'DOCKERFILE'
|
||||||
|
FROM debian:12
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
systemd systemd-resolved iproute2 dbus && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
systemctl enable systemd-resolved
|
||||||
|
CMD ["/lib/systemd/systemd"]
|
||||||
|
DOCKERFILE
|
||||||
|
)" || { fail "build failed"; return; }
|
||||||
|
|
||||||
|
start_systemd_container "$name" "$image"
|
||||||
|
wait_for_systemd "$name"
|
||||||
|
create_fips0 "$name"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(run_setup "$name" 2>&1)
|
||||||
|
echo " output: $output"
|
||||||
|
|
||||||
|
local backend
|
||||||
|
backend=$(get_backend "$name")
|
||||||
|
if [ "$backend" = "resolvectl" ]; then
|
||||||
|
pass "detected backend: resolvectl"
|
||||||
|
else
|
||||||
|
fail "expected resolvectl, got: $backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Teardown
|
||||||
|
run_teardown "$name" >/dev/null 2>&1
|
||||||
|
if ! file_exists "$name" /run/fips/dns-backend; then
|
||||||
|
pass "teardown cleaned state file"
|
||||||
|
else
|
||||||
|
fail "state file still exists after teardown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_container "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_debian13_resolved() {
|
||||||
|
local name="fips-dns-test-deb13-resolved"
|
||||||
|
local image="fips-dns-test:debian13-resolved"
|
||||||
|
log "Debian 13 (trixie) + systemd-resolved"
|
||||||
|
|
||||||
|
build_image "$image" "$(cat <<'DOCKERFILE'
|
||||||
|
FROM debian:trixie
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
systemd systemd-resolved iproute2 dbus && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
systemctl enable systemd-resolved
|
||||||
|
CMD ["/lib/systemd/systemd"]
|
||||||
|
DOCKERFILE
|
||||||
|
)" || { fail "build failed"; return; }
|
||||||
|
|
||||||
|
start_systemd_container "$name" "$image"
|
||||||
|
wait_for_systemd "$name"
|
||||||
|
create_fips0 "$name"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(run_setup "$name" 2>&1)
|
||||||
|
echo " output: $output"
|
||||||
|
|
||||||
|
local backend
|
||||||
|
backend=$(get_backend "$name")
|
||||||
|
if [ "$backend" = "resolvectl" ]; then
|
||||||
|
pass "detected backend: resolvectl"
|
||||||
|
else
|
||||||
|
fail "expected resolvectl, got: $backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_teardown "$name" >/dev/null 2>&1
|
||||||
|
if ! file_exists "$name" /run/fips/dns-backend; then
|
||||||
|
pass "teardown cleaned state file"
|
||||||
|
else
|
||||||
|
fail "state file still exists after teardown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_container "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_dnsmasq() {
|
||||||
|
local name="fips-dns-test-dnsmasq"
|
||||||
|
local image="fips-dns-test:dnsmasq"
|
||||||
|
log "Debian 12 + dnsmasq standalone"
|
||||||
|
|
||||||
|
build_image "$image" "$(cat <<'DOCKERFILE'
|
||||||
|
FROM debian:12
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
systemd dnsmasq iproute2 dbus && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
systemctl enable dnsmasq && \
|
||||||
|
mkdir -p /etc/dnsmasq.d
|
||||||
|
CMD ["/lib/systemd/systemd"]
|
||||||
|
DOCKERFILE
|
||||||
|
)" || { fail "build failed"; return; }
|
||||||
|
|
||||||
|
start_systemd_container "$name" "$image"
|
||||||
|
wait_for_systemd "$name"
|
||||||
|
create_fips0 "$name"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(run_setup "$name" 2>&1)
|
||||||
|
echo " output: $output"
|
||||||
|
|
||||||
|
local backend
|
||||||
|
backend=$(get_backend "$name")
|
||||||
|
if [ "$backend" = "dnsmasq" ]; then
|
||||||
|
pass "detected backend: dnsmasq"
|
||||||
|
else
|
||||||
|
fail "expected dnsmasq, got: $backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify config file was written
|
||||||
|
if file_exists "$name" /etc/dnsmasq.d/fips.conf; then
|
||||||
|
pass "dnsmasq config written"
|
||||||
|
echo " config: $(docker exec "$name" cat /etc/dnsmasq.d/fips.conf)"
|
||||||
|
else
|
||||||
|
fail "dnsmasq config not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Teardown
|
||||||
|
run_teardown "$name" >/dev/null 2>&1
|
||||||
|
if ! file_exists "$name" /etc/dnsmasq.d/fips.conf; then
|
||||||
|
pass "teardown removed dnsmasq config"
|
||||||
|
else
|
||||||
|
fail "dnsmasq config still exists after teardown"
|
||||||
|
fi
|
||||||
|
if ! file_exists "$name" /run/fips/dns-backend; then
|
||||||
|
pass "teardown cleaned state file"
|
||||||
|
else
|
||||||
|
fail "state file still exists after teardown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_container "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_nm_dnsmasq() {
|
||||||
|
local name="fips-dns-test-nm-dnsmasq"
|
||||||
|
local image="fips-dns-test:nm-dnsmasq"
|
||||||
|
log "Fedora + NetworkManager + dnsmasq plugin"
|
||||||
|
|
||||||
|
build_image "$image" "$(cat <<'DOCKERFILE'
|
||||||
|
FROM fedora:latest
|
||||||
|
RUN dnf install -y systemd NetworkManager dnsmasq iproute && \
|
||||||
|
dnf clean all && \
|
||||||
|
mkdir -p /etc/NetworkManager/conf.d /etc/NetworkManager/dnsmasq.d && \
|
||||||
|
printf '[main]\ndns=dnsmasq\n' > /etc/NetworkManager/conf.d/dns.conf && \
|
||||||
|
systemctl enable NetworkManager && \
|
||||||
|
systemctl disable systemd-resolved && \
|
||||||
|
systemctl mask systemd-resolved
|
||||||
|
CMD ["/sbin/init"]
|
||||||
|
DOCKERFILE
|
||||||
|
)" || { fail "build failed"; return; }
|
||||||
|
|
||||||
|
start_systemd_container "$name" "$image"
|
||||||
|
wait_for_systemd "$name"
|
||||||
|
create_fips0 "$name"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(run_setup "$name" 2>&1)
|
||||||
|
echo " output: $output"
|
||||||
|
|
||||||
|
local backend
|
||||||
|
backend=$(get_backend "$name")
|
||||||
|
if [ "$backend" = "nm-dnsmasq" ]; then
|
||||||
|
pass "detected backend: nm-dnsmasq"
|
||||||
|
else
|
||||||
|
fail "expected nm-dnsmasq, got: $backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if file_exists "$name" /etc/NetworkManager/dnsmasq.d/fips.conf; then
|
||||||
|
pass "NM dnsmasq config written"
|
||||||
|
echo " config: $(docker exec "$name" cat /etc/NetworkManager/dnsmasq.d/fips.conf)"
|
||||||
|
else
|
||||||
|
fail "NM dnsmasq config not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Teardown
|
||||||
|
run_teardown "$name" >/dev/null 2>&1
|
||||||
|
if ! file_exists "$name" /etc/NetworkManager/dnsmasq.d/fips.conf; then
|
||||||
|
pass "teardown removed NM dnsmasq config"
|
||||||
|
else
|
||||||
|
fail "NM dnsmasq config still exists after teardown"
|
||||||
|
fi
|
||||||
|
if ! file_exists "$name" /run/fips/dns-backend; then
|
||||||
|
pass "teardown cleaned state file"
|
||||||
|
else
|
||||||
|
fail "state file still exists after teardown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_container "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_no_resolver() {
|
||||||
|
local name="fips-dns-test-none"
|
||||||
|
local image="fips-dns-test:none"
|
||||||
|
log "Debian 12 bare (no resolver)"
|
||||||
|
|
||||||
|
build_image "$image" "$(cat <<'DOCKERFILE'
|
||||||
|
FROM debian:12
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
systemd iproute2 dbus && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
CMD ["/lib/systemd/systemd"]
|
||||||
|
DOCKERFILE
|
||||||
|
)" || { fail "build failed"; return; }
|
||||||
|
|
||||||
|
start_systemd_container "$name" "$image"
|
||||||
|
wait_for_systemd "$name"
|
||||||
|
create_fips0 "$name"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(run_setup "$name" 2>&1)
|
||||||
|
echo " output: $output"
|
||||||
|
|
||||||
|
local backend
|
||||||
|
backend=$(get_backend "$name")
|
||||||
|
if [ "$backend" = "none" ]; then
|
||||||
|
pass "detected backend: none (correct fallback)"
|
||||||
|
else
|
||||||
|
fail "expected none, got: $backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify it printed the warning
|
||||||
|
if echo "$output" | grep -q "No supported DNS resolver"; then
|
||||||
|
pass "printed manual instructions warning"
|
||||||
|
else
|
||||||
|
fail "missing manual instructions warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_teardown "$name" >/dev/null 2>&1
|
||||||
|
if ! file_exists "$name" /run/fips/dns-backend; then
|
||||||
|
pass "teardown cleaned state file"
|
||||||
|
else
|
||||||
|
fail "state file still exists after teardown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_container "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Main
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
ALL_SCENARIOS="debian12-resolved debian13-resolved dnsmasq nm-dnsmasq no-resolver"
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
scenarios="$ALL_SCENARIOS"
|
||||||
|
else
|
||||||
|
scenarios="$*"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for scenario in $scenarios; do
|
||||||
|
case "$scenario" in
|
||||||
|
debian12-resolved) test_debian12_resolved ;;
|
||||||
|
debian13-resolved) test_debian13_resolved ;;
|
||||||
|
dnsmasq) test_dnsmasq ;;
|
||||||
|
nm-dnsmasq) test_nm_dnsmasq ;;
|
||||||
|
no-resolver) test_no_resolver ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown scenario: $scenario"
|
||||||
|
echo "Available: $ALL_SCENARIOS"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "═══════════════════════════════════════"
|
||||||
|
echo "Results: $PASS passed, $FAIL failed, $SKIP skipped"
|
||||||
|
echo "═══════════════════════════════════════"
|
||||||
|
|
||||||
|
[ "$FAIL" -eq 0 ]
|
||||||
Reference in New Issue
Block a user