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:
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