mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Access layer for phones and laptops to reach FIPS routers on OpenWrt, stacked on the 802.11s mesh backhaul from #123. Squashed from five commits by Arjen (Origami74); their original messages follow. * feat(openwrt): open !FIPS access SSID — fips-ap-setup helper, default transport binding, how-to Client access layer for phones and laptops: every FIPS router broadcasts the same open SSID ('!FIPS' — the leading '!' sorts it to the top of alphabetically ordered network pickers), forming one standard ESS. Clients save it once and roam between all FIPS routers natively, with FIPS's Noise IK handshake as the only security layer: - fips-ap-setup: opt-in UCI helper that creates the 'fips-ap0' open AP (encryption none — security type must be uniform across routers or clients treat the ESS as different saved networks), an isolated network with a static ULA /64, RA-only odhcpd addressing (stateless SLAAC, no DHCP — the minimum that satisfies Android's provisioning check; no internet by design, so phones keep cellular as default route), and a locked-down fips_ap firewall zone (no path to br-lan or the WAN; only ICMPv6, mDNS, and the FIPS transports reachable). 'remove' subcommand undoes it. Radio setup stays opt-in; a package must not commandeer radios on install. - fips.yaml: ship 'ap0'/'ap1' Ethernet-transport entries commented out (matching the 802.11s mesh backhaul) so a stock install that never creates fips-ap* logs no per-boot "interface missing" bind warning; fips-ap-setup uncomments the matching block when it creates the interface and re-comments it on remove. - Regression test: extend shipped_openwrt_config_parses to assert the ap0/ap1 entries ship commented out and still parse once uncommented, alongside mesh0/mesh1. - Packaging: install the helper in ipk/apk/buildroot (three synced copies), extend CI structural checks and shellcheck targets. - docs/how-to/set-up-open-access-ssid.md: full guide, including the one-time 'no internet, stay connected' acceptance (stored per SSID, covers every FIPS router) and the security-type-uniformity constraint. * docs(openwrt): correct open-SSID/mesh security framing — peering is open by design The fips-ap-setup/fips-mesh-setup comments and both how-tos claimed a stranger "cannot pass the FIPS handshake" / "their frames die at the handshake" / the handshake surface "drops them". That is wrong: FIPS peer admission is open. An inbound handshake from any net-new identity is promoted (node::handlers::handshake::promote_connection), gated only by the daemon's max-peers cap — there is no allowlist, no PSK, and the AuthChallenge path is not wired to admission. The Noise IK handshake provides authentication (no impersonation of another identity, no MITM), not authorization. Restate the model accurately in all five places: a stranger on the open SSID (or the open mesh) can associate AND form a FIPS peer link — that is the point of open access. Containment is the isolated fips_ap zone (no path to br-lan or the WAN) plus the max-peers cap, not the handshake. Clarify that AP client isolation is an L2 control only: a peered stranger is an overlay peer like any other, so the FIPS overlay, not L2, is the trust boundary between clients. * feat(openwrt): serve DHCPv4 on the access SSID from a fixed roamable subnet RA-only addressing satisfied Android's provisioning check but left anything expecting IPv4 with a self-assigned address and a "no IP" complaint. dnsmasq now leases out of 10.21.<N>.0/24 (N = radio index; prefix echoes FIPS port 2121), deliberately identical on every router: a roaming client keeps its lease across the ESS, and dnsmasq's authoritative mode — the OpenWrt default, pinned by the helper — ACKs the renew a foreign router never issued. Lease collisions across routers surface as a NAK on renew and the client re-DHCPs. The dhcp section's 'dhcpv4 server' is read by both dnsmasq (default images) and odhcpd (only with maindhcp), so either arrangement serves. A DHCPv4/udp-67 accept rule joins the fips_ap zone; DHCPv6 stays off, the ULA RA stays as-is, and nothing depends on an upstream. The zone remains isolated — no forwardings, no internet. * feat(openwrt): enable mDNS rendezvous from fips-ap-setup Phone FIPS apps cannot open raw-Ethernet sockets, so DNS-SD is how they find the router's daemon — but node.rendezvous.lan defaults to off. Ship the lan block commented in fips.yaml (consistent with the apN transport entries) and have fips-ap-setup uncomment it when creating the access SSID. The awk match is scoped to node.rendezvous because transports.ethernet carries a 'lan' entry at the same indent. The switch is daemon-wide, so 'remove' deliberately leaves it on rather than guess whether other transports rely on it. * fix(openwrt): bind UDP dual-stack [::]:2121 so access-SSID clients reach it The shipped router config bound the UDP transport "0.0.0.0:2121" (IPv4 wildcard) while the mDNS LAN advert announces every interface address, including the router's IPv6 link-local — which phones on the !FIPS access SSID rightly prefer (their cellular default route swallows v4, and fd00::/8 is captured by the Myco mesh TUN). Result: the client's Noise msg1 arrives on an unbound v6 port and is silently lost; the handshake resends and times out. Symptom chain (observed on-device): mDNS resolve OK, platform push OK, "Sent Noise handshake message 1" to [fe80::…%N]:2121, four resends, no reply, 30 s stale-timeout. OpenWrt is Linux (bindv6only=0), so "[::]" accepts IPv4 via v4-mapped addresses too — nothing is lost. packaging/common is deliberately left on "0.0.0.0" for now: Windows defaults IPV6_V6ONLY=1, where "[::]" would drop v4 instead.
294 lines
9.9 KiB
Bash
Executable File
294 lines
9.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build a FIPS .ipk package for OpenWrt without the OpenWrt SDK.
|
|
#
|
|
# Uses cargo-zigbuild for cross-compilation and assembles the .ipk directly.
|
|
# An .ipk is just an ar archive containing two tarballs — no SDK required.
|
|
#
|
|
# Usage:
|
|
# ./packaging/openwrt/build-ipk.sh [--arch <name>]
|
|
#
|
|
# Architectures (--arch):
|
|
# aarch64 GL.iNet MT3000/MT6000, RPi 3/4/5, most modern routers [default]
|
|
# mipsel Older MIPS routers (TP-Link, Netgear, GL.iNet AR750)
|
|
# mips MIPS big-endian routers (ath79)
|
|
# arm 32-bit ARM routers (Cortex-A7)
|
|
# x86_64 x86 routers / VMs
|
|
#
|
|
# Output: dist/fips_<version>_<openwrt-arch>.ipk
|
|
#
|
|
# Prerequisites:
|
|
# cargo install cargo-zigbuild
|
|
# rustup target add <rust-triple> (added automatically if missing)
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Arguments
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ARCH="aarch64"
|
|
BIN_DIR="" # if set, use prebuilt binaries from here instead of compiling
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--arch) ARCH="$2"; shift 2 ;;
|
|
--arch=*) ARCH="${1#*=}"; shift ;;
|
|
--bin-dir) BIN_DIR="$2"; shift 2 ;;
|
|
--bin-dir=*) BIN_DIR="${1#*=}"; shift ;;
|
|
*) echo "Unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Architecture mapping
|
|
#
|
|
# RUST_TARGET — passed to cargo --target
|
|
# OPENWRT_ARCH — goes in the .ipk control file and filename
|
|
# ---------------------------------------------------------------------------
|
|
|
|
case "$ARCH" in
|
|
aarch64)
|
|
RUST_TARGET="aarch64-unknown-linux-musl"
|
|
OPENWRT_ARCH="aarch64_cortex-a53"
|
|
;;
|
|
mipsel)
|
|
RUST_TARGET="mipsel-unknown-linux-musl"
|
|
OPENWRT_ARCH="mipsel_24kc"
|
|
;;
|
|
mips)
|
|
RUST_TARGET="mips-unknown-linux-musl"
|
|
OPENWRT_ARCH="mips_24kc"
|
|
;;
|
|
arm)
|
|
RUST_TARGET="arm-unknown-linux-musleabihf"
|
|
OPENWRT_ARCH="arm_cortex-a7"
|
|
;;
|
|
x86_64)
|
|
RUST_TARGET="x86_64-unknown-linux-musl"
|
|
OPENWRT_ARCH="x86_64"
|
|
;;
|
|
*)
|
|
echo "Unknown arch: $ARCH" >&2
|
|
echo "Valid: aarch64, mipsel, mips, arm, x86_64" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
FILES_DIR="$SCRIPT_DIR/files"
|
|
DIST_DIR="$PROJECT_ROOT/dist"
|
|
|
|
PKG_NAME="fips"
|
|
PKG_VERSION="${PKG_VERSION:-$(cd "$PROJECT_ROOT" && git describe --tags --always --dirty 2>/dev/null || echo "0.1.0")}"
|
|
|
|
echo "==> Building $PKG_NAME $PKG_VERSION for $OPENWRT_ARCH ($RUST_TARGET)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Obtain binaries
|
|
#
|
|
# Either use a directory of prebuilt binaries (--bin-dir; CI cross-compiles
|
|
# once in a shared job and hands them to both the .ipk and .apk packagers), or
|
|
# compile from source here for a self-contained local build.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ -n "$BIN_DIR" ]; then
|
|
RELEASE_DIR="$BIN_DIR"
|
|
echo "==> Using prebuilt binaries from $RELEASE_DIR"
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
[ -f "$RELEASE_DIR/$bin" ] || {
|
|
echo "Error: prebuilt binary not found: $RELEASE_DIR/$bin" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
else
|
|
if ! command -v cargo-zigbuild &>/dev/null; then
|
|
echo "Error: cargo-zigbuild not found." >&2
|
|
echo " Install: cargo install cargo-zigbuild" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! rustup target list --installed | grep -q "^$RUST_TARGET$"; then
|
|
echo "==> Adding Rust target $RUST_TARGET..."
|
|
rustup target add "$RUST_TARGET"
|
|
fi
|
|
|
|
echo "==> Compiling..."
|
|
cd "$PROJECT_ROOT"
|
|
cargo zigbuild \
|
|
--release \
|
|
--target "$RUST_TARGET" \
|
|
--bin fips \
|
|
--bin fipsctl \
|
|
--bin fipstop \
|
|
--bin fips-gateway
|
|
|
|
RELEASE_DIR="$PROJECT_ROOT/target/$RUST_TARGET/release"
|
|
|
|
echo "==> Stripping binaries..."
|
|
STRIP="${LLVM_STRIP:-strip}"
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
"$STRIP" "$RELEASE_DIR/$bin" 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
SIZE=$(du -sh "$RELEASE_DIR/fips" | cut -f1)
|
|
echo " fips: $SIZE"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Assemble .ipk
|
|
# ---------------------------------------------------------------------------
|
|
# An .ipk is an ar archive with three members:
|
|
# debian-binary — format version ("2.0\n")
|
|
# control.tar.gz — package metadata, conffiles, pre/post scripts
|
|
# data.tar.gz — the actual filesystem tree
|
|
|
|
WORK_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
|
|
|
CONTROL_DIR="$WORK_DIR/control"
|
|
DATA_DIR="$WORK_DIR/data"
|
|
mkdir -p "$CONTROL_DIR" "$DATA_DIR"
|
|
|
|
# ---- data tree ----
|
|
|
|
install -d "$DATA_DIR/usr/bin"
|
|
install -m 0755 "$RELEASE_DIR/fips" "$DATA_DIR/usr/bin/fips"
|
|
install -m 0755 "$RELEASE_DIR/fipsctl" "$DATA_DIR/usr/bin/fipsctl"
|
|
install -m 0755 "$RELEASE_DIR/fipstop" "$DATA_DIR/usr/bin/fipstop"
|
|
install -m 0755 "$RELEASE_DIR/fips-gateway" "$DATA_DIR/usr/bin/fips-gateway"
|
|
install -m 0755 "$FILES_DIR/usr/bin/fips-mesh-setup" "$DATA_DIR/usr/bin/fips-mesh-setup"
|
|
install -m 0755 "$FILES_DIR/usr/bin/fips-ap-setup" "$DATA_DIR/usr/bin/fips-ap-setup"
|
|
|
|
install -d "$DATA_DIR/etc/init.d"
|
|
install -m 0755 "$FILES_DIR/etc/init.d/fips" "$DATA_DIR/etc/init.d/fips"
|
|
install -m 0755 "$FILES_DIR/etc/init.d/fips-gateway" "$DATA_DIR/etc/init.d/fips-gateway"
|
|
|
|
install -d "$DATA_DIR/etc/fips"
|
|
install -m 0600 "$FILES_DIR/etc/fips/fips.yaml" "$DATA_DIR/etc/fips/fips.yaml"
|
|
install -m 0755 "$FILES_DIR/etc/fips/firewall.sh" "$DATA_DIR/etc/fips/firewall.sh"
|
|
|
|
install -d "$DATA_DIR/etc/dnsmasq.d"
|
|
install -m 0644 "$FILES_DIR/etc/dnsmasq.d/fips.conf" "$DATA_DIR/etc/dnsmasq.d/fips.conf"
|
|
|
|
install -d "$DATA_DIR/etc/sysctl.d"
|
|
install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-bridge.conf" "$DATA_DIR/etc/sysctl.d/fips-bridge.conf"
|
|
install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-gateway.conf" "$DATA_DIR/etc/sysctl.d/fips-gateway.conf"
|
|
|
|
install -d "$DATA_DIR/etc/hotplug.d/net"
|
|
install -m 0755 "$FILES_DIR/etc/hotplug.d/net/99-fips" "$DATA_DIR/etc/hotplug.d/net/99-fips"
|
|
|
|
install -d "$DATA_DIR/etc/uci-defaults"
|
|
install -m 0755 "$FILES_DIR/etc/uci-defaults/90-fips-setup" "$DATA_DIR/etc/uci-defaults/90-fips-setup"
|
|
|
|
install -d "$DATA_DIR/lib/upgrade/keep.d"
|
|
install -m 0644 "$FILES_DIR/lib/upgrade/keep.d/fips" "$DATA_DIR/lib/upgrade/keep.d/fips"
|
|
|
|
# ---- control files ----
|
|
|
|
PKG_SIZE=$(du -sk "$DATA_DIR" | cut -f1)
|
|
|
|
cat > "$CONTROL_DIR/control" <<EOF
|
|
Package: $PKG_NAME
|
|
Version: $PKG_VERSION
|
|
Architecture: $OPENWRT_ARCH
|
|
Maintainer: FIPS Network
|
|
Section: net
|
|
Priority: optional
|
|
Depends: kmod-tun, kmod-br-netfilter, kmod-nft-nat, kmod-nf-conntrack, ip-full
|
|
Description: FIPS Mesh Network Daemon
|
|
Distributed, decentralized mesh networking over UDP, TCP, and raw Ethernet.
|
|
Provides a TUN interface (fips0) with ULA IPv6 addressing and a DNS
|
|
responder for .fips name resolution.
|
|
Installed-Size: $PKG_SIZE
|
|
EOF
|
|
|
|
# Mark fips.yaml as a conffile so opkg won't overwrite user edits on upgrade.
|
|
cat > "$CONTROL_DIR/conffiles" <<EOF
|
|
/etc/fips/fips.yaml
|
|
EOF
|
|
|
|
cat > "$CONTROL_DIR/postinst" <<'EOF'
|
|
#!/bin/sh
|
|
# Run first-boot UCI setup (the script deletes itself when done).
|
|
if [ -x /etc/uci-defaults/90-fips-setup ]; then
|
|
/etc/uci-defaults/90-fips-setup && rm -f /etc/uci-defaults/90-fips-setup
|
|
fi
|
|
|
|
/etc/init.d/fips enable
|
|
/etc/init.d/fips start
|
|
/etc/init.d/fips-gateway enable
|
|
/etc/init.d/fips-gateway start
|
|
exit 0
|
|
EOF
|
|
chmod 0755 "$CONTROL_DIR/postinst"
|
|
|
|
cat > "$CONTROL_DIR/prerm" <<'EOF'
|
|
#!/bin/sh
|
|
/etc/init.d/fips-gateway stop 2>/dev/null || true
|
|
/etc/init.d/fips-gateway disable 2>/dev/null || true
|
|
/etc/init.d/fips stop 2>/dev/null || true
|
|
/etc/init.d/fips disable 2>/dev/null || true
|
|
exit 0
|
|
EOF
|
|
chmod 0755 "$CONTROL_DIR/prerm"
|
|
|
|
# ---- pack ----
|
|
|
|
PKG_FILENAME="${PKG_NAME}_${PKG_VERSION}_${OPENWRT_ARCH}.ipk"
|
|
IPK_WORK="$WORK_DIR/ipk"
|
|
mkdir -p "$IPK_WORK"
|
|
|
|
echo "2.0" > "$IPK_WORK/debian-binary"
|
|
|
|
# Detect a tar that supports --format=gnu.
|
|
# On macOS, Homebrew's GNU tar is installed as 'gtar'; the system tar is BSD.
|
|
# Our filenames are short so BSD tar (ustar) works too, but gnu is preferred
|
|
# to match ipkg-build exactly and to embed numeric UID/GID.
|
|
# COPYFILE_DISABLE=1 suppresses macOS resource-fork (._*) files; no-op on Linux.
|
|
if command -v gtar &>/dev/null; then
|
|
# Homebrew GNU tar on macOS
|
|
TAR_CMD="gtar"
|
|
TAR_EXTRA_FLAGS="--format=gnu --numeric-owner"
|
|
elif tar --version 2>/dev/null | grep -q 'GNU tar'; then
|
|
# System tar is GNU tar (Linux)
|
|
TAR_CMD="tar"
|
|
TAR_EXTRA_FLAGS="--format=gnu --numeric-owner"
|
|
else
|
|
# macOS BSD tar (libarchive). Its default format is PAX (typeflag 0x78),
|
|
# which OpenWrt's busybox tar cannot handle. Force ustar explicitly.
|
|
TAR_CMD="tar"
|
|
TAR_EXTRA_FLAGS="--format=ustar"
|
|
fi
|
|
|
|
ipk_tar() {
|
|
# ipk_tar <output.tar.gz> <source-dir> [paths...]
|
|
local out="$1" src="$2"; shift 2
|
|
local mtime_flags=""
|
|
if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then
|
|
mtime_flags="--mtime=@$SOURCE_DATE_EPOCH"
|
|
fi
|
|
COPYFILE_DISABLE=1 "$TAR_CMD" $TAR_EXTRA_FLAGS $mtime_flags -czf "$out" -C "$src" "$@"
|
|
}
|
|
|
|
ipk_tar "$IPK_WORK/control.tar.gz" "$CONTROL_DIR" .
|
|
ipk_tar "$IPK_WORK/data.tar.gz" "$DATA_DIR" .
|
|
|
|
# The outer .ipk container is a gzip-compressed tar — NOT an ar archive.
|
|
# (Debian .deb uses ar; OpenWrt .ipk uses tar.gz.)
|
|
# Entries must be named with ./ prefix, as ipkg-build produces.
|
|
mkdir -p "$DIST_DIR"
|
|
ipk_tar "$DIST_DIR/$PKG_FILENAME" "$IPK_WORK" ./debian-binary ./control.tar.gz ./data.tar.gz
|
|
|
|
echo ""
|
|
echo "==> Done: dist/$PKG_FILENAME"
|
|
echo " $(du -sh "$DIST_DIR/$PKG_FILENAME" | cut -f1)"
|
|
echo ""
|
|
echo "Install on router:"
|
|
echo " scp -O dist/$PKG_FILENAME root@192.168.1.1:/tmp/"
|
|
echo " ssh root@192.168.1.1 opkg install /tmp/$PKG_FILENAME"
|