mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
Remove every DNS routing file on purge and uninstall, and restart the resolver that used it
When the package was purged, or the tarball uninstalled, while fips-dns was not running, fips-dns-teardown never ran and the DNS routing that fips-dns-setup wrote stayed behind. The cleanup in postrm purge and in uninstall.sh was meant to catch that case but removed the dns-delegate file from the wrong directory (dns-delegate/ instead of dns-delegate.d/), never removed the systemd-resolved global drop-in, and restarted no resolver. systemd-resolved kept sending .fips queries to [::1]:5354, where nothing listens any more, so .fips lookups timed out. Both scripts now remove all four files fips-dns-setup can write, and restart systemd-resolved, reload dnsmasq or reload NetworkManager when they removed that resolver's file and it is running. A failed restart prints a warning and does not fail the removal. A packaging test pins both scripts to the paths fips-dns-setup and fips-dns-teardown use, and the deb-install scenario now purges the package with the routing file in place and fips-dns stopped, checking the file is gone and systemd-resolved restarted. The CI comment on the arm64 leg is corrected to say that leg now runs that purge. No suite runs uninstall.sh, and the test's doc comment says so.
This commit is contained in:
@@ -1007,11 +1007,12 @@ jobs:
|
||||
- type: deb-install
|
||||
scenario: ubuntu26
|
||||
arch: amd64
|
||||
# The arm64 package on the oldest supported distribution: a fresh
|
||||
# install and a daemon start. Deliberately GitHub-only (the local host
|
||||
# is x86_64), and deliberately one leg: the upgrade, purge and
|
||||
# conffile paths run under debian12 on amd64 only and stay
|
||||
# unexercised on arm64.
|
||||
# The arm64 package on the oldest supported distribution: the install
|
||||
# scenario, which covers a fresh install, a daemon start and a purge
|
||||
# of the DNS routing. Deliberately GitHub-only (the local host is
|
||||
# x86_64), and deliberately one leg: the upgrade and conffile paths,
|
||||
# including the upgrade scenario's own purge, run under debian12 on
|
||||
# amd64 only and stay unexercised on arm64.
|
||||
- type: deb-install
|
||||
scenario: ubuntu22
|
||||
arch: arm64
|
||||
|
||||
+36
-1
@@ -13,10 +13,45 @@ case "$1" in
|
||||
# Remove runtime directory
|
||||
rm -rf /run/fips/
|
||||
|
||||
# Remove DNS config files that fips-dns-setup may have created
|
||||
# Remove the DNS routing fips-dns-setup may have written, in case
|
||||
# fips-dns-teardown did not run (prerm's stop runs it only when
|
||||
# fips-dns.service was active), and make the resolver drop it. The
|
||||
# paths match packaging/common/fips-dns-teardown, which dpkg has
|
||||
# already removed, so it cannot be called from here.
|
||||
restart_resolved=0
|
||||
if [ -f /etc/systemd/dns-delegate.d/fips.dns-delegate ]; then
|
||||
rm -f /etc/systemd/dns-delegate.d/fips.dns-delegate
|
||||
restart_resolved=1
|
||||
fi
|
||||
if [ -f /etc/systemd/resolved.conf.d/fips.conf ]; then
|
||||
rm -f /etc/systemd/resolved.conf.d/fips.conf
|
||||
restart_resolved=1
|
||||
fi
|
||||
# Only pre-v0.3.0 development builds wrote this path, and systemd
|
||||
# never read it.
|
||||
rm -f /etc/systemd/dns-delegate/fips.dns-delegate
|
||||
if [ "$restart_resolved" = 1 ] && [ -d /run/systemd/system ] \
|
||||
&& systemctl is-active --quiet systemd-resolved.service; then
|
||||
systemctl restart systemd-resolved \
|
||||
|| echo "fips: warning: could not restart systemd-resolved; restart it to drop the .fips route"
|
||||
fi
|
||||
if [ -f /etc/dnsmasq.d/fips.conf ]; then
|
||||
rm -f /etc/dnsmasq.d/fips.conf
|
||||
if [ -d /run/systemd/system ] \
|
||||
&& systemctl is-active --quiet dnsmasq.service; then
|
||||
systemctl reload dnsmasq \
|
||||
|| echo "fips: warning: could not reload dnsmasq; reload it to drop the .fips route"
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/NetworkManager/dnsmasq.d/fips.conf ]; then
|
||||
rm -f /etc/NetworkManager/dnsmasq.d/fips.conf
|
||||
if [ -d /run/systemd/system ] \
|
||||
&& systemctl is-active --quiet NetworkManager.service \
|
||||
&& command -v nmcli >/dev/null 2>&1; then
|
||||
nmcli general reload \
|
||||
|| echo "fips: warning: could not reload NetworkManager; reload it to drop the .fips route"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove fips system group
|
||||
if getent group fips >/dev/null 2>&1; then
|
||||
|
||||
@@ -42,10 +42,54 @@ rm -rf /usr/lib/fips/
|
||||
systemctl daemon-reload
|
||||
echo "systemd units and DNS scripts removed."
|
||||
|
||||
# Clean up DNS config files that fips-dns-setup may have created
|
||||
# --- Remove DNS routing ---
|
||||
# fips-dns-setup may have written one of these, and stopping fips-dns.service
|
||||
# above runs fips-dns-teardown only when the unit was active. The paths match
|
||||
# packaging/common/fips-dns-teardown.
|
||||
|
||||
restart_resolved=false
|
||||
if [ -f /etc/systemd/dns-delegate.d/fips.dns-delegate ]; then
|
||||
rm -f /etc/systemd/dns-delegate.d/fips.dns-delegate
|
||||
echo "Removed /etc/systemd/dns-delegate.d/fips.dns-delegate."
|
||||
restart_resolved=true
|
||||
fi
|
||||
if [ -f /etc/systemd/resolved.conf.d/fips.conf ]; then
|
||||
rm -f /etc/systemd/resolved.conf.d/fips.conf
|
||||
echo "Removed /etc/systemd/resolved.conf.d/fips.conf."
|
||||
restart_resolved=true
|
||||
fi
|
||||
# Only pre-v0.3.0 development builds wrote this path, and systemd never read it.
|
||||
rm -f /etc/systemd/dns-delegate/fips.dns-delegate
|
||||
rm -f /etc/dnsmasq.d/fips.conf
|
||||
rm -f /etc/NetworkManager/dnsmasq.d/fips.conf
|
||||
if $restart_resolved && systemctl is-active --quiet systemd-resolved.service 2>/dev/null; then
|
||||
if systemctl restart systemd-resolved; then
|
||||
echo "systemd-resolved restarted."
|
||||
else
|
||||
echo "Warning: could not restart systemd-resolved; restart it to drop the .fips route." >&2
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/dnsmasq.d/fips.conf ]; then
|
||||
rm -f /etc/dnsmasq.d/fips.conf
|
||||
echo "Removed /etc/dnsmasq.d/fips.conf."
|
||||
if systemctl is-active --quiet dnsmasq.service 2>/dev/null; then
|
||||
if systemctl reload dnsmasq; then
|
||||
echo "dnsmasq reloaded."
|
||||
else
|
||||
echo "Warning: could not reload dnsmasq; reload it to drop the .fips route." >&2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/NetworkManager/dnsmasq.d/fips.conf ]; then
|
||||
rm -f /etc/NetworkManager/dnsmasq.d/fips.conf
|
||||
echo "Removed /etc/NetworkManager/dnsmasq.d/fips.conf."
|
||||
if systemctl is-active --quiet NetworkManager.service 2>/dev/null \
|
||||
&& command -v nmcli >/dev/null 2>&1; then
|
||||
if nmcli general reload; then
|
||||
echo "NetworkManager reloaded."
|
||||
else
|
||||
echo "Warning: could not reload NetworkManager; reload it to drop the .fips route." >&2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Remove tmpfiles.d entry ---
|
||||
|
||||
|
||||
@@ -161,6 +161,33 @@ fn logical_lines(sh: &str) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Returns the logical lines of a shell script, trimmed at both ends, without
|
||||
/// the lines that are comments.
|
||||
fn code_lines(sh: &str) -> Vec<String> {
|
||||
logical_lines(sh)
|
||||
.into_iter()
|
||||
.map(|l| l.trim().to_string())
|
||||
.filter(|l| !l.starts_with('#'))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the code lines of the `case` branch `label)` in a shell script:
|
||||
/// those after the line that trims to `label)`, up to the next line that trims
|
||||
/// to `;;`.
|
||||
fn case_branch(sh: &str, label: &str) -> Vec<String> {
|
||||
let open = format!("{label})");
|
||||
let lines = code_lines(sh);
|
||||
let start = 1 + lines
|
||||
.iter()
|
||||
.position(|l| *l == open)
|
||||
.unwrap_or_else(|| panic!("no `{open}` branch found"));
|
||||
let len = lines[start..]
|
||||
.iter()
|
||||
.position(|l| l == ";;")
|
||||
.unwrap_or_else(|| panic!("`{open}` branch is never closed with `;;`"));
|
||||
lines[start..start + len].to_vec()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deb_and_aur_packages_declare_nftables_for_the_firewall_units_nft() {
|
||||
let unit = repo_file("packaging/debian/fips-firewall.service");
|
||||
@@ -316,3 +343,77 @@ fn freebsd_newsyslog_entry_signals_the_daemon8_supervisor_started_with_sighup_re
|
||||
"build-pkg.sh pkg-plist does not list etc/newsyslog.conf.d/fips.conf: {plist:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Pins the DNS cleanup in `postrm purge` and `uninstall.sh` to the files
|
||||
/// `fips-dns-setup` writes, so a purge after a `fips-dns` that never ran its
|
||||
/// teardown does not leave the resolver sending `.fips` to a dead responder.
|
||||
///
|
||||
/// This is a text test. Each path must appear on an `rm -f` line, but a
|
||||
/// resolver command passes wherever it appears on a code line, including in a
|
||||
/// message. What `postrm` actually does is covered by the deb-install purge
|
||||
/// check. No suite runs `uninstall.sh`: its two resolved paths were run once,
|
||||
/// by hand in a container, and its dnsmasq and NetworkManager paths by nothing.
|
||||
#[test]
|
||||
fn dns_cleanup_in_postrm_purge_and_uninstall_removes_every_file_fips_dns_setup_writes_and_restarts_its_resolver()
|
||||
{
|
||||
let setup = rc_vars(&repo_file("packaging/common/fips-dns-setup"));
|
||||
let teardown = rc_vars(&repo_file("packaging/common/fips-dns-teardown"));
|
||||
let paths: Vec<&str> = [
|
||||
"DNS_DELEGATE_FILE",
|
||||
"RESOLVED_DROPIN_FILE",
|
||||
"DNSMASQ_CONF",
|
||||
"NM_DNSMASQ_CONF",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let path = setup
|
||||
.get(name)
|
||||
.filter(|p| p.starts_with('/'))
|
||||
.unwrap_or_else(|| panic!("fips-dns-setup sets no absolute {name}"));
|
||||
assert_eq!(
|
||||
teardown.get(name),
|
||||
Some(path),
|
||||
"fips-dns-teardown's {name} is not the file fips-dns-setup writes"
|
||||
);
|
||||
path.as_str()
|
||||
})
|
||||
.collect();
|
||||
let commands = [
|
||||
"restart systemd-resolved",
|
||||
"reload dnsmasq",
|
||||
"nmcli general reload",
|
||||
];
|
||||
|
||||
let scripts = [
|
||||
(
|
||||
"packaging/debian/postrm purge)",
|
||||
case_branch(&repo_file("packaging/debian/postrm"), "purge"),
|
||||
),
|
||||
(
|
||||
"packaging/systemd/uninstall.sh",
|
||||
code_lines(&repo_file("packaging/systemd/uninstall.sh")),
|
||||
),
|
||||
];
|
||||
let mut missing = Vec::new();
|
||||
for (script, lines) in &scripts {
|
||||
for path in &paths {
|
||||
if !lines
|
||||
.iter()
|
||||
.any(|l| l.contains("rm -f") && l.contains(path))
|
||||
{
|
||||
missing.push(format!("{script}: no `rm -f` of {path}"));
|
||||
}
|
||||
}
|
||||
for command in commands {
|
||||
if !lines.iter().any(|l| l.contains(command)) {
|
||||
missing.push(format!("{script}: never runs `{command}`"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
missing.is_empty(),
|
||||
"DNS cleanup does not match the files fips-dns-setup writes and the resolvers \
|
||||
fips-dns-teardown restarts:\n {}",
|
||||
missing.join("\n ")
|
||||
);
|
||||
}
|
||||
|
||||
+111
-1
@@ -8,13 +8,16 @@
|
||||
# to come up, and verifies that `dig @127.0.0.53 AAAA <npub>.fips`
|
||||
# returns a non-empty AAAA answer through the resolver backend that
|
||||
# fips-dns-setup configured. Then exercises fips-gateway against the
|
||||
# same daemon to verify the gateway/daemon default-pairing.
|
||||
# same daemon to verify the gateway/daemon default-pairing. Finally it
|
||||
# purges the package with the DNS routing file planted and fips-dns
|
||||
# stopped, and checks the file is removed and systemd-resolved restarted.
|
||||
#
|
||||
# This is the most thorough test surface — it exercises:
|
||||
# - cargo deb packaging (binary stripping, dependency declaration)
|
||||
# - dpkg conffile placement (/etc/fips/fips.yaml)
|
||||
# - postinst maintainer scripts (systemd unit enablement,
|
||||
# fips-dns.service running fips-dns-setup)
|
||||
# - postrm purge (removing the DNS routing fips-dns-setup wrote)
|
||||
# - The fips, fips-dns, and (optionally) fips-gateway systemd units
|
||||
# - End-to-end .fips resolution as a real user would experience it
|
||||
#
|
||||
@@ -301,6 +304,111 @@ EOF
|
||||
return
|
||||
}
|
||||
|
||||
# Purge the package with the DNS routing file planted and fips-dns stopped, and
|
||||
# check that postrm removes the file and restarts systemd-resolved.
|
||||
#
|
||||
# Stopping fips-dns runs fips-dns-teardown, which removes the file; putting it
|
||||
# back gives the state in which removal leaves it behind: a live delegation and
|
||||
# an inactive fips-dns, so prerm's stop runs no teardown. Only postrm purge is
|
||||
# left to clean up, and a file it misses keeps the resolver sending .fips to
|
||||
# [::1]:5354 after nothing listens there.
|
||||
#
|
||||
# Args: <name> <expected_backend>, the backend the scenario expects
|
||||
# fips-dns-setup to pick (dns-delegate or global-drop-in).
|
||||
check_purge_clears_dns() {
|
||||
local name="$1" backend="$2" file
|
||||
case "$backend" in
|
||||
dns-delegate) file=/etc/systemd/dns-delegate.d/fips.dns-delegate ;;
|
||||
global-drop-in) file=/etc/systemd/resolved.conf.d/fips.conf ;;
|
||||
*)
|
||||
fail "purge: no DNS routing file known for backend '$backend'"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
# The gateway-enable restart of fips.service is passed on to fips-dns
|
||||
# (Requires=fips.service), whose setup waits for fips0 before it writes the
|
||||
# file, and nothing since has waited for it. After=fips.service stops the
|
||||
# old instance before the daemon, so active here means the new setup ran.
|
||||
if ! wait_for_service_active "$name" fips-dns.service; then
|
||||
fail "purge: fips-dns.service not active again after the gateway-enable restart"
|
||||
echo " --- fips-dns.service journal ---"
|
||||
docker exec "$name" journalctl -u fips-dns.service --no-pager 2>&1 | tail -20
|
||||
return
|
||||
fi
|
||||
if ! cexec "$name" test -f "$file"; then
|
||||
fail "purge: $file not written by fips-dns-setup before the purge"
|
||||
return
|
||||
fi
|
||||
# Saved inside the container: cexec runs docker exec without -i, so a
|
||||
# copy piped back from the host would arrive empty.
|
||||
if ! cexec "$name" cp "$file" /root/fips-dns.saved; then
|
||||
fail "purge: could not save $file"
|
||||
return
|
||||
fi
|
||||
|
||||
cexec "$name" systemctl stop fips-dns.service >/dev/null 2>&1
|
||||
cexec "$name" cp /root/fips-dns.saved "$file"
|
||||
cexec "$name" systemctl restart systemd-resolved >/dev/null 2>&1
|
||||
|
||||
local ok=1 status
|
||||
if ! cexec "$name" sh -c "test -s '$file' && cmp -s '$file' /root/fips-dns.saved"; then
|
||||
fail "purge: $file not restored before the purge"
|
||||
ok=0
|
||||
fi
|
||||
if cexec "$name" systemctl is-active --quiet fips-dns.service; then
|
||||
fail "purge: fips-dns.service still active before the purge"
|
||||
ok=0
|
||||
fi
|
||||
# Captured rather than piped into grep -q: under pipefail, grep closing the
|
||||
# pipe early can fail the pipeline on a match.
|
||||
if ! status=$(cexec "$name" resolvectl status 2>&1); then
|
||||
fail "purge: resolvectl status failed before the purge"
|
||||
echo "$status" | tail -10
|
||||
ok=0
|
||||
elif ! grep -q ':5354' <<<"$status"; then
|
||||
fail "purge: resolvectl status does not show $file in effect before the purge"
|
||||
echo "$status" | tail -25
|
||||
ok=0
|
||||
fi
|
||||
[ "$ok" = 1 ] || return
|
||||
local before after
|
||||
before=$(cexec "$name" systemctl show -p InvocationID --value systemd-resolved)
|
||||
|
||||
run_apt "$name" "$UPGRADE_APT_TIMEOUT" purge -y fips
|
||||
echo " purge took ${APT_SECS}s"
|
||||
if [ "$APT_RC" -ne 0 ]; then
|
||||
fail "purge: apt-get purge exited $APT_RC"
|
||||
echo "$APT_OUT" | tail -20
|
||||
return
|
||||
fi
|
||||
|
||||
# test exits 1 for a missing file; any other failure is docker exec's.
|
||||
local rc=0
|
||||
cexec "$name" test -e "$file" || rc=$?
|
||||
case "$rc" in
|
||||
1) pass "purge removed $file" ;;
|
||||
0) fail "purge left $file behind" ;;
|
||||
*) fail "purge: could not check for $file (exit $rc)" ;;
|
||||
esac
|
||||
after=$(cexec "$name" systemctl show -p InvocationID --value systemd-resolved)
|
||||
if [ -n "$before" ] && [ -n "$after" ] && [ "$before" != "$after" ]; then
|
||||
pass "purge restarted systemd-resolved"
|
||||
else
|
||||
fail "purge did not restart systemd-resolved (InvocationID '$before' -> '$after')"
|
||||
fi
|
||||
if ! status=$(cexec "$name" resolvectl status 2>&1); then
|
||||
fail "purge: resolvectl status failed after the purge"
|
||||
echo "$status" | tail -10
|
||||
elif grep -q ':5354' <<<"$status"; then
|
||||
fail "purge: resolvectl status still routes to port 5354"
|
||||
echo "$status" | tail -25
|
||||
else
|
||||
pass "purge: resolvectl status no longer routes to port 5354"
|
||||
fi
|
||||
return
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Scenario runner
|
||||
#
|
||||
@@ -601,6 +709,8 @@ DOCKERFILE
|
||||
docker exec "$name" journalctl -u fips-gateway.service --no-pager 2>&1 | tail -15
|
||||
fi
|
||||
|
||||
check_purge_clears_dns "$name" "$expected_backend"
|
||||
|
||||
cleanup_container "$name"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user