mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
packaging/README.md and the pfSense builder's header said a firmware upgrade removes the package, and the fips-dns-setup comment said the same of everything under /usr/local. pfSense-upgrade reinstalls only pfSense-pkg-* packages, and a live Plus 26.03.1 to 26.07 upgrade kept this one, as the pfSense README, the post-install banner and pkg-descr already say. A major base change still calls for the package built for the new base.
579 lines
25 KiB
Bash
Executable File
579 lines
25 KiB
Bash
Executable File
#!/bin/sh
|
|
# Build a pfSense .pkg package for FIPS using pkg-create(8).
|
|
#
|
|
# Usage: packaging/pfsense/build-pkg.sh [--version <v>] [--abi <abi>]
|
|
# [--target <triple>] [--product <id>] [--dynamic] [--no-build]
|
|
#
|
|
# Links statically by default; --dynamic opts out. See the "Static
|
|
# linking is the default" section of packaging/pfsense/README.md.
|
|
#
|
|
# Prerequisites: the pinned Rust toolchain, pkg(8). Must run on FreeBSD:
|
|
# the binaries are native, and pkg create needs a FreeBSD host.
|
|
# Output: deploy/fips-<version>-pfsense-<products>-<arch>.pkg
|
|
#
|
|
# This is a sibling of packaging/freebsd/build-pkg.sh, not a replacement
|
|
# for it. pfSense is FreeBSD underneath, but it diverges in the four
|
|
# places a package touches:
|
|
#
|
|
# - Boot. pfSense's rc.start_packages globs /usr/local/etc/rc.d/*.sh
|
|
# and runs each as `<script> start`. The FreeBSD package's
|
|
# rc.d/fips has no .sh suffix and gates on an /etc/rc.conf variable,
|
|
# so on pfSense it would never start.
|
|
# - DNS. pfSense generates unbound.conf from config.xml and reads no
|
|
# conf.d directory, so the FreeBSD package's drop-in is inert here.
|
|
# This package integrates through the DNS Resolver custom options.
|
|
# - The responder's bind address, for the reason recorded in
|
|
# fips.yaml.dns.
|
|
# - Lifetime. A firmware upgrade keeps the package (pfSense-upgrade
|
|
# reinstalls only pfSense-pkg-* packages), but a major upgrade changes
|
|
# the FreeBSD base, so post-install says to reinstall the package
|
|
# built for the new base.
|
|
#
|
|
# Ships fips, fipsctl and fipstop. fips-gateway is excluded: its NAT
|
|
# backend is nftables (Linux-only), and pfSense has pf for that anyway.
|
|
|
|
set -eu
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
|
|
# Mechanics shared with the FreeBSD builder: version derivation, the
|
|
# stage layout, the @sample manifest scripts, and pkg create.
|
|
. "${PROJECT_ROOT}/packaging/common/pkg-lib.sh"
|
|
|
|
NO_BUILD=0
|
|
VERSION=""
|
|
ABI_OVERRIDE=""
|
|
STATIC=1
|
|
PRODUCT=""
|
|
TARGET=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--no-build) NO_BUILD=1 ;;
|
|
--static) STATIC=1 ;; # the default, spelled out
|
|
--dynamic) STATIC=0 ;;
|
|
--product) PRODUCT="${2:?--product requires an argument}"; shift ;;
|
|
--version) VERSION="${2:?--version requires an argument}"; shift ;;
|
|
--abi) ABI_OVERRIDE="${2:?--abi requires an argument}"; shift ;;
|
|
--target) TARGET="${2:?--target requires an argument}"; shift ;;
|
|
*)
|
|
echo "usage: $0 [--version <version>] [--abi <abi>] [--target <triple>] [--product <id>] [--dynamic] [--no-build]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# CI passes a derived version that appends +<branch>.<height>.<hash> on
|
|
# branch builds; otherwise the version comes from Cargo.toml.
|
|
VERSION="$(pkg_resolve_version "$PROJECT_ROOT" "$VERSION")"
|
|
|
|
if [ -n "$ABI_OVERRIDE" ]; then
|
|
ABI="$ABI_OVERRIDE"
|
|
else
|
|
ABI="$(pkg_host_abi)"
|
|
fi
|
|
ABI_MAJOR="$(printf '%s' "$ABI" | awk -F: '{print $2}')"
|
|
ARCH="${ABI##*:}"
|
|
|
|
# --- Which pfSense this is for --------------------------------------
|
|
#
|
|
# The filename names the pfSense product(s), not the FreeBSD ABI, because
|
|
# that is what the person choosing a download knows: "I run Plus 26.03 on
|
|
# a 4200", not "I need FreeBSD:16:aarch64". pkg still enforces the ABI,
|
|
# which the manifest carries; the name is wayfinding.
|
|
#
|
|
# One ABI can serve more than one product. CE 2.9 and Plus 26.x are both
|
|
# FreeBSD 16 on amd64 and the artifact is byte-identical, so the name
|
|
# carries every supported product the package installs on. The mapping is
|
|
# therefore ABI -> products: the ABI is what gets built, the products are
|
|
# what people look for.
|
|
#
|
|
# Supported releases as of 2026-09, from Netgate's version table:
|
|
#
|
|
# CE 2.8.1 FreeBSD 15 amd64
|
|
# CE 2.9.0 FreeBSD 16 amd64
|
|
# Plus 26.03.1 FreeBSD 16 amd64, aarch64
|
|
# Plus 26.07 FreeBSD 16 amd64, aarch64
|
|
#
|
|
# CE has only ever shipped for amd64, and Netgate has said there are no
|
|
# plans for an ARM CE image. Plus 24.x and 25.07 (FreeBSD 15) and 25.11
|
|
# (FreeBSD 16) are end-of-life and deliberately absent: a package named
|
|
# for an unsupported release is an invitation to install it there.
|
|
#
|
|
# Verify against https://docs.netgate.com/pfsense/en/latest/releases/versions.html
|
|
# when a release moves, and change this table in the same commit.
|
|
abi_products() {
|
|
case "$1" in
|
|
FreeBSD:15:amd64) echo "ce2.8" ;;
|
|
FreeBSD:16:amd64) echo "ce2.9 plus26" ;;
|
|
FreeBSD:16:aarch64) echo "plus26" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
|
|
PRODUCTS="$(abi_products "$ABI")"
|
|
if [ -z "$PRODUCTS" ]; then
|
|
echo "error: no supported pfSense release runs on ${ABI}." >&2
|
|
case "$ABI" in
|
|
FreeBSD:15:aarch64)
|
|
echo "error: The ARM releases on FreeBSD 15 (Plus 24.x, 25.07) are end-of-" >&2
|
|
echo "error: life. Plus 26.x on ARM is FreeBSD:16:aarch64." >&2
|
|
;;
|
|
*)
|
|
echo "error: If Netgate has shipped a release on this base, add it to the" >&2
|
|
echo "error: table in $0 in the same change, so the next build does not" >&2
|
|
echo "error: have to guess." >&2
|
|
;;
|
|
esac
|
|
exit 1
|
|
fi
|
|
|
|
# --product is an assertion, not a selector. The artifact serves every
|
|
# product listed for its ABI whatever is passed, so the name always
|
|
# carries them all; --product says "I believe I am building for X", and
|
|
# the build refuses when X is not among them — which is how an ABI
|
|
# chosen for the wrong release gets caught before anything is packaged.
|
|
if [ -n "$PRODUCT" ]; then
|
|
product_ok=0
|
|
for known_product in $PRODUCTS; do
|
|
[ "$known_product" = "$PRODUCT" ] && product_ok=1
|
|
done
|
|
if [ "$product_ok" -eq 0 ]; then
|
|
echo "error: --product ${PRODUCT} does not run on ${ABI}, which serves: ${PRODUCTS}." >&2
|
|
case "$PRODUCT" in
|
|
ce2.8 | ce2.9)
|
|
echo "error: CE ships for amd64 only: CE 2.8 is FreeBSD 15, CE 2.9 is FreeBSD 16." >&2 ;;
|
|
plus26)
|
|
echo "error: Plus 26.x is FreeBSD 16, on amd64 or aarch64." >&2 ;;
|
|
*)
|
|
echo "error: Known products: ce2.8, ce2.9, plus26." >&2 ;;
|
|
esac
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# The filename tag: every product this ABI serves, joined with '-'.
|
|
PRODUCT_TAG="$(printf '%s' "$PRODUCTS" | tr ' ' '-')"
|
|
|
|
# Where cargo leaves the binaries. `--target` puts them one level deeper,
|
|
# and is also the flag that says this is not a host build.
|
|
if [ -n "$TARGET" ]; then
|
|
BIN_DIR="${PROJECT_ROOT}/target/${TARGET}/release"
|
|
else
|
|
BIN_DIR="${PROJECT_ROOT}/target/release"
|
|
fi
|
|
|
|
# --- Static linking -------------------------------------------------
|
|
#
|
|
# pfSense runs a FreeBSD base you cannot obtain: Netgate builds Plus
|
|
# from a 16.0-CURRENT snapshot of their own, and download.freebsd.org
|
|
# keeps only the last two CURRENT builds. So the build host's libc is
|
|
# almost always *newer* than the appliance's, which is the direction
|
|
# that breaks: a binary can reference a versioned libc symbol the
|
|
# appliance does not export, install cleanly, and then refuse to start.
|
|
#
|
|
# Linking statically removes the negotiation entirely — there is no
|
|
# libc.so.7 to disagree with. What is left is the kernel's binary
|
|
# compatibility, which FreeBSD promises in one direction only: binaries
|
|
# from an older release run on a newer kernel. So build on a base no
|
|
# newer than the appliance's, never on a snapshot ahead of it. That is
|
|
# why the FreeBSD 16 package is the 15.1 build relabelled (--no-build
|
|
# --abi FreeBSD:16:amd64) rather than a build on a 16.0-CURRENT host.
|
|
#
|
|
# Verified viable on this codebase: no dlopen/libloading anywhere, and
|
|
# FreeBSD builds files+dns resolution into libc, so a static binary
|
|
# still resolves hostnames (the thing that defeats static glibc).
|
|
if [ "$STATIC" -eq 1 ]; then
|
|
# RUSTFLAGS must not reach build scripts and proc-macros, which run
|
|
# on the build host; passing --target is what confines it.
|
|
if [ -z "$TARGET" ]; then
|
|
TARGET="$(rustc -vV | sed -n 's/^host: //p')"
|
|
[ -n "$TARGET" ] || { echo "error: could not determine host target triple" >&2; exit 1; }
|
|
BIN_DIR="${PROJECT_ROOT}/target/${TARGET}/release"
|
|
echo "==> static build for host target ${TARGET}"
|
|
fi
|
|
RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=+crt-static"
|
|
export RUSTFLAGS
|
|
fi
|
|
|
|
# The package's arch has to describe the binaries in it, and nothing
|
|
# downstream checks that: pkg believes the ABI string in the manifest, so
|
|
# a mislabelled package installs on the appliance and then cannot exec.
|
|
# Cross-check the two here, where it is still cheap.
|
|
#
|
|
# pkg's arch names are not the Rust triple's: amd64 is x86_64, and only
|
|
# aarch64 spells itself the same in both.
|
|
case "$TARGET" in
|
|
"") expected_arch="$ARCH" ;; # host build: nothing to cross-check
|
|
aarch64-*freebsd*) expected_arch="aarch64" ;;
|
|
x86_64-*freebsd*) expected_arch="amd64" ;;
|
|
i686-*freebsd*) expected_arch="i386" ;;
|
|
*)
|
|
echo "error: --target ${TARGET} is not a FreeBSD target triple." >&2
|
|
echo "error: This package can only be built for FreeBSD-based systems." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [ "$expected_arch" != "$ARCH" ]; then
|
|
echo "error: --target ${TARGET} produces ${expected_arch} binaries, but the" >&2
|
|
echo "error: ABI says ${ARCH} (${ABI}). Pass a matching --abi, e.g.:" >&2
|
|
echo "error: --target ${TARGET} --abi FreeBSD:${ABI_MAJOR}:${expected_arch}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# pkg refuses a package whose ABI does not match the running system, in
|
|
# both the FreeBSD major and the architecture, so the build has to be
|
|
# aimed at the target's base rather than at the build host's.
|
|
#
|
|
# pfSense CE 2.8.1 FreeBSD 15, amd64 -> FreeBSD:15:amd64
|
|
# pfSense CE 2.9.0 FreeBSD 16, amd64 -> FreeBSD:16:amd64
|
|
# pfSense Plus 26.x FreeBSD 16, amd64 -> FreeBSD:16:amd64
|
|
# pfSense Plus 26.x FreeBSD 16, ARM -> FreeBSD:16:aarch64
|
|
#
|
|
# The mapping moves between releases; check it against the target before
|
|
# building, and confirm on the appliance with `pkg config abi`.
|
|
HOST_ABI="$(pkg config abi 2>/dev/null || echo "unknown")"
|
|
if [ "$ABI" != "$HOST_ABI" ]; then
|
|
echo "warning: building for ${ABI}, but this host is ${HOST_ABI}." >&2
|
|
echo "warning: The binaries must genuinely be ${ARCH} and must link against" >&2
|
|
echo "warning: the target's base libraries — an ABI string alone does not" >&2
|
|
echo "warning: make an amd64 binary run on ARM. Verify with:" >&2
|
|
echo "warning: file ${BIN_DIR}/fips" >&2
|
|
echo "warning: Target mapping: https://docs.netgate.com/pfsense/en/latest/releases/versions.html" >&2
|
|
fi
|
|
|
|
# --- Toolchain provenance -------------------------------------------
|
|
#
|
|
# rust-toolchain.toml pins an exact compiler, and rustup honours it
|
|
# wherever rustup has binaries. aarch64 FreeBSD is not such a place:
|
|
#
|
|
# rustup target add aarch64-unknown-freebsd
|
|
# -> no prebuilt artifacts available for target
|
|
# (and on the platform itself)
|
|
# -> installer for platform 'aarch64-unknown-freebsd' not found
|
|
#
|
|
# so an ARM build uses the ports Rust, and the ports cargo ignores
|
|
# rust-toolchain.toml entirely. The pin is therefore not a property
|
|
# every build of this package has. Rather than let that be silent, the
|
|
# version actually used is checked here and recorded in the package.
|
|
RUST_VERSION="$(rustc --version 2>/dev/null | awk '{print $2}')"
|
|
[ -n "$RUST_VERSION" ] \
|
|
|| { echo "error: no rustc on PATH" >&2; exit 1; }
|
|
PINNED_VERSION="$(sed -n 's/.*channel *= *"\([^"]*\)".*/\1/p' \
|
|
"${PROJECT_ROOT}/rust-toolchain.toml" 2>/dev/null | head -1)"
|
|
|
|
# True when $1 is strictly older than $2, comparing dotted numerics.
|
|
version_lt() {
|
|
[ "$1" != "$2" ] || return 1
|
|
[ "$(printf '%s\n%s\n' "$1" "$2" \
|
|
| sort -t. -k1,1n -k2,2n -k3,3n | head -1)" = "$1" ]
|
|
}
|
|
|
|
# Edition 2024 needs 1.85. Below it the build dies deep inside a
|
|
# dependency with a message that never names the real cause, so refuse
|
|
# up front where the error can say what is wrong.
|
|
if version_lt "$RUST_VERSION" "1.85.0"; then
|
|
echo "error: rustc ${RUST_VERSION} is below the edition-2024 floor (1.85.0)." >&2
|
|
echo "error: This crate cannot be built with it." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PIN_HONOURED="yes"
|
|
if [ -n "$PINNED_VERSION" ] && [ "$RUST_VERSION" != "$PINNED_VERSION" ]; then
|
|
PIN_HONOURED="no"
|
|
echo "notice: rustc ${RUST_VERSION} is NOT the pinned ${PINNED_VERSION}." >&2
|
|
echo "notice: rust-toolchain.toml pins ${PINNED_VERSION}; this build does not" >&2
|
|
echo "notice: honour it. Expected on aarch64, where rustup ships no toolchain;" >&2
|
|
echo "notice: unexpected anywhere rustup works, and worth investigating there." >&2
|
|
echo "notice: The package records this — see 'pkg info -A'." >&2
|
|
fi
|
|
|
|
# Where the compiler came from, when it came from ports (ARM). Pinning
|
|
# the ports package version is the only reproducibility available on a
|
|
# platform rustup does not serve.
|
|
RUST_PKG="$(pkg info -q rust 2>/dev/null | head -1)"
|
|
|
|
# Static linking is unusable on aarch64 FreeBSD, so the default does not
|
|
# apply there. A statically linked aarch64 binary faults at addr=0x0
|
|
# exactly where posix_spawn should be, killing the process the first time
|
|
# it spawns anything:
|
|
#
|
|
# openat("/dev/null", O_RDONLY|O_CLOEXEC) = 9
|
|
# pipe2() = 0
|
|
# pipe2() = 0
|
|
# SIGNAL 11 (SIGSEGV) code=SEGV_MAPERR addr=0x0
|
|
#
|
|
# The same trace on static amd64 reaches rfork(RFSPAWN) and spawns
|
|
# normally, so this is specific to the architecture, not to static
|
|
# linking. FIPS spawns sysctl in is_ipv6_disabled() at the top of
|
|
# TunDevice::create, so the daemon dies during TUN setup and looks like a
|
|
# TUN bug; with tun.enabled false it never spawns and appears healthy.
|
|
#
|
|
# Refused rather than silently downgraded: a package that quietly linked
|
|
# differently from what was asked is how the wrong artifact ships.
|
|
if [ "$STATIC" -eq 1 ] && [ "$ARCH" = "aarch64" ]; then
|
|
echo "error: --static is not supported on aarch64: a statically linked" >&2
|
|
echo "error: aarch64 FreeBSD binary segfaults at posix_spawn, so the daemon" >&2
|
|
echo "error: dies the first time it shells out (sysctl, during TUN setup)." >&2
|
|
echo "error: Build with --dynamic and check ldd on the target, since that" >&2
|
|
echo "error: reintroduces the base-drift exposure static linking removes." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$NO_BUILD" -eq 0 ]; then
|
|
pkg_cargo_build "$PROJECT_ROOT" "$TARGET"
|
|
fi
|
|
|
|
pkg_require_binaries "$BIN_DIR" fips fipsctl fipstop
|
|
|
|
# crt-static is a request, not a guarantee: a target that does not
|
|
# respect it still links dynamically and says nothing. Shipping a
|
|
# dynamic binary while believing it static would put back exactly the
|
|
# drift this flag exists to remove, so check the artifact, not the flag.
|
|
if [ "$STATIC" -eq 1 ]; then
|
|
for bin in fips fipsctl fipstop; do
|
|
if ! file -b "${BIN_DIR}/${bin}" | grep -q 'statically linked'; then
|
|
echo "error: --static was requested but ${bin} is not statically linked:" >&2
|
|
echo "error: $(file -b "${BIN_DIR}/${bin}")" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "==> verified: all three binaries are statically linked"
|
|
fi
|
|
|
|
|
|
# The ABI in the manifest is an assertion about the binaries, and pkg
|
|
# believes it without looking. Check it against what is actually in
|
|
# BIN_DIR, on every build.
|
|
#
|
|
# The --target cross-check above cannot cover this: it compares the
|
|
# requested triple with the requested ABI, so a host build that names a
|
|
# foreign ABI (`--abi FreeBSD:15:aarch64` with no --target) passes it and
|
|
# then packages the host's binaries under a foreign arch. That produces
|
|
# a package which installs on the appliance and cannot exec — the exact
|
|
# failure the naming rules exist to prevent. check-pfsense-pkg.sh catches
|
|
# it, but only if someone runs it; the build should not emit it at all.
|
|
case "$ARCH" in
|
|
amd64) arch_signature="x86-64" ;;
|
|
aarch64) arch_signature="aarch64" ;;
|
|
i386) arch_signature="Intel 80386" ;;
|
|
*) arch_signature="" ;;
|
|
esac
|
|
if [ -n "$arch_signature" ]; then
|
|
binary_description="$(file -b "${BIN_DIR}/fips" 2>/dev/null)"
|
|
case "$binary_description" in
|
|
*"$arch_signature"*) ;;
|
|
*)
|
|
echo "error: the ABI says ${ARCH} (${ABI}), but ${BIN_DIR}/fips is not:" >&2
|
|
echo "error: ${binary_description}" >&2
|
|
echo "error: pkg trusts the manifest, so this package would install on" >&2
|
|
echo "error: the appliance and then fail to exec. Build for ${ARCH}, or" >&2
|
|
echo "error: correct --abi." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/fips-pfsense-pkg.XXXXXX")"
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/fips-pfsense-work.XXXXXX")"
|
|
trap 'rm -rf "$STAGE" "$WORK"' EXIT
|
|
|
|
echo "==> staging into ${STAGE}"
|
|
pkg_stage_tree "$STAGE"
|
|
pkg_stage_binaries "$BIN_DIR" "$STAGE" fips fipsctl fipstop
|
|
|
|
# --- Configuration sample -------------------------------------------
|
|
#
|
|
# The shipped config is the common default with its `dns:` block
|
|
# replaced by the pfSense one in fips.yaml.dns (which records why the
|
|
# responder binds IPv4 loopback here). Everything else — identity,
|
|
# transports, peers — is tracked from packaging/common/fips.yaml like
|
|
# every other platform's package, so it does not drift.
|
|
#
|
|
# Owning one block silently would hide a new dns option added upstream,
|
|
# so the key set of the common block is checked first: anything beyond
|
|
# what fips.yaml.dns already accounts for fails the build.
|
|
COMMON_CONFIG="${PROJECT_ROOT}/packaging/common/fips.yaml"
|
|
DNS_BLOCK="${SCRIPT_DIR}/fips.yaml.dns"
|
|
STAGED_CONFIG="${STAGE}/usr/local/etc/fips/fips.yaml.sample"
|
|
|
|
# Active (uncommented) keys in the common config's dns block. bind_addr
|
|
# is commented out there, so it is deliberately not in this list.
|
|
EXPECTED_DNS_KEYS="enabled port"
|
|
|
|
actual_dns_keys="$(awk '
|
|
/^[^[:space:]#]/ { section = $1; sub(/:.*/, "", section); next }
|
|
section != "dns" { next }
|
|
/^[[:space:]]*#/ { next }
|
|
/^[[:space:]]+[a-z_]+:/ {
|
|
key = $1; sub(/:.*/, "", key); print key
|
|
}
|
|
' "$COMMON_CONFIG" | tr '\n' ' ' | sed 's/ *$//')"
|
|
|
|
if [ "$actual_dns_keys" != "$EXPECTED_DNS_KEYS" ]; then
|
|
echo "error: the dns: block in ${COMMON_CONFIG} has changed." >&2
|
|
echo "error: expected keys: ${EXPECTED_DNS_KEYS}" >&2
|
|
echo "error: found keys: ${actual_dns_keys}" >&2
|
|
echo "error: This package substitutes its own dns: block, so a new or" >&2
|
|
echo "error: renamed option there would be dropped silently. Fold the" >&2
|
|
echo "error: change into ${DNS_BLOCK} and update EXPECTED_DNS_KEYS." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Splice: copy the common config, replacing the top-level dns: block.
|
|
# Skipping runs until the next top-level key, so the blank and indented
|
|
# lines inside the block go with it.
|
|
awk -v blockfile="$DNS_BLOCK" '
|
|
BEGIN {
|
|
while ((getline line < blockfile) > 0) { block = block line "\n" }
|
|
if (block == "") { print "error: dns block file is empty" > "/dev/stderr"; exit 1 }
|
|
}
|
|
/^dns:[[:space:]]*$/ { replaced++; printf "%s", block; skipping = 1; next }
|
|
skipping && /^[^[:space:]#]/ { skipping = 0 }
|
|
skipping { next }
|
|
{ print }
|
|
END {
|
|
if (replaced != 1) {
|
|
printf "error: replaced %d dns: blocks, expected exactly 1\n", replaced > "/dev/stderr"
|
|
exit 1
|
|
}
|
|
}
|
|
' "$COMMON_CONFIG" > "${WORK}/fips.yaml"
|
|
|
|
# Belt and braces: the staged config must actually carry the pfSense
|
|
# bind address, and must not still carry the ::1 default.
|
|
grep -Fqx ' bind_addr: "127.0.0.1"' "${WORK}/fips.yaml" || {
|
|
echo "error: staged config is missing the pfSense dns.bind_addr line" >&2
|
|
exit 1
|
|
}
|
|
|
|
# fips.yaml may hold a node private key (nsec:), so it is never
|
|
# world-readable — 0600, like the Debian, macOS and FreeBSD packages.
|
|
install -m 0600 "${WORK}/fips.yaml" "$STAGED_CONFIG"
|
|
|
|
install -m 0644 "${PROJECT_ROOT}/packaging/common/hosts" \
|
|
"${STAGE}/usr/local/etc/fips/hosts.sample"
|
|
install -m 0644 "${SCRIPT_DIR}/fips.conf" \
|
|
"${STAGE}/usr/local/etc/fips/fips.conf.sample"
|
|
# Not a .sample: it is the source fips.sh copies into
|
|
# /var/etc/newsyslog.conf.d at every start, never edited in place.
|
|
install -m 0644 "${SCRIPT_DIR}/fips.newsyslog" \
|
|
"${STAGE}/usr/local/etc/fips/fips.newsyslog"
|
|
|
|
install -m 0755 "${SCRIPT_DIR}/fips.sh" "${STAGE}/usr/local/etc/rc.d/fips.sh"
|
|
|
|
install -m 0755 "${SCRIPT_DIR}/fips-dns-setup" \
|
|
"${SCRIPT_DIR}/fips-dns-teardown" \
|
|
"${STAGE}/usr/local/libexec/fips/"
|
|
install -m 0644 "${SCRIPT_DIR}/fips-unbound-custom.php" \
|
|
"${STAGE}/usr/local/libexec/fips/"
|
|
|
|
# Record what produced this package, in the package. "Which compiler
|
|
# built this, and is it statically linked?" should be answerable from
|
|
# the artifact via `pkg info -A fips`, not from the memory of whoever
|
|
# ran the build — especially on aarch64, where the answer is not the
|
|
# pinned toolchain.
|
|
LINKAGE="dynamic"
|
|
[ "$STATIC" -eq 1 ] && LINKAGE="static"
|
|
ANNOTATIONS=" pfsense_products: \"${PRODUCTS}\"
|
|
built_with: \"rustc ${RUST_VERSION}\"
|
|
toolchain_pin: \"${PINNED_VERSION:-unset}\"
|
|
pin_honoured: \"${PIN_HONOURED}\"
|
|
linkage: \"${LINKAGE}\""
|
|
if [ -n "$RUST_PKG" ]; then
|
|
ANNOTATIONS="${ANNOTATIONS}
|
|
rust_pkg: \"${RUST_PKG}\""
|
|
fi
|
|
|
|
DESC="$(cat "${SCRIPT_DIR}/pkg-descr")"
|
|
|
|
# The config files get @sample semantics — copied into place on install
|
|
# if absent, removed on deinstall only if unmodified — but spelled out as
|
|
# manifest scripts: the @sample plist keyword lives in the ports tree
|
|
# (/usr/ports/Keywords/sample.ucl), which neither a plain pkg-create
|
|
# host nor pfSense itself has.
|
|
cat > "${STAGE}/+MANIFEST" <<EOF
|
|
$(pkg_manifest_header "$VERSION" "$ABI" "Self-organizing encrypted mesh network on Nostr identities (pfSense build)" "$DESC")
|
|
annotations: {
|
|
${ANNOTATIONS}
|
|
}
|
|
scripts: {
|
|
post-install: <<EOD
|
|
# Control-socket access group: fips.sh creates /var/run/fips as
|
|
# root:fips 0750, so members can use fipsctl/fipstop without root.
|
|
$(pkg_group_script)
|
|
# Install-if-absent config. fips.yaml may hold a node private key
|
|
# (nsec:), so it is 0600; FreeBSD has no "root" group, wheel is gid 0.
|
|
$(pkg_sample_seed_script fips.yaml:0600 hosts:0644 fips.conf:0644)
|
|
# pkg upgrade runs the old package's pre-deinstall, which stops the
|
|
# daemon and leaves this marker if it had been running. Bring it back up
|
|
# on the new binaries, and only then.
|
|
if [ -f /var/run/fips/upgrade-restart ]; then
|
|
rm -f /var/run/fips/upgrade-restart
|
|
/usr/local/etc/rc.d/fips.sh onestart >/dev/null 2>&1 || true
|
|
fi
|
|
if [ "\${PKG_UPGRADE:-}" != "true" ]; then
|
|
echo ""
|
|
echo "FIPS installed. To finish:"
|
|
echo " 1. vi /usr/local/etc/fips/fips.yaml # identity and peers"
|
|
echo " 2. /usr/local/etc/rc.d/fips.sh start"
|
|
echo " 3. /usr/local/libexec/fips/fips-dns-setup # .fips in the DNS Resolver"
|
|
echo ""
|
|
echo "Step 3 edits config.xml, so it is not run for you. It is"
|
|
echo "revertable from Diagnostics > Backup & Restore > Config History."
|
|
echo ""
|
|
echo "The mesh needs 'Allow IPv6' (System > Advanced > Networking),"
|
|
echo "which is on by default. If it has been turned off, pfSense blocks"
|
|
echo "all IPv6 and the fd00::/8 mesh cannot pass traffic even though"
|
|
echo ".fips still resolves — see the README."
|
|
echo ""
|
|
echo "Not a Netgate-supported package. A firmware upgrade keeps it"
|
|
echo "(it is not a pfSense-pkg-*); after a major upgrade, reinstall the"
|
|
echo "package built for the new base. To upgrade the package itself use"
|
|
echo "'pkg install ./<file>.pkg', then 'fips.sh restart'."
|
|
fi
|
|
EOD
|
|
pre-deinstall: <<EOD
|
|
# Stop the daemon so its binary is never replaced (upgrade) or removed
|
|
# (deinstall) underneath a running process, and remember whether it was
|
|
# running so post-install can restore that state on upgrade.
|
|
if /usr/local/etc/rc.d/fips.sh status >/dev/null 2>&1; then
|
|
if [ "\${PKG_UPGRADE:-}" = "true" ]; then
|
|
mkdir -p /var/run/fips && touch /var/run/fips/upgrade-restart
|
|
fi
|
|
fi
|
|
/usr/local/etc/rc.d/fips.sh stop >/dev/null 2>&1 || true
|
|
if [ "\${PKG_UPGRADE:-}" != "true" ]; then
|
|
# Removal: take the .fips block back out of config.xml. Left behind,
|
|
# it would forward the fips. zone to a port nothing listens on.
|
|
if ! /usr/local/libexec/fips/fips-dns-teardown; then
|
|
echo "fips: the .fips block is still in the DNS Resolver custom options;"
|
|
echo "fips: remove it by hand under Services > DNS Resolver > Custom options,"
|
|
echo "fips: or the fips. zone stays forwarded to a port nothing listens on."
|
|
fi
|
|
$(pkg_sample_purge_script fips.yaml hosts fips.conf)
|
|
fi
|
|
EOD
|
|
}
|
|
EOF
|
|
|
|
cat > "${STAGE}/pkg-plist" <<'EOF'
|
|
bin/fips
|
|
bin/fipsctl
|
|
bin/fipstop
|
|
etc/fips/fips.conf.sample
|
|
etc/fips/fips.newsyslog
|
|
etc/fips/fips.yaml.sample
|
|
etc/fips/hosts.sample
|
|
etc/rc.d/fips.sh
|
|
libexec/fips/fips-dns-setup
|
|
libexec/fips/fips-dns-teardown
|
|
libexec/fips/fips-unbound-custom.php
|
|
@dir etc/fips
|
|
EOF
|
|
|
|
OUT="${PROJECT_ROOT}/deploy/fips-${VERSION}-pfsense-${PRODUCT_TAG}-${ARCH}.pkg"
|
|
|
|
pkg_create_package "$STAGE" "${PROJECT_ROOT}/deploy" "$VERSION" "$OUT"
|