mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-30 07:37:36 +00:00
The TUN, DNS and ICMPv6 code moved from src/upper to src/ipv6tun with an alias, `pub use ipv6tun as upper;`, and re-exports inside ipv6tun of the hosts file, the path MTU map and three protocol constants, so the old paths kept resolving. Point every user at the module that defines the item: crate::ipv6tun for TUN, DNS and ICMPv6 items, crate::hosts for the hosts file, crate::node::path_mtu for the path MTU map, and crate::proto::framing and crate::proto::mmp for FIPS_OVERHEAD, MIN_ACTIONABLE_PATH_MTU and MIN_REACTIVE_PATH_MTU. Then remove the alias and the re-exports, so ipv6tun no longer re-exports items that belong elsewhere. fipsctl reads the hosts file through fips::hosts. The three constants are re-exported at the crate root, since proto is crate-private. Comments, doc links and packaging comments that named src/upper now name src/ipv6tun. Only paths change and no code moves, so behaviour and log targets are unchanged. Removing fips::upper breaks the build for library users of that path; the CHANGELOG lists where each item now lives.
225 lines
9.5 KiB
Bash
Executable File
225 lines
9.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# fips-dns-setup - route .fips DNS queries to the FIPS daemon on pfSense.
|
|
#
|
|
# pfSense generates /var/unbound/unbound.conf from config.xml on every
|
|
# apply, and unbound.inc emits no conf.d include and no generic drop-in
|
|
# mechanism - the include: lines it writes name specific generated files
|
|
# (access_lists.conf, host_entries.conf, domainoverrides.conf, ...).
|
|
# So the stock FreeBSD fips-dns-setup, which drops a file into
|
|
# /var/unbound/conf.d/, configures nothing here: the file is never read
|
|
# and the next GUI apply does not so much as mention it.
|
|
#
|
|
# The writable surface pfSense does offer is the DNS Resolver "Custom
|
|
# options" box, which unbound.inc splices into the generated config
|
|
# verbatim. It is stored base64-encoded in config.xml, which is the part
|
|
# that makes it the right home: config.xml is what survives a reboot, a
|
|
# firmware upgrade and a config restore. What this package installs
|
|
# under /usr/local survives a firmware upgrade too (pfSense-upgrade
|
|
# reinstalls only pfSense-pkg-* packages), but not a removal of the
|
|
# package. So the .fips zone can outlive the daemon, which is a loud
|
|
# failure (SERVFAIL on .fips) rather than a quiet one.
|
|
#
|
|
# This edits the firewall's live configuration, so it is deliberately
|
|
# NOT run from the package's post-install: installing a package should
|
|
# not rewrite config.xml behind the operator's back. Run it by hand.
|
|
# write_config() records a config-history entry, so the edit is
|
|
# revertable from Diagnostics > Backup & Restore > Config History.
|
|
#
|
|
# Usage: fips-dns-setup [--config <fips.yaml>]
|
|
|
|
set -eu
|
|
|
|
FIPS_CONFIG="/usr/local/etc/fips/fips.yaml"
|
|
PHP_HELPER="/usr/local/libexec/fips/fips-unbound-custom.php"
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--config) FIPS_CONFIG="${2:?--config requires an argument}"; shift ;;
|
|
*) echo "usage: $0 [--config <fips.yaml>]" >&2; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
log() { echo "fips-dns: $*"; }
|
|
|
|
[ -f /etc/inc/config.inc ] || {
|
|
log "ERROR: /etc/inc/config.inc not found - this is not a pfSense system."
|
|
log "ERROR: On stock FreeBSD use the FreeBSD package's fips-dns-setup."
|
|
exit 1
|
|
}
|
|
[ -f "$PHP_HELPER" ] || { log "ERROR: ${PHP_HELPER} is missing"; exit 1; }
|
|
[ -f "$FIPS_CONFIG" ] || { log "ERROR: ${FIPS_CONFIG} is missing"; exit 1; }
|
|
|
|
# Read dns.bind_addr and dns.port out of fips.yaml.
|
|
#
|
|
# Scoped to the top-level `dns:` block on purpose: `bind_addr` also
|
|
# appears under every transport, and a plain grep would happily hand
|
|
# back the UDP transport's 0.0.0.0:2121 and point unbound at it.
|
|
yaml_dns_field() {
|
|
awk -v field="$1" '
|
|
# A line starting in column 1 with a key opens a new top-level
|
|
# block; anything indented belongs to the block above it.
|
|
/^[^[:space:]#]/ { section = $1; sub(/:.*/, "", section); next }
|
|
section != "dns" { next }
|
|
{
|
|
line = $0
|
|
sub(/[[:space:]]*#.*$/, "", line) # strip trailing comment
|
|
if (line !~ "^[[:space:]]+" field ":") next
|
|
sub("^[[:space:]]+" field ":[[:space:]]*", "", line)
|
|
gsub(/^["'"'"']|["'"'"']$/, "", line) # strip quotes
|
|
if (line != "") { print line; exit }
|
|
}
|
|
' "$FIPS_CONFIG"
|
|
}
|
|
|
|
BIND_ADDR="$(yaml_dns_field bind_addr)"
|
|
DNS_PORT="$(yaml_dns_field port)"
|
|
|
|
# Defaults match src/ipv6tun/config.rs (DEFAULT_DNS_BIND_ADDR, DEFAULT_DNS_PORT).
|
|
[ -n "$BIND_ADDR" ] || BIND_ADDR="::1"
|
|
[ -n "$DNS_PORT" ] || DNS_PORT="5354"
|
|
|
|
# forward-addr needs a specific address to send to. A wildcard bind is
|
|
# valid for the daemon (it listens everywhere) but names nothing unbound
|
|
# could query, so refuse it here rather than write a forward-zone that
|
|
# can never answer.
|
|
case "$BIND_ADDR" in
|
|
0.0.0.0 | :: | '[::]')
|
|
log "ERROR: dns.bind_addr is ${BIND_ADDR}, a wildcard. unbound's forward-addr"
|
|
log "ERROR: needs one address to send to. Set in ${FIPS_CONFIG}:"
|
|
log "ERROR: dns:"
|
|
log "ERROR: bind_addr: \"127.0.0.1\""
|
|
log "ERROR: then restart: /usr/local/etc/rc.d/fips.sh restart && $0"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# unbound wants a bare address in forward-addr; the @port suffix carries
|
|
# the port, and pfSense's own domain-override code uses the same form.
|
|
FORWARD_ADDR="${BIND_ADDR}@${DNS_PORT}"
|
|
|
|
case "$BIND_ADDR" in
|
|
*:*)
|
|
# An IPv6 bind means unbound has to query over IPv6, and pfSense
|
|
# writes `do-ip6: {$ipv6_allow}` from the system "Allow IPv6"
|
|
# setting. With IPv6 disallowed, do-ip6: no makes every .fips
|
|
# query fail with nothing in the log to say why. `do-ip6: yes`
|
|
# in custom options cannot be relied on to override the
|
|
# generated one, so point the operator at the fix that always
|
|
# works: bind the responder on IPv4 loopback.
|
|
log "NOTE: dns.bind_addr is ${BIND_ADDR} (IPv6). unbound only queries over"
|
|
log "NOTE: IPv6 when System > Advanced > Networking has 'Allow IPv6' set."
|
|
log "NOTE: If .fips does not resolve, set in ${FIPS_CONFIG}:"
|
|
log "NOTE: dns:"
|
|
log "NOTE: bind_addr: \"127.0.0.1\""
|
|
log "NOTE: then restart: /usr/local/etc/rc.d/fips.sh restart && $0"
|
|
;;
|
|
esac
|
|
|
|
# Refuse to touch config.xml for a daemon that is not answering. Without
|
|
# this the script edits the firewall's configuration, restarts unbound
|
|
# and reports success while nothing is listening — which is a far worse
|
|
# failure than declining, because the operator then has a healthy-looking
|
|
# resolver forwarding into a void and no message saying so.
|
|
wait_for_daemon() {
|
|
# sockstat prints IPv6 endpoints bracketed ([::1]:5354) and IPv4 bare.
|
|
case "$BIND_ADDR" in
|
|
*:*) SOCK_ADDR="[${BIND_ADDR}]" ;;
|
|
*) SOCK_ADDR="$BIND_ADDR" ;;
|
|
esac
|
|
i=0
|
|
while [ "$i" -lt 30 ]; do
|
|
sockstat -l -p "$DNS_PORT" 2>/dev/null \
|
|
| grep -Fq "${SOCK_ADDR}:${DNS_PORT}" && return 0
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
log "ERROR: nothing is listening on ${BIND_ADDR}:${DNS_PORT} after 30s."
|
|
log "ERROR: config.xml has NOT been modified. Check the daemon first:"
|
|
log "ERROR: /usr/local/etc/rc.d/fips.sh status"
|
|
log "ERROR: tail -50 /var/log/fips.log"
|
|
log "ERROR: and confirm dns.enabled is true in ${FIPS_CONFIG}."
|
|
return 1
|
|
}
|
|
|
|
# A resolver can be configured perfectly and still never be asked. If the
|
|
# firewall's own stub resolver points at a public server rather than at
|
|
# unbound, .fips queries leave the box and come back NXDOMAIN from the
|
|
# root servers, with nothing in unbound's log because unbound never saw
|
|
# them. `drill <npub>.fips` reporting SERVER: 8.8.8.8 is exactly this.
|
|
warn_if_not_system_resolver() {
|
|
if grep -Eq '^[[:space:]]*nameserver[[:space:]]+(127\.0\.0\.1|::1)' \
|
|
/etc/resolv.conf 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
log "WARNING: /etc/resolv.conf has no 127.0.0.1/::1 nameserver, so this"
|
|
log "WARNING: firewall does not ask its own resolver — .fips queries go"
|
|
log "WARNING: straight to the upstream servers and come back NXDOMAIN."
|
|
log "WARNING: The forward-zone just written is correct but unreachable."
|
|
log "WARNING:"
|
|
log "WARNING: Fix in the GUI: System > General Setup > DNS Server Settings,"
|
|
log "WARNING: set 'DNS Resolution Behavior' to"
|
|
log "WARNING: Use local DNS (127.0.0.1), fall back to remote DNS servers"
|
|
log "WARNING: and make sure Services > DNS Resolver is enabled."
|
|
log "WARNING:"
|
|
log "WARNING: Current /etc/resolv.conf nameservers:"
|
|
grep -E '^[[:space:]]*nameserver' /etc/resolv.conf 2>/dev/null \
|
|
| sed 's/^/fips-dns: /' || true
|
|
return 0
|
|
}
|
|
|
|
wait_for_daemon || exit 1
|
|
|
|
log "Pointing the fips. zone at ${FORWARD_ADDR} via DNS Resolver custom options"
|
|
|
|
# The snippet itself. Three directives beyond the forward-zone, each one
|
|
# load-bearing:
|
|
#
|
|
# domain-insecure - the .fips zone is unsigned, and pfSense enables
|
|
# DNSSEC validation by default; without this every
|
|
# answer is thrown away as bogus.
|
|
# do-not-query-localhost: no
|
|
# - unbound's default is to refuse loopback
|
|
# forwarders outright, which SERVFAILs every
|
|
# .fips query instead of asking the daemon.
|
|
# pfSense's unbound.inc never sets this.
|
|
# forward-first: no - never fall back to the public resolvers for a
|
|
# name the daemon declined; .fips does not exist
|
|
# outside the mesh, and leaking the query would
|
|
# publish which npubs this firewall talks to.
|
|
SNIPPET="$(cat <<EOS
|
|
server:
|
|
domain-insecure: "fips."
|
|
do-not-query-localhost: no
|
|
|
|
forward-zone:
|
|
name: "fips."
|
|
forward-addr: ${FORWARD_ADDR}
|
|
forward-first: no
|
|
|
|
# Custom options are spliced in last, so anything added below the END
|
|
# marker would otherwise continue this forward-zone clause.
|
|
server:
|
|
EOS
|
|
)"
|
|
|
|
# `--` separates the script's arguments from php's own options; with -f,
|
|
# php is entitled to read what follows the filename as its own flags.
|
|
printf '%s\n' "$SNIPPET" | /usr/local/bin/php -f "$PHP_HELPER" -- add "$BIND_ADDR" "$DNS_PORT"
|
|
rc=$?
|
|
case "$rc" in
|
|
0) ;;
|
|
3) # Written, but the DNS Resolver is disabled: the block is inert and
|
|
# the helper has already said what to do instead. Nothing to verify.
|
|
exit 0 ;;
|
|
*) exit "$rc" ;;
|
|
esac
|
|
|
|
# The block is in place; whether anything will ever ask unbound for it
|
|
# is a separate question, and the answer is not always yes.
|
|
warn_if_not_system_resolver
|
|
|
|
log "Done. Verify with:"
|
|
log " drill -p ${DNS_PORT} <npub>.fips @${BIND_ADDR} AAAA # the daemon directly"
|
|
log " drill <npub>.fips AAAA # the full chain"
|