Follow the macOS install layout for config, ACL, and identity paths

The macOS packaging installs config under /usr/local/etc/fips, wired through
the launchd plist and build-pkg.sh, but the default-path constants and the
config search path were hardcoded to /etc/fips for all Unix. On macOS the
daemon and fipsctl therefore looked in a directory that does not exist: the
ACL and host-map loaders hit their NotFound no-op arm and returned empty
state, so a populated peers.deny reported effective_mode "default_open" with
enforcement inactive, and host-file aliases went unloaded, with no error.

The peers.allow, peers.deny and hosts defaults now follow the platform's
packaging, and fipsctl keygen writes its identity there too. The config
search path keeps probing /etc/fips first and adds /usr/local/etc/fips after
it, so an existing install keeps working across the upgrade and the packaged
file still wins over a stale leftover. Both the macOS search-path entry and
the keygen output directory read one SYSTEM_CONFIG_DIR constant, so they
cannot drift apart. At startup the daemon warns once about hosts, peers.allow
or peers.deny stranded at the old location; the config file is deliberately
excluded, since both directories stay on the search path and a config left
behind is still read.

The control-socket snapshot tests repoint the ACL reloader at non-existent
paths under the temp dir, so the snapshot no longer reflects whatever ACL
files happen to exist on the machine running the tests.

Platform-gated unit tests pin both layouts, so a future refactor cannot
silently drift either one. Linux and Windows behavior is unchanged.

