Rename the Ethernet per-interface config flag from discovery to listen, so the receive/transmit toggle pair reads as the symmetric announce (transmit) / listen (receive) neighbor-beacon vocabulary. The old discovery: key is still accepted via a serde alias, so deployed configs load unchanged; to_yaml re-emits it under the canonical listen: name. Marked deprecated for removal at the v2 cutover. Updates the config field + accessor, the transport listen_enabled local, the one struct-literal test consumer, the chaos sim config generator, packaged fips.yaml examples, and the classified operator-facing docs (ethernet neighbor-beacon subsystem prose; the generic Transport discovery capability prose is left unchanged). Adds a compat parse test asserting the legacy alias, the new key, and that deny_unknown_fields still rejects unknown keys. Behavior-neutral.
11 KiB
Security Reference
Consolidated security reference covering the nftables baseline, peer ACL file format, cryptographic primitives, rekey defaults, replay window, filesystem permissions, threat-resistance matrix, and default network exposures per transport. For the threat-model design and rationale, see ../design/fips-security.md. For the operator activation steps and drop-in recipes, see ../how-to/enable-mesh-firewall.md.
nftables Baseline
The shipped baseline is /etc/fips/fips.nft. It defines a single
nftables table inet fips with one chain hooked at input, structured
as follows:
| Step | Rule | Effect |
|---|---|---|
| 1 | iifname != "fips0" return |
Match only traffic arriving on fips0; everything else short-circuits. |
| 2 | ct state established,related accept |
Allow conntrack replies and related ICMPv6 errors. |
| 3 | icmpv6 type echo-request accept |
Allow IPv6 echo (ping6 reachability). |
| 4 | include "/etc/fips/fips.d/*.nft" |
Splice in operator drop-ins (empty matches nothing). |
| 5 | counter drop |
Default-deny everything else; counter increments on every drop. |
Outbound from fips0 is unrestricted. The baseline is a documented
dpkg conffile — operator edits to /etc/fips/fips.nft are preserved
across upgrades.
The systemd unit is fips-firewall.service (oneshot). It is not
enabled by default; activation is an explicit operator gesture
documented in
../how-to/enable-mesh-firewall.md.
Drop-In File Format
Operator extensions live under /etc/fips/fips.d/ with the .nft
suffix. Each file is included inline into the inbound chain at the
marked point and may contain any nftables rule lines valid in that
context.
Naming convention: <purpose>-from-<source>.nft keeps drop-ins easy
to scan. Examples shipped in the design discussion:
ssh-from-bastion.nft— accept TCP/22 from a single mesh-node addresshttp-from-cluster.nft— accept TCP/80 from a/64mesh-address prefixdns-public.nft— accept UDP/53 and TCP/53 from any mesh nodegit-from-trusted.nft— accept TCP/9418 from a set of mesh-node addresses
After editing, reload via
sudo systemctl reload-or-restart fips-firewall.service (or
equivalently sudo nft -f /etc/fips/fips.nft since the file is
idempotent).
Cryptographic Primitives
| Component | Choice | Where Used |
|---|---|---|
| Curve | secp256k1 | FMP IK, FSP XK, Schnorr signatures |
| Diffie-Hellman | ECDH on secp256k1 (x-only normalized) | Noise IK, Noise XK |
| AEAD | ChaCha20-Poly1305 | FMP link encryption, FSP session encryption |
| Hash | SHA-256 | NodeAddr derivation, Noise transcript |
| Key derivation | HKDF-SHA256 | Noise key schedule |
| Signatures | secp256k1 Schnorr | TreeAnnounce, LookupResponse proof, Nostr adverts |
| Noise pattern (link) | Noise_IK_secp256k1_ChaChaPoly_SHA256 |
FMP link layer (IK with epoch payload) |
| Noise pattern (session) | Noise_XK_secp256k1_ChaChaPoly_SHA256 |
FSP session layer (XK with epoch payload) |
These choices align with the Nostr cryptographic stack (secp256k1 + ChaCha20-Poly1305 + SHA-256) and the NIP-44 encrypted messaging standard.
Rekey Defaults
Both link-layer and session-layer Noise sessions rekey under one of
two triggers, configurable under node.rekey.*:
| Parameter | Default | Description |
|---|---|---|
enabled |
true |
Master switch. |
after_secs |
120 |
Time-based rekey threshold. |
after_messages |
65536 |
Message-count rekey threshold. |
In addition to the configurable triggers, the daemon retains the old
session keys for a fixed 10-second drain window after each
cutover (compile-time constant DRAIN_WINDOW_SECS in
src/node/handlers/rekey.rs). Rekey rotates the Noise key schedule
and the session indices; old session keys are kept in
previous_session for the drain window so in-flight packets
encrypted under the old keys still decrypt.
Replay Window
Both layers use explicit per-packet counters with a sliding bitmap
window for replay protection. The bitmap is 2048 entries at both
layers — large enough to accommodate UDP reordering and packet loss
without false-positive replay rejection. Counters older than the
window are rejected. The same ReplayWindow and
decrypt_with_replay_check() implementation is used at both the FMP
and FSP layers.
Peer ACL
Mesh-level ACL files at /etc/fips/peers.allow and
/etc/fips/peers.deny give the operator allowlist/blocklist control
over which npubs may complete the FMP Noise IK link handshake.
File format:
- One entry per line. An entry is either a bech32
npub1..., an alias defined in/etc/fips/hosts, or the literalALLwildcard (case-insensitive). - Lines beginning with
#are comments. - Blank lines are ignored.
Evaluation order (first match wins, default-allow on no match):
peers.allow— if the peer matches an entry here (orALLis inpeers.allow), the handshake is admitted, regardless of anypeers.denyentry.peers.deny— if the peer matches an entry here (orALLis inpeers.deny), the handshake is refused.- Otherwise the peer is admitted.
peers.allow is not an exclusive gate on its own: an unlisted
peer falls through to step 3 and is admitted unless it appears in
peers.deny. To turn peers.allow into a strict allowlist, place
ALL in peers.deny so every unlisted peer is rejected at step 2.
The ALL wildcard makes the operator's posture explicit:
ALLinpeers.allowadmits every peer (same effect as the default-allow behavior, but documented in the file).ALLinpeers.denyblocks every peer except those listed inpeers.allow— the "allowlist-strict" posture.
In practice this collapses to a few common postures:
- Default-allow with denylist: leave
peers.allowempty; populatepeers.deny. All npubs may peer except those listed. - Allowlist-strict: populate
peers.allowand putALLinpeers.deny. Only the listed npubs may peer; everyone else is rejected at step 2.
A populated peers.allow with an empty peers.deny is not a
strict allowlist — it is equivalent to default-allow plus an
explicit "always-admit" set. The strict variant requires ALL
in peers.deny.
Aliases are resolved through /etc/fips/hosts at file-load
time. If peers.allow lists core-vm and /etc/fips/hosts
maps core-vm to a specific npub, that npub is admitted. If
core-vm is later remapped to a different npub, the ACL
re-resolves on the next mtime change. Operators should be aware
that ACL semantics follow the hosts-file aliasing, not just
the literal npubs visible in the file.
Both files are reloaded automatically when their mtime changes — no daemon restart or signal is needed. ACL evaluation runs after msg1 decryption but before any further peer-state mutation; rate-limited msg1s never reach the ACL.
Filesystem Permissions
| Path | Owner | Mode | Purpose |
|---|---|---|---|
/etc/fips/fips.key |
root:root | 0600 |
Persistent identity private key (sensitive). |
/etc/fips/fips.pub |
root:root | 0644 |
Public key (npub). |
/etc/fips/fips.yaml |
root:root | 0644 |
Daemon configuration (dpkg conffile). |
/etc/fips/fips.nft |
root:root | 0644 |
nftables baseline (dpkg conffile). |
/etc/fips/fips.d/ |
root:root | 0755 |
Operator drop-in directory. |
/etc/fips/hosts |
root:root | 0644 |
Optional hostname → npub map (dpkg conffile). |
/etc/fips/peers.allow |
root:root | 0644 |
Optional peer allowlist. |
/etc/fips/peers.deny |
root:root | 0644 |
Optional peer denylist. |
/run/fips/control.sock |
root:fips | 0770 |
Control socket (members of fips group can use fipsctl). |
/run/fips/ |
root:fips | 0750 |
Control socket parent directory. |
Adding a user to the fips group grants fipsctl access without
requiring root. The daemon chowns the control socket and its parent
directory at bind time.
Threat-Resistance Matrix
The link layer's threat-resistance matrix is consolidated here from the FMP design document:
| Threat | Mitigation |
|---|---|
| Connection exhaustion | Token-bucket rate limit + connection count limit |
| CPU exhaustion (msg1 flood) | Rate limit before crypto operations |
| Replay attacks | Counter-based nonces with sliding window (2048 entries) |
| State confusion | Strict handshake state machine validation |
| Spoofed encrypted packets | Index lookup + AEAD verification |
| Spoofed msg2 | Index lookup + Noise ephemeral key binding |
| Address spoofing | Cryptographic authority, not address-based |
| Session correlation | Index rotation on rekey |
Inbound exposure on fips0 |
Default-deny nftables baseline (operator opt-in) |
| Sybil identities | Discretionary peering + handshake rate limiting + optional peer ACL |
| Eclipse attack | Diverse peering across independent operators and transports |
| Unauthorized peer admission | Optional peers.allow allowlist consulted before handshake |
See ../design/fips-mesh-layer.md for the unauthenticated-attack-surface analysis (only handshake msg1 is reachable by unauthenticated parties), and ../design/fips-mesh-operation.md for the metadata-privacy model and the rejection of onion routing.
Default Network Exposures by Transport
| Transport | Default Inbound | Default Bind | Opt-in |
|---|---|---|---|
| UDP | None until bind_addr set |
0.0.0.0:2121 typical |
Operator sets transports.udp.bind_addr |
| TCP | None until bind_addr set |
None — outbound-only without bind | Operator sets transports.tcp.bind_addr |
| Ethernet | Listens on configured interface (raw AF_PACKET) |
EtherType 0x2121 on selected interface | Per-flag listen, announce, auto_connect, accept_connections |
| Tor | None until directory_service configured |
127.0.0.1:8443 (loopback only) |
Operator sets transports.tor.directory_service and configures HiddenServiceDir in torrc |
| BLE | Off by default | n/a | Operator enables transports.ble.* |
| Nostr discovery | Off by default | n/a (relay client, not a listener) | Operator sets node.discovery.nostr.enabled: true |
The mesh-layer fips0 interface is reachable from any mesh node that
can route to you, not only direct peers — your direct peers forward
traffic from any reachable mesh node onto your fips0. The
default-deny nftables baseline (operator opt-in) is the recommended
way to restrict inbound traffic on fips0. See
../how-to/enable-mesh-firewall.md.
See also
- ../design/fips-security.md — threat
model and design rationale for the
fips0baseline - ../design/fips-mesh-layer.md — FMP link encryption, replay protection, rate limiting
- ../design/fips-session-layer.md — FSP end-to-end encryption, Noise XK, replay window
- ../how-to/enable-mesh-firewall.md — operator activation and drop-in recipes
- configuration.md — full
node.rekey.*,node.rate_limit.*parameter tables