mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-06 03:28:24 +00:00
Merge master into next, carrying the NixOS flake module
Packaging only: the flake outputs, the NixOS module and its docs. No Rust source is touched, so the cargo quartet exercises none of it. Nothing local validates the Nix. nix is not installed on this host and no CI workflow builds the flake, so the module is unexercised on both lines rather than merely untested here.
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
rustPlatform.bindgenHook # sets LIBCLANG_PATH + clang for bindgen
|
||||
];
|
||||
|
||||
buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [
|
||||
buildInputs = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
|
||||
pkgs.dbus # libdbus-1.so.3, linked via bluer→libdbus-sys
|
||||
pkgs.stdenv.cc.cc.lib # libgcc_s.so.1, needed by every Rust binary
|
||||
];
|
||||
@@ -57,21 +57,29 @@
|
||||
src = ./.;
|
||||
# Drop the build dir and the usual editor/VCS noise so the source
|
||||
# hash is stable and unrelated edits don't trigger rebuilds.
|
||||
filter =
|
||||
path: type:
|
||||
(pkgs.lib.cleanSourceFilter path type) && (baseNameOf path != "target");
|
||||
filter = path: type: (pkgs.lib.cleanSourceFilter path type) && (baseNameOf path != "target");
|
||||
};
|
||||
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
|
||||
inherit buildInputs;
|
||||
|
||||
# Ship the default config, hosts file, and nftables baseline so
|
||||
# the NixOS module can reference them via $out/share/fips/ without
|
||||
# needing the source tree. DNS routing is handled declaratively
|
||||
# via services.resolved on NixOS (no setup/teardown scripts needed).
|
||||
postInstall = ''
|
||||
install -Dm 0644 ${./packaging/common/fips.yaml} $out/share/fips/fips.yaml
|
||||
install -Dm 0644 ${./packaging/common/hosts} $out/share/fips/hosts
|
||||
install -Dm 0644 ${./packaging/common/fips.nft} $out/share/fips/fips.nft
|
||||
'';
|
||||
|
||||
# autoPatchelfHook rewrites the RPATH of the built binaries so the
|
||||
# daemon finds libdbus-1.so.3 (linked via bluer→libdbus-sys) in the
|
||||
# Nix store at runtime — without it the `fips` binary fails to load
|
||||
# on NixOS where there is no global /usr/lib.
|
||||
nativeBuildInputs =
|
||||
nativeBuildInputs ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.autoPatchelfHook ];
|
||||
nativeBuildInputs ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.autoPatchelfHook ];
|
||||
|
||||
# The test suite exercises TUN devices, raw sockets and mDNS, none of
|
||||
# which exist in the build sandbox. The AUR/Debian packaging likewise
|
||||
@@ -84,7 +92,7 @@
|
||||
homepage = cargoToml.package.homepage;
|
||||
license = pkgs.lib.licenses.mit;
|
||||
mainProgram = "fips";
|
||||
platforms = pkgs.lib.platforms.linux ++ pkgs.lib.platforms.darwin;
|
||||
platforms = pkgs.lib.platforms.unix;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -124,5 +132,15 @@
|
||||
|
||||
formatter = pkgs.nixfmt;
|
||||
}
|
||||
);
|
||||
)
|
||||
// {
|
||||
# System-independent outputs — outside eachDefaultSystem.
|
||||
# Overlay so consumers get pkgs.fips automatically.
|
||||
overlays.default = final: prev: {
|
||||
fips = self.packages.${final.system}.default;
|
||||
};
|
||||
|
||||
# NixOS module — consumers import this and set services.fips.enable = true.
|
||||
nixosModules.default = import ./packaging/nixos;
|
||||
};
|
||||
}
|
||||
|
||||
+40
-2
@@ -48,6 +48,7 @@ packaging/
|
||||
debian/ Debian/Ubuntu .deb packaging via cargo-deb
|
||||
freebsd/ FreeBSD .pkg packaging via pkg-create(8)
|
||||
macos/ macOS .pkg installer via pkgbuild
|
||||
nixos/ NixOS flake module (services.fips.*)
|
||||
systemd/ Generic Linux systemd tarball packaging
|
||||
openwrt-ipk/ OpenWrt .ipk packaging via cargo-zigbuild (opkg)
|
||||
openwrt-apk/ OpenWrt .apk packaging via cargo-zigbuild + apk mkpkg
|
||||
@@ -244,8 +245,45 @@ nix develop # dev shell with the pinned toolchain + cargo-edit
|
||||
nix flake check # build + validate the flake
|
||||
```
|
||||
|
||||
Add to a NixOS configuration via the flake's `packages.<system>.fips`
|
||||
output, e.g. `environment.systemPackages = [ fips.packages.${system}.default ];`.
|
||||
The flake also exposes:
|
||||
|
||||
- `overlays.default` — adds `pkgs.fips` to nixpkgs
|
||||
- `nixosModules.default` — a NixOS module (`packaging/nixos/`) that provides
|
||||
`services.fips.enable` and runs the daemon as a systemd service
|
||||
|
||||
**As a package only** (no service management):
|
||||
|
||||
```nix
|
||||
environment.systemPackages = [ fips.packages.${system}.default ];
|
||||
```
|
||||
|
||||
**As a managed NixOS service** (recommended — starts on boot, journalctl logs):
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
{
|
||||
inputs.fips = {
|
||||
url = "github:jmcorgan/fips";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, fips, ... }@inputs: {
|
||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs = { inherit inputs; };
|
||||
modules = [
|
||||
./configuration.nix
|
||||
fips.nixosModules.default
|
||||
{ services.fips.enable = true; }
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See [`packaging/nixos/README.md`](nixos/README.md) for the full option
|
||||
reference (`services.fips.enable`, `.package`, `.configFile`,
|
||||
`.openFirewall`).
|
||||
|
||||
## Shared Assets
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# NixOS Packaging
|
||||
|
||||
NixOS module and flake outputs for FIPS.
|
||||
|
||||
## Quick Start (flake consumers)
|
||||
|
||||
Add fips as a flake input and enable the service:
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
fips = {
|
||||
url = "github:jmcorgan/fips";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, fips, ... }@inputs: {
|
||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||
specialArgs = { inherit inputs; };
|
||||
modules = [
|
||||
./configuration.nix
|
||||
fips.nixosModules.default # ← import the module
|
||||
{
|
||||
services.fips.enable = true; # ← enable the daemon
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Apply with:
|
||||
|
||||
```sh
|
||||
sudo nixos-rebuild switch --flake .#myhost
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `services.fips.enable` | bool | `false` | Enable the FIPS mesh network daemon |
|
||||
| `services.fips.package` | package | `pkgs.fips` | The fips package (provided via overlay) |
|
||||
| `services.fips.configFile` | path | shipped default | Default `fips.yaml` used to seed `/var/lib/fips/fips.yaml` on first run |
|
||||
| `services.fips.openFirewall` | bool | `true` | Open UDP 2121 + TCP 8443 |
|
||||
| `services.fips.dns.enable` | bool | `true` | Route `.fips` queries to the fips DNS responder via systemd-resolved |
|
||||
| `services.fips.gateway.enable` | bool | `false` | Enable the outbound LAN gateway (`fips-gateway.service`) |
|
||||
|
||||
## What it does
|
||||
|
||||
- Installs the `fips`, `fipsctl`, `fipstop`, and `fips-gateway` binaries
|
||||
- Creates a `fips` group (add your user for non-sudo `fipsctl`/`fipstop`)
|
||||
- Seeds `/var/lib/fips/fips.yaml` and `/etc/fips/hosts` from the shipped
|
||||
defaults on first run only — operator edits are never clobbered
|
||||
- Runs `fips` as a systemd service (`fips.service`) as `root:fips`
|
||||
(root is required for TUN interfaces and raw sockets)
|
||||
- Routes `.fips` DNS queries to the fips responder on `[::1]:5354` via
|
||||
systemd-resolved (enabled by default; disable with `services.fips.dns.enable = false`)
|
||||
- Optionally runs the outbound LAN gateway (`fips-gateway.service`)
|
||||
- Opens firewall ports for UDP (2121) and TCP (8443) transports
|
||||
|
||||
## Config file layout (hybrid pattern)
|
||||
|
||||
FIPS uses a hybrid config pattern that balances declarative defaults with
|
||||
operator-editable runtime state:
|
||||
|
||||
| Path | Purpose | Writable | Seeded from |
|
||||
|---|---|---|---|
|
||||
| `/var/lib/fips/fips.yaml` | Main config | yes | `services.fips.configFile` (first run only) |
|
||||
| `/var/lib/fips/fips.key` | Node identity (private) | yes | generated by fips on first start |
|
||||
| `/var/lib/fips/fips.pub` | Node identity (public) | yes | generated by fips on first start |
|
||||
| `/etc/fips/hosts` | Static hostname → npub map | yes | shipped `hosts` (first run only) |
|
||||
| `/etc/fips/peers.allow` | Peer allowlist (ACL) | yes | operator-created |
|
||||
| `/etc/fips/peers.deny` | Peer denylist (ACL) | yes | operator-created |
|
||||
| `/etc/fips/fips.d/` | nftables drop-in rules | yes | operator-created |
|
||||
|
||||
### Why two locations?
|
||||
|
||||
- **`/var/lib/fips/`** holds the main config and identity keys. This survives
|
||||
system rebuilds and reboots. fips derives key paths from the config file's
|
||||
parent directory, so `fips.key`/`fips.pub` land here automatically.
|
||||
- **`/etc/fips/`** holds the hosts file and ACL files. fips **hardcodes** these
|
||||
paths on Linux (`DEFAULT_HOSTS_PATH`, `DEFAULT_PEERS_ALLOW_PATH`,
|
||||
`DEFAULT_PEERS_DENY_PATH`), so they cannot be relocated.
|
||||
|
||||
### Why not `environment.etc`?
|
||||
|
||||
NixOS `environment.etc` creates symlinks into the read-only Nix store. That
|
||||
makes files immutable at runtime and would clobber operator edits on every
|
||||
rebuild. The module instead seeds real writable files via a `preStart`
|
||||
script that only runs when the target file is absent.
|
||||
|
||||
### Why `--config /var/lib/fips/fips.yaml`?
|
||||
|
||||
fips has a config search path (`./fips.yaml`, `~/.config/fips/fips.yaml`,
|
||||
`/etc/fips/fips.yaml`). Passing `--config` explicitly bypasses that search
|
||||
path entirely, so fips loads **only** the user-managed file and never
|
||||
accidentally picks up a stale `/etc/fips/fips.yaml`.
|
||||
|
||||
## Usage after install
|
||||
|
||||
```sh
|
||||
sudo journalctl -u fips -f # follow logs
|
||||
fipsctl show status # check status (needs fips group membership)
|
||||
fipstop # live monitoring
|
||||
```
|
||||
|
||||
To use `fipsctl`/`fipstop` without sudo, add your user to the `fips` group:
|
||||
|
||||
```nix
|
||||
users.users.myuser.extraGroups = [ "fips" ];
|
||||
```
|
||||
|
||||
Log out and back in for the group change to take effect.
|
||||
|
||||
### Editing config at runtime
|
||||
|
||||
```sh
|
||||
sudo nano /var/lib/fips/fips.yaml # edit main config
|
||||
sudo nano /etc/fips/hosts # add static hostname mappings
|
||||
sudo nano /etc/fips/peers.allow # add peers to the allowlist
|
||||
sudo systemctl restart fips # apply changes
|
||||
```
|
||||
|
||||
Files are group-writable (`0664`, owned by `root:fips`), so `fips` group
|
||||
members can edit without sudo.
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
packaging/nixos/
|
||||
├── default.nix # NixOS module (services.fips.*)
|
||||
└── README.md # this file
|
||||
```
|
||||
|
||||
The flake also exposes:
|
||||
|
||||
- `overlays.default` — adds `pkgs.fips`
|
||||
- `nixosModules.default` — the NixOS module
|
||||
@@ -0,0 +1,218 @@
|
||||
# NixOS module for the FIPS mesh network daemon.
|
||||
#
|
||||
# Exposed as nixosModules.default in flake.nix.
|
||||
# Consumers enable it with:
|
||||
#
|
||||
# { inputs, ... }: {
|
||||
# imports = [ inputs.fips.nixosModules.default ];
|
||||
# services.fips.enable = true;
|
||||
# }
|
||||
#
|
||||
# The package is provided via the flake's overlay (overlays.default), so it
|
||||
# lands in pkgs.fips without the consumer needing to know the input name.
|
||||
#
|
||||
# --- Hybrid config pattern ---
|
||||
#
|
||||
# fips.yaml and identity keys live in /var/lib/fips/ (writable, survives
|
||||
# reboots and rebuilds). The shipped default is seeded there on first run
|
||||
# only; operator edits are never clobbered.
|
||||
#
|
||||
# The hosts file and ACL files (peers.allow, peers.deny) MUST stay at
|
||||
# /etc/fips/ because fips hardcodes those paths on Linux
|
||||
# (DEFAULT_HOSTS_PATH, DEFAULT_PEERS_ALLOW_PATH, DEFAULT_PEERS_DENY_PATH).
|
||||
# They are seeded as real writable files (not Nix store symlinks) so the
|
||||
# operator can edit them directly.
|
||||
#
|
||||
# fips is launched with --config /var/lib/fips/fips.yaml, which bypasses
|
||||
# the config search path entirely — fips will never accidentally load
|
||||
# /etc/fips/fips.yaml instead of the user-managed version.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.fips;
|
||||
stateDir = "/var/lib/fips";
|
||||
in
|
||||
{
|
||||
options.services.fips = {
|
||||
enable = lib.mkEnableOption "FIPS mesh network daemon";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.fips;
|
||||
defaultText = lib.literalExpression "pkgs.fips";
|
||||
description = ''
|
||||
The fips package to use. Defaults to the one provided by the
|
||||
fips flake overlay (overlays.default).
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${cfg.package}/share/fips/fips.yaml";
|
||||
defaultText = lib.literalExpression "''${cfg.package}/share/fips/fips.yaml";
|
||||
description = ''
|
||||
Default fips.yaml used to seed the writable config at
|
||||
/var/lib/fips/fips.yaml on first run. The operator can then edit
|
||||
/var/lib/fips/fips.yaml directly; this file is never overwritten
|
||||
after the initial seed.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Open firewall ports for fips transports
|
||||
(UDP 2121, TCP 8443).
|
||||
'';
|
||||
};
|
||||
|
||||
dns = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Route .fips queries to the fips DNS responder on [::1]:5354
|
||||
via systemd-resolved. Without this, .fips hostnames don't
|
||||
resolve on the host.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
gateway = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable the outbound LAN gateway. Lets non-fips hosts on the
|
||||
LAN reach mesh destinations via DNS-allocated virtual IPs
|
||||
and kernel NAT. Requires the fips daemon running with DNS.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# 'fips' group so non-root users can run fipsctl/fipstop without sudo
|
||||
# and edit config/hosts files group-writable.
|
||||
users.groups.fips = { };
|
||||
|
||||
# Writable directories for hosts file, ACL files, and nftables drop-ins.
|
||||
# fips hardcodes /etc/fips/hosts, /etc/fips/peers.allow,
|
||||
# /etc/fips/peers.deny on Linux, so these MUST live here. Created as
|
||||
# real directories (not environment.etc symlinks) so the operator can
|
||||
# edit files in place.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /etc/fips 0775 root fips -"
|
||||
"d /etc/fips/fips.d 0775 root fips -"
|
||||
];
|
||||
|
||||
systemd.services.fips = {
|
||||
description = "FIPS Mesh Network Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"network-online.target"
|
||||
"systemd-tmpfiles-setup.service"
|
||||
];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
# Seed config and hosts on first run only. Existing files are never
|
||||
# overwritten, so operator edits survive service restarts and system
|
||||
# rebuilds.
|
||||
preStart = ''
|
||||
# Seed writable config from the shipped default (only if absent).
|
||||
# fips derives key paths (fips.key/fips.pub) from the config file's
|
||||
# parent directory, so keys also land in /var/lib/fips/.
|
||||
if [ ! -f ${stateDir}/fips.yaml ]; then
|
||||
cp "${cfg.configFile}" ${stateDir}/fips.yaml
|
||||
chown root:fips ${stateDir}/fips.yaml
|
||||
chmod 0664 ${stateDir}/fips.yaml
|
||||
fi
|
||||
|
||||
# Seed hosts file (fips hardcodes /etc/fips/hosts on Linux).
|
||||
if [ ! -f /etc/fips/hosts ]; then
|
||||
cp "${cfg.package}/share/fips/hosts" /etc/fips/hosts
|
||||
chown root:fips /etc/fips/hosts
|
||||
chmod 0664 /etc/fips/hosts
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
# Run as root:fips so the daemon has root for TUN/raw sockets while
|
||||
# group members can access the control socket and state files.
|
||||
Group = "fips";
|
||||
# --config bypasses the search path: fips loads ONLY this file,
|
||||
# never /etc/fips/fips.yaml.
|
||||
ExecStart = "${cfg.package}/bin/fips --config ${stateDir}/fips.yaml";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
|
||||
# Writable state directory (/var/lib/fips/) for config + keys.
|
||||
# root:fips 0775 so group members can edit the config.
|
||||
StateDirectory = "fips";
|
||||
StateDirectoryMode = "0775";
|
||||
|
||||
# Control socket directory (/run/fips/) — group-accessible.
|
||||
RuntimeDirectory = "fips";
|
||||
RuntimeDirectoryMode = "0770";
|
||||
|
||||
# Log directory (/var/log/fips/) for the built-in profiler.
|
||||
LogsDirectory = "fips";
|
||||
|
||||
# Security hardening (daemon runs as root for TUN and raw sockets).
|
||||
# Mirrors packaging/systemd/fips.service.
|
||||
ProtectHome = "yes";
|
||||
PrivateTmp = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = false;
|
||||
};
|
||||
};
|
||||
|
||||
services.resolved = lib.mkIf cfg.dns.enable {
|
||||
enable = true;
|
||||
settings.Resolve = {
|
||||
DNS = [ "[::1]:5354" ];
|
||||
Domains = [ "~fips" ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.fips-gateway = lib.mkIf cfg.gateway.enable {
|
||||
description = "FIPS Outbound LAN Gateway";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "fips.service" ];
|
||||
requires = [ "fips.service" ];
|
||||
|
||||
preStart = ''
|
||||
for i in $(seq 1 30); do
|
||||
${pkgs.iproute2}/bin/ip link show fips0 >/dev/null 2>&1 && exit 0
|
||||
sleep 1
|
||||
done
|
||||
echo "fips0 did not appear within 30s" >&2
|
||||
exit 1
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${cfg.package}/bin/fips-gateway --config ${stateDir}/fips.yaml";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
TimeoutStopSec = 15;
|
||||
ProtectHome = "yes";
|
||||
PrivateTmp = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = false;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 8443 ];
|
||||
allowedUDPPorts = [ 2121 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user