Adding a second system config directory moves the directory the daemon
derives the identity key path from, since that comes from whichever config
file loaded last. A host carrying fips.yaml at both locations would have
resolved fips.key to the new directory, found none, and under persistent
generated a fresh identity, silently changing its npub, routing address and
mesh IPv6 with no migration path. The daemon now adopts a key stranded at the
legacy path and warns to move it rather than generating one. The fallback is
confined to keys resolved from the system config directory, so a run using
./fips.yaml or a user config is never redirected to a system key.

Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
This commit is contained in:
redshift
2026-08-09 13:40:56 +00:00
committed by Johnathan Corgan
co-authored by Johnathan Corgan
parent 0155345984
commit cdb03c76b0
12 changed files with 412 additions and 12 deletions
+49
View File
@@ -37,6 +37,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- macOS: `peers.allow`, `peers.deny`, and the `hosts` file are now read
from `/usr/local/etc/fips/`, matching the install layout the macOS
packaging ships (`packaging/macos/`). The default-path constants were
hardcoded to `/etc/fips/...` with only a `#[cfg(unix)]` / `#[cfg(windows)]`
split, so on macOS the daemon looked in a directory that does not exist:
`load_file` / `load_hosts_file` hit their `NotFound` no-op arm and silently
returned an empty ACL / empty host map. A populated `peers.deny` therefore
reported `effective_mode: "default_open"` and `enforcement_active: false`
via `fipsctl acl show`, and host-file aliases went unloaded, with no error
or warning. The default constants now follow the platform's packaging —
`/usr/local/etc/fips/` on macOS, `/etc/fips/` on Linux and other Unix
for the ACL files, and `/etc/fips/` on Linux and `%ProgramData%\fips\`
on Windows for the hosts file — and are pinned by platform-gated unit
tests so the layout cannot silently drift again. At startup the daemon
warns once if any of these files exist at the old `/etc/fips/` location
but not at the current default. Linux and Windows behavior is unchanged.
**macOS users with existing files in `/etc/fips/` should move them to
`/usr/local/etc/fips/`.**
- macOS: `fipsctl keygen` now writes `fips.key` / `fips.pub` to
`/usr/local/etc/fips/` by default, matching the install layout the macOS
packaging ships. The default output directory was hardcoded to
`/etc/fips` for all Unix, but the daemon derives its identity key paths
from the config file's directory — `/usr/local/etc/fips/fips.yaml` on
macOS — so a generated identity landed where the daemon never reads it
and the node silently kept an ephemeral identity. Linux and other Unix
keep `/etc/fips`, Windows is unchanged, and the values are pinned by
platform-gated unit tests.
- macOS: the system-wide config search path now includes
`/usr/local/etc/fips/fips.yaml` in addition to `/etc/fips/fips.yaml`,
matching the install layout the macOS packaging ships. Previously only
`/etc/fips/fips.yaml` was probed, so a bare `fips` run without `--config`
skipped the installed config and derived identity key paths from a
non-existent directory. `/etc/fips/fips.yaml` is still probed first so
existing installs keep working. Both the macOS entry in the search path
and the directory `fipsctl keygen` writes to read the shared
`SYSTEM_CONFIG_DIR` constant, so the two cannot drift apart. The
launchd-installed daemon was unaffected (it always passes `--config`).
Linux and Windows behavior is unchanged. Because the daemon derives the
identity key directory from whichever config file loaded last, a macOS host
carrying `fips.yaml` at both locations would have resolved `fips.key` to the
new directory, found none, and under `persistent` generated a fresh
identity — silently changing its npub, routing address and mesh IPv6. The
daemon now adopts a key stranded at `/etc/fips/fips.key` and warns to move
it, instead of generating one. The fallback is confined to keys resolved
from the system config directory, so a run using `./fips.yaml` or a user
config is never redirected to a system key.
- Nostr NAT traversal no longer breaks after the host suspends. The traversal
clock cached a Unix timestamp once at startup and advanced it with a
monotonic `Instant`, which does not tick while a machine is asleep, so after
+43
View File
@@ -72,6 +72,49 @@ present after the first successful daemon start. If the daemon never
came up cleanly (config error, permission problem), the key files
will be missing.
### macOS note
The macOS package (`.pkg`) installs config and keys under
`/usr/local/etc/fips/` instead of `/etc/fips/`. The paths above become:
| Linux / other Unix | macOS |
| --- | --- |
| `/etc/fips/fips.yaml` | `/usr/local/etc/fips/fips.yaml` |
| `/etc/fips/fips.key` | `/usr/local/etc/fips/fips.key` |
| `/etc/fips/fips.pub` | `/usr/local/etc/fips/fips.pub` |
`fipsctl keygen` writes to `/usr/local/etc/fips/` by default on macOS.
The daemon still probes `/etc/fips/fips.yaml` as a fallback (so an
existing install is not broken by an upgrade), but the macOS packaging
only installs files under `/usr/local/etc/fips/`.
If you have files in `/etc/fips/` from a manual install, move them:
```sh
sudo mv /etc/fips/fips.yaml /usr/local/etc/fips/fips.yaml
sudo mv /etc/fips/fips.key /usr/local/etc/fips/fips.key
sudo mv /etc/fips/fips.pub /usr/local/etc/fips/fips.pub
sudo mv /etc/fips/peers.allow /usr/local/etc/fips/peers.allow 2>/dev/null || true
sudo mv /etc/fips/peers.deny /usr/local/etc/fips/peers.deny 2>/dev/null || true
sudo mv /etc/fips/hosts /usr/local/etc/fips/hosts 2>/dev/null || true
```
The daemon logs a warning at startup if any of `peers.allow`, `peers.deny`,
or `hosts` exist at the old `/etc/fips/` path but not at
`/usr/local/etc/fips/`. `fips.yaml` is deliberately not included: both
directories stay on the config search path, so a config file left at
`/etc/fips/` is still read and is not a stranded file.
`fips.key` is handled differently again, because losing it is worse than
not reading it. The daemon derives the key directory from whichever config
file loaded last, so a host carrying `fips.yaml` at both locations resolves
the key to `/usr/local/etc/fips/`. If no key is there and one exists at
`/etc/fips/fips.key`, the daemon **uses the old key and warns**, rather than
generating a new identity: a fresh keypair would change the node's npub,
routing address and mesh IPv6 with no migration path. Move the key when you
see that warning; the fallback exists to make the upgrade survivable, not
to be relied on.
### File layout and permissions
| Path | Mode | Owner | Contents |
+1 -1
View File
@@ -66,7 +66,7 @@ highest-priority value wins.
| Priority | Path | Purpose |
| -------- | ---- | ------- |
| 1 | `/etc/fips/fips.yaml` | System-wide defaults |
| 1 | `/usr/local/etc/fips/fips.yaml` (macOS), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
| 2 | `~/.config/fips/fips.yaml` | User preferences |
| 3 | `~/.fips.yaml` | Legacy user config |
| 4 | `./fips.yaml` | Deployment-specific overrides |
+1 -1
View File
@@ -89,7 +89,7 @@ daemon.
| Flag | Argument | Default | Description |
| ---- | -------- | ------- | ----------- |
| `-d`, `--dir` | `DIR` | `/etc/fips` (Unix), `%APPDATA%\fips` (Windows) | Output directory for `fips.key` and `fips.pub`. |
| `-d`, `--dir` | `DIR` | `/usr/local/etc/fips` (macOS), `/etc/fips` (other Unix), `%APPDATA%\fips` (Windows) | Output directory for `fips.key` and `fips.pub`. Matches the directory the platform's packaging installs config into, which is where the daemon derives the key paths from. |
| `-f`, `--force` | — | off | Overwrite an existing `fips.key`. |
| `-s`, `--stdout` | — | off | Print `nsec` then `npub` to stdout instead of writing files. |
+1 -1
View File
@@ -13,7 +13,7 @@ locations, lowest to highest priority:
| Priority | Path | Purpose |
|----------|------|---------|
| 1 (lowest) | `/etc/fips/fips.yaml` | System-wide defaults |
| 1 (lowest) | `/usr/local/etc/fips/fips.yaml` (macOS), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
| 2 | `~/.config/fips/fips.yaml` | User preferences |
| 3 | `~/.fips.yaml` | Legacy user config |
| 4 (highest) | `./fips.yaml` | Deployment-specific overrides |
+5
View File
@@ -111,6 +111,11 @@ async fn run_daemon(
}
}
// The hosts/ACL defaults on macOS moved from /etc/fips to
// /usr/local/etc/fips; flag files stranded at the old location.
#[cfg(target_os = "macos")]
fips::node::warn_on_legacy_config_paths();
// Identity provisioning: config nsec > key file > generate ephemeral
let resolved = match resolve_identity(&config, &loaded_paths) {
Ok(r) => r,
+18 -1
View File
@@ -293,10 +293,13 @@ fn print_response(value: &serde_json::Value) {
}
/// Default directory for keygen output.
///
/// Must match the platform's config dir, since the daemon derives key
/// paths from the config file's location.
fn default_key_dir() -> PathBuf {
#[cfg(unix)]
{
PathBuf::from("/etc/fips")
PathBuf::from(fips::config::SYSTEM_CONFIG_DIR)
}
#[cfg(windows)]
{
@@ -607,6 +610,20 @@ fn sparkline(values: &[f64], min: f64, max: f64) -> String {
mod tests {
use super::*;
// macOS packaging ships config under /usr/local/etc/fips/.
#[cfg(target_os = "macos")]
#[test]
fn test_default_key_dir_follows_macos_packaging_layout() {
assert_eq!(default_key_dir(), PathBuf::from("/usr/local/etc/fips"));
}
// Non-macOS Unix keeps the historic /etc/fips/ location.
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn test_default_key_dir_keeps_etc_fips_layout() {
assert_eq!(default_key_dir(), PathBuf::from("/etc/fips"));
}
#[test]
fn test_acl_show_command_name() {
assert_eq!(AclCommands::Show.command_name(), "show_acl");
+176 -6
View File
@@ -3,7 +3,8 @@
//! Loads configuration from YAML files with a cascading priority system:
//! 1. `./fips.yaml` (current directory - highest priority)
//! 2. `~/.config/fips/fips.yaml` (user config directory)
//! 3. `/etc/fips/fips.yaml` (system - lowest priority)
//! 3. `/etc/fips/fips.yaml` (system - lowest priority; on macOS
//! `/usr/local/etc/fips/fips.yaml` is also probed, after `/etc/fips/`)
//!
//! Values from higher priority files override those from lower priority files.
//!
@@ -47,6 +48,15 @@ pub use transport::{
/// Default config filename.
const CONFIG_FILENAME: &str = "fips.yaml";
/// System-wide config directory, following the platform's packaging layout
/// (`/usr/local/etc/fips` on macOS, `/etc/fips` otherwise). The daemon
/// derives identity key paths from the config file's location, so anything
/// that reads or writes config-adjacent files should use this one constant.
#[cfg(target_os = "macos")]
pub const SYSTEM_CONFIG_DIR: &str = "/usr/local/etc/fips";
#[cfg(not(target_os = "macos"))]
pub const SYSTEM_CONFIG_DIR: &str = "/etc/fips";
/// Default key filename, placed alongside the config file.
const KEY_FILENAME: &str = "fips.key";
@@ -83,6 +93,42 @@ pub fn key_file_path(config_path: &Path) -> PathBuf {
.join(KEY_FILENAME)
}
/// Legacy system config directory, from before the platform-packaging move.
///
/// Equal to [`SYSTEM_CONFIG_DIR`] everywhere except where packaging installs
/// outside `/etc`, which makes [`legacy_key_fallback`] inert on those
/// platforms without needing a `cfg` of its own.
const LEGACY_SYSTEM_CONFIG_DIR: &str = "/etc/fips";
/// Find an identity key stranded at the legacy system config directory.
///
/// Adding a second system config directory to the search path moves the
/// directory `resolve_identity` derives the key path from, because that
/// directory comes from whichever config file loaded last. A node that
/// carries a config at both locations would otherwise find no key at the new
/// one and, under `persistent`, generate a fresh identity — silently changing
/// its npub, routing address and mesh IPv6, none of which has a migration
/// path.
///
/// Returns the legacy key only when all of these hold, which confines the
/// fallback to exactly that regression:
///
/// - the two directories actually differ, so this is inert on Linux
/// - no key exists at `key_path`
/// - `key_path` sits in `system_dir`, so an operator using `./fips.yaml` or a
/// user config is never redirected to a system key
/// - a key does exist at `legacy_dir`
fn legacy_key_fallback(key_path: &Path, system_dir: &Path, legacy_dir: &Path) -> Option<PathBuf> {
if system_dir == legacy_dir || key_path.exists() {
return None;
}
if key_path.parent() != Some(system_dir) {
return None;
}
let legacy = legacy_dir.join(KEY_FILENAME);
legacy.exists().then_some(legacy)
}
/// Derive the public key file path from a config file path.
pub fn pub_file_path(config_path: &Path) -> PathBuf {
config_path
@@ -307,7 +353,31 @@ pub fn resolve_identity(
});
}
// No key file yet — generate and persist
// No key at the resolved location. Before generating a new identity,
// check whether one is stranded at the legacy system config directory:
// generating here would silently change the node's npub, routing
// address and mesh IPv6.
if let Some(legacy) = legacy_key_fallback(
&key_path,
Path::new(SYSTEM_CONFIG_DIR),
Path::new(LEGACY_SYSTEM_CONFIG_DIR),
) {
let nsec = read_key_file(&legacy)?;
let identity = Identity::from_secret_str(&nsec)?;
tracing::warn!(
legacy = %legacy.display(),
current = %key_path.display(),
"Identity key found at the legacy path but not at the current default; \
using it so the node keeps its identity move it to the current path"
);
let _ = write_pub_file(&pub_path, &identity.npub());
return Ok(ResolvedIdentity {
nsec,
source: IdentitySource::KeyFile(legacy),
});
}
// No key file anywhere — generate and persist
let identity = Identity::generate();
let nsec = encode_nsec(&identity.keypair().secret_key());
let npub = identity.npub();
@@ -454,7 +524,8 @@ impl Config {
/// Load configuration from the standard search paths.
///
/// Files are loaded in reverse priority order and merged:
/// 1. `/etc/fips/fips.yaml` (loaded first, lowest priority)
/// 1. `/etc/fips/fips.yaml` (and `/usr/local/etc/fips/fips.yaml` on macOS;
/// loaded first, lowest priority)
/// 2. `~/.config/fips/fips.yaml` (user config)
/// 3. `./fips.yaml` (loaded last, highest priority)
///
@@ -500,9 +571,18 @@ impl Config {
pub fn search_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
// System config (lowest priority)
// System config — /etc/fips is always probed so existing installs
// keep working after an upgrade.
paths.push(PathBuf::from("/etc/fips").join(CONFIG_FILENAME));
// macOS packaging installs config under /usr/local/etc/fips
// (Homebrew-style prefix); probe it after /etc/fips so the
// packaged file wins over a stale /etc/fips leftover. Read from
// SYSTEM_CONFIG_DIR rather than a second literal, so this path and
// the directory `fipsctl keygen` writes into cannot drift apart.
#[cfg(target_os = "macos")]
paths.push(PathBuf::from(SYSTEM_CONFIG_DIR).join(CONFIG_FILENAME));
// User config directory
if let Some(config_dir) = dirs::config_dir() {
paths.push(config_dir.join("fips").join(CONFIG_FILENAME));
@@ -930,13 +1010,103 @@ node:
// Should include current directory
assert!(paths.iter().any(|p| p.ends_with("fips.yaml")));
// Should include /etc/fips on Unix
#[cfg(unix)]
// Should always include /etc/fips as a system config path
assert!(
paths
.iter()
.any(|p| p.starts_with("/etc/fips") && p.ends_with("fips.yaml"))
);
// macOS should also include /usr/local/etc/fips
#[cfg(target_os = "macos")]
assert!(
paths
.iter()
.any(|p| p.starts_with("/usr/local/etc/fips") && p.ends_with("fips.yaml"))
);
}
// --- legacy identity-key fallback ---
//
// Guards the regression the second system config directory introduces:
// the key directory follows whichever config file loaded last, so a node
// carrying config at both locations would find no key at the new one and
// generate a fresh identity. These drive `legacy_key_fallback` with real
// directories under a temp root rather than asserting on the constants,
// and they run on every platform because the function takes both
// directories as arguments.
fn write_stub_key(dir: &Path) -> PathBuf {
std::fs::create_dir_all(dir).unwrap();
let p = dir.join(KEY_FILENAME);
std::fs::write(&p, "nsec1stub\n").unwrap();
p
}
#[test]
fn legacy_key_is_adopted_when_the_new_location_has_none() {
let root = TempDir::new().unwrap();
let legacy = root.path().join("etc/fips");
let system = root.path().join("usr/local/etc/fips");
std::fs::create_dir_all(&system).unwrap();
let legacy_key = write_stub_key(&legacy);
let key_path = system.join(KEY_FILENAME);
assert_eq!(
legacy_key_fallback(&key_path, &system, &legacy),
Some(legacy_key),
"a key stranded at the legacy path must be adopted, not regenerated"
);
}
#[test]
fn a_key_at_the_new_location_wins_over_the_legacy_one() {
let root = TempDir::new().unwrap();
let legacy = root.path().join("etc/fips");
let system = root.path().join("usr/local/etc/fips");
write_stub_key(&legacy);
let key_path = write_stub_key(&system);
assert_eq!(legacy_key_fallback(&key_path, &system, &legacy), None);
}
#[test]
fn no_fallback_when_the_two_directories_are_the_same() {
// The Linux case: nothing moved, so the fallback must be inert even
// though a key exists at that one directory.
let root = TempDir::new().unwrap();
let dir = root.path().join("etc/fips");
write_stub_key(&dir);
let absent = dir.join("nonexistent").join(KEY_FILENAME);
assert_eq!(legacy_key_fallback(&absent, &dir, &dir), None);
}
#[test]
fn no_fallback_for_a_config_outside_the_system_directory() {
// An operator running with ./fips.yaml or a user config must never be
// silently redirected to a system key.
let root = TempDir::new().unwrap();
let legacy = root.path().join("etc/fips");
let system = root.path().join("usr/local/etc/fips");
let elsewhere = root.path().join("home/someone");
std::fs::create_dir_all(&elsewhere).unwrap();
write_stub_key(&legacy);
let key_path = elsewhere.join(KEY_FILENAME);
assert_eq!(legacy_key_fallback(&key_path, &system, &legacy), None);
}
#[test]
fn no_fallback_when_the_legacy_location_is_empty_too() {
let root = TempDir::new().unwrap();
let legacy = root.path().join("etc/fips");
let system = root.path().join("usr/local/etc/fips");
std::fs::create_dir_all(&legacy).unwrap();
std::fs::create_dir_all(&system).unwrap();
let key_path = system.join(KEY_FILENAME);
assert_eq!(legacy_key_fallback(&key_path, &system, &legacy), None);
}
#[test]
+16 -1
View File
@@ -2435,11 +2435,26 @@ mod tests {
/// runtime state (no peers, links, sessions, transports, or cache
/// entries). This keeps every per-element list empty and every
/// scalar deterministic modulo `VOLATILE_KEYS`.
///
/// The peer ACL is isolated from the host filesystem: the reloader is
/// repointed at non-existent paths under the process temp dir, so the
/// snapshot is stable regardless of whether an operator has edited the
/// real `peers.allow` / `peers.deny` on the dev/CI machine. Without this,
/// `show_acl` would reflect the host's live ACL state (e.g. a populated
/// `peers.deny` flips `effective_mode` from `default_open` to
/// `denylist`), making the snapshot machine-dependent.
fn build_test_node() -> Node {
let identity =
Identity::from_secret_bytes(&TEST_SEED).expect("test seed is a valid secret key");
let config = Config::new();
Node::with_identity(identity, config).expect("default config is valid")
let mut node = Node::with_identity(identity, config).expect("default config is valid");
let nonce = std::process::id();
let tmp = std::env::temp_dir();
node.isolate_peer_acl_for_test(
tmp.join(format!("fips-snapshot-test-{nonce}.allow")),
tmp.join(format!("fips-snapshot-test-{nonce}.deny")),
);
node
}
/// Recursively walk a JSON value, replacing the value of any key
+72
View File
@@ -23,11 +23,52 @@ use std::time::SystemTime;
use tracing::{debug, info, warn};
/// Default path for the peer allow list.
///
/// On macOS the install layout (see `packaging/macos/`) ships config under
/// `/usr/local/etc/fips/` rather than `/etc/fips/`; the default follows the
/// platform's packaging so the daemon reads the file the operator was told
/// to edit. Linux and other Unix keep the historic `/etc/fips/` location.
#[cfg(target_os = "macos")]
pub const DEFAULT_PEERS_ALLOW_PATH: &str = "/usr/local/etc/fips/peers.allow";
#[cfg(not(target_os = "macos"))]
pub const DEFAULT_PEERS_ALLOW_PATH: &str = "/etc/fips/peers.allow";
/// Default path for the peer deny list.
///
/// See [`DEFAULT_PEERS_ALLOW_PATH`] for the macOS `/usr/local/etc/fips/`
/// rationale.
#[cfg(target_os = "macos")]
pub const DEFAULT_PEERS_DENY_PATH: &str = "/usr/local/etc/fips/peers.deny";
#[cfg(not(target_os = "macos"))]
pub const DEFAULT_PEERS_DENY_PATH: &str = "/etc/fips/peers.deny";
/// Warn about config files stranded at the pre-move default location.
///
/// The macOS defaults for `hosts`, `peers.allow` and `peers.deny` moved
/// from `/etc/fips` to `/usr/local/etc/fips`, the directory the macOS
/// packaging actually populates. The old location is no longer read by
/// the default path constants, and a `peers.deny` silently left behind
/// there would fail open (a missing deny list is not an error), so surface
/// the situation loudly once at startup.
#[cfg(target_os = "macos")]
pub fn warn_on_legacy_config_paths() {
for (current, name) in [
(crate::upper::hosts::DEFAULT_HOSTS_PATH, "hosts"),
(DEFAULT_PEERS_ALLOW_PATH, "peers.allow"),
(DEFAULT_PEERS_DENY_PATH, "peers.deny"),
] {
let legacy = format!("/etc/fips/{name}");
if std::path::Path::new(&legacy).exists() && !std::path::Path::new(current).exists() {
warn!(
legacy = %legacy,
current = %current,
"Config file found at legacy path but not at the current default; \
it is no longer read move it to the current path"
);
}
}
}
/// Result of evaluating a peer against the ACL.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerAclDecision {
@@ -451,6 +492,16 @@ impl Node {
decision
)))
}
/// Test-only: replace the peer-ACL reloader with one that reads from
/// the given paths, isolating the node from the host's real
/// `peers.allow` / `peers.deny` files. Used by snapshot tests
/// that must be deterministic regardless of whether an operator has
/// edited the system ACL files on the dev/CI machine.
#[cfg(test)]
pub(crate) fn isolate_peer_acl_for_test(&mut self, allow: PathBuf, deny: PathBuf) {
self.peer_acl = PeerAclReloader::with_paths(allow, deny);
}
}
#[cfg(test)]
@@ -487,6 +538,27 @@ mod tests {
acl
}
// Guard against the macOS path regression: the install layout
// (`packaging/macos/`) ships config under `/usr/local/etc/fips/`, so the
// default ACL paths must follow it, or `peers.allow`/`peers.deny` are
// silently unread on macOS (see the `NotFound` no-op in `load_file`).
#[cfg(target_os = "macos")]
#[test]
fn test_default_acl_paths_follow_macos_packaging_layout() {
assert_eq!(DEFAULT_PEERS_ALLOW_PATH, "/usr/local/etc/fips/peers.allow");
assert_eq!(DEFAULT_PEERS_DENY_PATH, "/usr/local/etc/fips/peers.deny");
}
// Non-macOS Unix/Linux keeps the historic `/etc/fips/` location; this
// runs on the Linux CI matrix and pins the value so a future refactor
// can't silently drift it.
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn test_default_acl_paths_keep_etc_fips_layout() {
assert_eq!(DEFAULT_PEERS_ALLOW_PATH, "/etc/fips/peers.allow");
assert_eq!(DEFAULT_PEERS_DENY_PATH, "/etc/fips/peers.deny");
}
#[test]
fn test_acl_decision_allowed_and_display() {
assert!(PeerAclDecision::AllowList.allowed());
+2
View File
@@ -5,6 +5,8 @@
//! Bloom filters, coordinate caches, transports, links, and peers.
pub(crate) mod acl;
#[cfg(target_os = "macos")]
pub use acl::warn_on_legacy_config_paths;
mod bloom;
pub(crate) mod context;
#[cfg(unix)]
+28 -1
View File
@@ -17,7 +17,15 @@ use std::time::SystemTime;
use tracing::{debug, info, warn};
/// Default path for the FIPS hosts file.
#[cfg(unix)]
///
/// On macOS the install layout (see `packaging/macos/`) ships config under
/// `/usr/local/etc/fips/`, not `/etc/fips/`; the default follows the
/// platform's packaging so aliases edited by the operator are actually
/// loaded. Linux and other Unix keep the historic `/etc/fips/` location;
/// Windows uses `%ProgramData%`.
#[cfg(target_os = "macos")]
pub const DEFAULT_HOSTS_PATH: &str = "/usr/local/etc/fips/hosts";
#[cfg(all(unix, not(target_os = "macos")))]
pub const DEFAULT_HOSTS_PATH: &str = "/etc/fips/hosts";
#[cfg(windows)]
pub const DEFAULT_HOSTS_PATH: &str = r"C:\ProgramData\fips\hosts";
@@ -311,6 +319,25 @@ mod tests {
use super::*;
use crate::Identity;
// --- default path tests ---
// Guard against the macOS path regression: the install layout
// (`packaging/macos/`) ships config under `/usr/local/etc/fips/`, so the
// default hosts path must follow it, or host-file aliases are silently
// unloaded on macOS (see the `NotFound` no-op in `load_hosts_file`).
#[cfg(target_os = "macos")]
#[test]
fn test_default_hosts_path_follows_macos_packaging_layout() {
assert_eq!(DEFAULT_HOSTS_PATH, "/usr/local/etc/fips/hosts");
}
// Non-macOS Unix/Linux keeps the historic `/etc/fips/` location.
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn test_default_hosts_path_keeps_etc_fips_layout() {
assert_eq!(DEFAULT_HOSTS_PATH, "/etc/fips/hosts");
}
// --- validate_hostname tests ---
#[test]