mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
Adds FreeBSD as a supported platform. The daemon, fipsctl, TUN datapath and DNS integration build and run there, with a native pkg and an rc.d service. The one piece of genuinely new datapath logic is the TUN framing. FreeBSD's tun rejects every non-IPv4 packet with EAFNOSUPPORT unless TUNSIFHEAD is set, so nothing IPv6 can be sent at all; with it set, every frame carries a 4-byte network-order address-family prefix the way macOS utun does. The ioctl is issued at device creation and the prefix is stripped on read, which gives callers the same raw-IP contract as Linux and macOS. A frame carrying only the header reads as zero bytes and the reader loops treat it as nothing to do. The address family is now taken from libc rather than hardcoded, because AF_INET6 is 30 on Darwin and 28 on FreeBSD. The reader shutdown path, the writer's address-family header and the supervisor's shutdown pipe were all macOS-only and are now shared with FreeBSD, since neither platform wakes a blocked read when the interface goes down. Linux continues to rely on interface deletion. mdns-sd moves from 0.19 to 0.20 for socket-pktinfo 0.4.1, the first release that builds on FreeBSD, which uses IP_RECVDSTADDR and IP_RECVIF instead of Linux-style IP_PKTINFO. This is the only change here that affects every platform rather than just the new one. The config, ACL, hosts and keygen path constants now treat FreeBSD the same as macOS, since both install under /usr/local/etc/fips. Those constants arrived separately on maint and are merged here rather than duplicated: the predicates widen to cover FreeBSD, the platform-gated tests widen with them, and keygen keeps reading the shared SYSTEM_CONFIG_DIR constant rather than reintroducing a literal. Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
222 lines
8.4 KiB
Bash
Executable File
222 lines
8.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# fips-dns-setup — Configure DNS routing for the .fips domain (FreeBSD).
|
|
#
|
|
# Detects the system's DNS resolver and configures it to forward .fips
|
|
# queries to the FIPS DNS responder on [::1]:5354 (the daemon's default
|
|
# IPv6 loopback bind).
|
|
#
|
|
# Backends (tried in order):
|
|
# 1. local_unbound (base system) — drop-in /var/unbound/conf.d/fips.conf
|
|
# 2. unbound (pkg) — drop-in /usr/local/etc/unbound/conf.d/
|
|
# 3. dnsmasq (pkg) — drop-in include, if a conf-dir is used
|
|
# 4. Warning with manual instructions
|
|
#
|
|
# Notes baked in from field debugging:
|
|
# - The daemon binds ::1 ONLY, so the resolver must forward over IPv6;
|
|
# local-unbound-setup often writes `do-ip6: no`, which fails silently.
|
|
# The drop-in forces `do-ip6: yes`.
|
|
# - `domain-insecure: "fips."` is required or DNSSEC validation rejects
|
|
# the unsigned zone.
|
|
# - A configured, running unbound is useless if /etc/resolv.conf does
|
|
# not point at it — warn loudly if it doesn't.
|
|
|
|
set -eu
|
|
|
|
FIPS_DNS_PORT="5354"
|
|
FIPS_DNS_LOOPBACK_V6="::1"
|
|
|
|
LOCAL_UNBOUND_DROPIN_DIR="/var/unbound/conf.d"
|
|
LOCAL_UNBOUND_DROPIN="${LOCAL_UNBOUND_DROPIN_DIR}/fips.conf"
|
|
PKG_UNBOUND_DROPIN_DIR="/usr/local/etc/unbound/conf.d"
|
|
PKG_UNBOUND_DROPIN="${PKG_UNBOUND_DROPIN_DIR}/fips.conf"
|
|
DNSMASQ_DROPIN_DIR="/usr/local/etc/dnsmasq.d"
|
|
DNSMASQ_DROPIN="${DNSMASQ_DROPIN_DIR}/fips.conf"
|
|
|
|
# Record which backend was configured, for teardown.
|
|
STATE_DIR="/var/run/fips"
|
|
STATE_FILE="${STATE_DIR}/dns-backend"
|
|
|
|
log() { echo "fips-dns: $*"; }
|
|
|
|
save_backend() {
|
|
mkdir -p "$STATE_DIR"
|
|
echo "$1" > "$STATE_FILE"
|
|
}
|
|
|
|
service_enabled_or_running() {
|
|
service "$1" enabled >/dev/null 2>&1 || service "$1" onestatus >/dev/null 2>&1
|
|
}
|
|
|
|
# Wait for the daemon's DNS responder to be listening (up to 30s). The
|
|
# TUN interface name is kernel-assigned (tunN), so the responder socket
|
|
# is the reliable readiness signal.
|
|
wait_for_daemon() {
|
|
i=0
|
|
while [ "$i" -lt 30 ]; do
|
|
sockstat -6 -l -p "$FIPS_DNS_PORT" 2>/dev/null | grep -q ":$FIPS_DNS_PORT" && return 0
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
log "ERROR: nothing listening on [${FIPS_DNS_LOOPBACK_V6}]:$FIPS_DNS_PORT after 30s (is the fips service running?)"
|
|
return 1
|
|
}
|
|
|
|
# The forward-zone drop-in shared by both unbound backends.
|
|
unbound_snippet() {
|
|
cat <<EOF
|
|
# Managed by fips_dns (fips-dns-setup). Routes .fips queries to the
|
|
# FIPS daemon's local DNS responder.
|
|
server:
|
|
# The .fips zone is unsigned; skip DNSSEC validation for it.
|
|
domain-insecure: "fips."
|
|
# The daemon binds [::1] only — forwarding must go over IPv6.
|
|
# local-unbound-setup writes do-ip6: no by default, which would
|
|
# make this forward-zone fail silently.
|
|
do-ip6: yes
|
|
# unbound refuses to query loopback forwarders by default, which
|
|
# silently SERVFAILs every .fips query instead of asking the
|
|
# daemon on [::1]:5354.
|
|
do-not-query-localhost: no
|
|
|
|
forward-zone:
|
|
name: "fips."
|
|
forward-addr: ${FIPS_DNS_LOOPBACK_V6}@${FIPS_DNS_PORT}
|
|
forward-first: no
|
|
EOF
|
|
}
|
|
|
|
# A resolver can be perfectly configured and still unused: if
|
|
# /etc/resolv.conf has no loopback nameserver, applications never query
|
|
# it. This was the actual root cause of ".fips doesn't resolve" in the
|
|
# field, so diagnose it here.
|
|
warn_if_not_system_resolver() {
|
|
if ! grep -Eq '^[[:space:]]*nameserver[[:space:]]+(127\.0\.0\.1|::1)' /etc/resolv.conf 2>/dev/null; then
|
|
log "WARNING: /etc/resolv.conf has no 127.0.0.1/::1 nameserver —"
|
|
log "WARNING: the local resolver is configured but is NOT the system resolver,"
|
|
log "WARNING: so .fips names will not resolve. Fix (base local_unbound):"
|
|
log "WARNING: service local_unbound enable && local-unbound-setup <upstream-ip ...>"
|
|
log "WARNING: (rewrites resolv.conf to 127.0.0.1 and keeps this drop-in)."
|
|
fi
|
|
|
|
# Field finding: `options edns0` in resolv.conf (written by
|
|
# local-unbound-setup via resolv_conf_options in /etc/resolvconf.conf)
|
|
# has broken public resolution on some setups.
|
|
if grep -Eq '^[[:space:]]*options.*\bedns0\b' /etc/resolv.conf 2>/dev/null; then
|
|
log "NOTE: /etc/resolv.conf sets 'options edns0'. If public DNS resolution"
|
|
log "NOTE: fails, remove that line and make it permanent by setting"
|
|
log "NOTE: resolv_conf_options=\"\""
|
|
log "NOTE: in /etc/resolvconf.conf (else resolvconf(8) re-adds it)."
|
|
fi
|
|
}
|
|
|
|
# local-unbound-setup snapshots the nameservers it finds in
|
|
# /etc/resolv.conf into /var/unbound/forward.conf. If it is (re-)run
|
|
# AFTER resolv.conf already points at 127.0.0.1, unbound ends up
|
|
# forwarding every public query to itself — and with unbound's
|
|
# do-not-query-localhost default it refuses the loop, so all public
|
|
# resolution dies with SERVFAIL. Detect and explain.
|
|
warn_if_forward_loop() {
|
|
fwd="/var/unbound/forward.conf"
|
|
[ -f "$fwd" ] || return 0
|
|
if grep -Eq '^[[:space:]]*forward-addr:[[:space:]]*(127\.0\.0\.1|::1)([[:space:]]|$)' "$fwd"; then
|
|
log "WARNING: ${fwd} forwards public queries to localhost — unbound is"
|
|
log "WARNING: forwarding to itself, which breaks ALL public resolution."
|
|
log "WARNING: (Cause: local-unbound-setup was run while resolv.conf already"
|
|
log "WARNING: pointed at 127.0.0.1.) Fix by re-running it with explicit"
|
|
log "WARNING: upstream resolvers, e.g. your router or ISP resolver:"
|
|
log "WARNING: local-unbound-setup 192.168.1.1"
|
|
fi
|
|
}
|
|
|
|
# Backend 1: base-system local_unbound
|
|
try_local_unbound() {
|
|
service_enabled_or_running local_unbound || return 1
|
|
[ -d /var/unbound ] || return 1
|
|
|
|
log "Configuring via local_unbound (${LOCAL_UNBOUND_DROPIN})"
|
|
mkdir -p "$LOCAL_UNBOUND_DROPIN_DIR"
|
|
unbound_snippet > "$LOCAL_UNBOUND_DROPIN"
|
|
|
|
if command -v local-unbound-checkconf >/dev/null 2>&1 \
|
|
&& ! local-unbound-checkconf >/dev/null 2>&1; then
|
|
log "ERROR: local-unbound-checkconf rejected the config; removing drop-in"
|
|
rm -f "$LOCAL_UNBOUND_DROPIN"
|
|
return 1
|
|
fi
|
|
|
|
service local_unbound reload >/dev/null 2>&1 \
|
|
|| service local_unbound onerestart >/dev/null 2>&1 \
|
|
|| log "WARNING: local_unbound reload failed (config written, may need manual restart)"
|
|
save_backend "local_unbound"
|
|
warn_if_not_system_resolver
|
|
warn_if_forward_loop
|
|
return 0
|
|
}
|
|
|
|
# Backend 2: pkg unbound
|
|
try_pkg_unbound() {
|
|
service_enabled_or_running unbound || return 1
|
|
[ -d /usr/local/etc/unbound ] || return 1
|
|
|
|
log "Configuring via unbound (${PKG_UNBOUND_DROPIN})"
|
|
mkdir -p "$PKG_UNBOUND_DROPIN_DIR"
|
|
unbound_snippet > "$PKG_UNBOUND_DROPIN"
|
|
|
|
if ! grep -Erqs '^[[:space:]]*include(-toplevel)?:.*conf\.d' /usr/local/etc/unbound/unbound.conf; then
|
|
log "NOTE: ensure unbound.conf includes the drop-in directory, e.g.:"
|
|
log "NOTE: include-toplevel: \"${PKG_UNBOUND_DROPIN_DIR}/*.conf\""
|
|
fi
|
|
|
|
service unbound reload >/dev/null 2>&1 \
|
|
|| service unbound onerestart >/dev/null 2>&1 \
|
|
|| log "WARNING: unbound reload failed (config written, may need manual restart)"
|
|
save_backend "pkg-unbound"
|
|
warn_if_not_system_resolver
|
|
return 0
|
|
}
|
|
|
|
# Backend 3: pkg dnsmasq
|
|
#
|
|
# dnsmasq's `server=/<domain>/<addr>#<port>` accepts a bare IPv6 literal.
|
|
try_dnsmasq() {
|
|
service_enabled_or_running dnsmasq || return 1
|
|
|
|
log "Configuring via dnsmasq (${DNSMASQ_DROPIN})"
|
|
mkdir -p "$DNSMASQ_DROPIN_DIR"
|
|
cat > "$DNSMASQ_DROPIN" <<EOF
|
|
# FIPS .fips domain forwarding (managed by fips_dns)
|
|
server=/fips/${FIPS_DNS_LOOPBACK_V6}#${FIPS_DNS_PORT}
|
|
EOF
|
|
|
|
if ! grep -Eqs "^[[:space:]]*conf-dir=.*dnsmasq\.d" /usr/local/etc/dnsmasq.conf; then
|
|
log "NOTE: ensure dnsmasq.conf reads the drop-in directory, e.g.:"
|
|
log "NOTE: conf-dir=${DNSMASQ_DROPIN_DIR}/,*.conf"
|
|
fi
|
|
|
|
service dnsmasq reload >/dev/null 2>&1 \
|
|
|| service dnsmasq onerestart >/dev/null 2>&1 \
|
|
|| log "WARNING: dnsmasq reload failed (config written, may need manual restart)"
|
|
save_backend "dnsmasq"
|
|
warn_if_not_system_resolver
|
|
return 0
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
wait_for_daemon || exit 1
|
|
|
|
try_local_unbound && exit 0
|
|
try_pkg_unbound && exit 0
|
|
try_dnsmasq && exit 0
|
|
|
|
log "WARNING: No supported DNS resolver detected."
|
|
log "To resolve .fips domains, forward the fips. zone to"
|
|
log "[${FIPS_DNS_LOOPBACK_V6}]:${FIPS_DNS_PORT} (the daemon's default bind)."
|
|
log ""
|
|
log "Easiest path on FreeBSD (base local_unbound):"
|
|
log " sysrc local_unbound_enable=YES"
|
|
log " local-unbound-setup"
|
|
log " service fips_dns restart"
|
|
save_backend "none"
|
|
exit 0
|