206bb1fe1f
Replace the NixOS tmpfiles-based approach for pre-creating plugin subdirectories with runtime directory creation in the data-mesher process itself. This ensures plugin directories exist under every configured network's state directory, not just the home network. - Rename NixOS option fileDirectories to pluginDirectories - Add plugin_directories as a top-level dm.toml config key - Create plugin subdirectories in NewFiles() on startup - Remove tmpfiles rules and related assertions from the NixOS module - Add test covering multi-network plugin directory creation
174 lines
5.7 KiB
Nix
174 lines
5.7 KiB
Nix
{
|
|
lib,
|
|
pkgs,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
|
|
cfg = config.services.data-mesher;
|
|
settingsFormat = pkgs.formats.toml { };
|
|
|
|
in
|
|
{
|
|
|
|
options.services.data-mesher = {
|
|
enable = lib.mkEnableOption "Data Mesher, data syncing daemon";
|
|
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "The Data Mesher package to use.";
|
|
default = pkgs.callPackage ../../packages/data-mesher/package.nix { };
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "data-mesher";
|
|
description = ''
|
|
User account under which data-mesher runs.
|
|
'';
|
|
};
|
|
|
|
group = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "data-mesher";
|
|
description = ''
|
|
User group under which data-mesher runs.
|
|
'';
|
|
};
|
|
|
|
openFirewall = lib.mkEnableOption "Open ports in firewall";
|
|
|
|
pluginDirectories = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
example = [
|
|
"dns"
|
|
"nss"
|
|
];
|
|
description = ''
|
|
List of subdirectories to create under each network's files directory on startup.
|
|
These are intended for plugins that need to BindReadOnlyPath into the file tree.
|
|
Plugin services should use After=data-mesher.service to ensure directories exist.
|
|
'';
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
default = { };
|
|
description = "Data Mesher settings, which correspond to the contents of the dm.toml file";
|
|
type = lib.types.submodule {
|
|
freeformType = settingsFormat.type;
|
|
imports = [
|
|
./settings.nix
|
|
];
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable (
|
|
let
|
|
configFile =
|
|
settingsFormat.generate "dm.toml"
|
|
# filter any null values as the toml generator doesn't like it
|
|
(lib.filterAttrsRecursive (_n: v: v != null) cfg.settings);
|
|
in
|
|
{
|
|
environment.systemPackages = [
|
|
cfg.package
|
|
];
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedTCPPorts = [
|
|
cfg.settings.cluster.port
|
|
cfg.settings.http.port
|
|
];
|
|
};
|
|
|
|
services.data-mesher.settings.plugin_directories = lib.mkDefault cfg.pluginDirectories;
|
|
|
|
environment.etc."${cfg.user}/dm.toml".source = configFile;
|
|
|
|
users.users.${cfg.user} = {
|
|
isSystemUser = true;
|
|
home = "/var/lib/${cfg.user}";
|
|
inherit (cfg) group;
|
|
};
|
|
|
|
users.groups.${cfg.group} = { };
|
|
|
|
systemd.services.data-mesher = {
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
description = "data mesher daemon";
|
|
|
|
# restart if the config changes
|
|
restartTriggers = [ configFile ];
|
|
|
|
serviceConfig = {
|
|
Type = "notify";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
|
|
StateDirectory = cfg.user;
|
|
ConfigurationDirectory = cfg.user;
|
|
|
|
Restart = "always";
|
|
RestartSec = "10";
|
|
|
|
ExecStart = "${cfg.package}/bin/data-mesher server";
|
|
|
|
# Basic protection (similar to DynamicUser)
|
|
RemoveIPC = true; # Remove SysV IPC objects when service stops
|
|
PrivateTmp = true; # Private /tmp and /var/tmp
|
|
NoNewPrivileges = true; # Prevent privilege escalation via setuid/setgid
|
|
ProtectSystem = "strict"; # Mount /, /usr, /boot, /efi read-only; /etc read-only
|
|
ProtectHome = "read-only"; # Make /home, /root, /run/user read-only
|
|
|
|
# Filesystem isolation
|
|
ProtectProc = "invisible"; # Hide processes owned by other users in /proc
|
|
ProcSubset = "pid"; # Only expose /proc/self and /proc/pid/*, hide /proc/sys, /proc/fs, etc.
|
|
PrivateDevices = true; # Private /dev with only pseudo-devices (null, zero, random), no physical hardware
|
|
ProtectKernelTunables = true; # Make /proc/sys and /sys read-only, prevent tweaking kernel parameters
|
|
ProtectKernelModules = true; # Deny module loading/unloading, block access to /usr/lib/modules
|
|
ProtectKernelLogs = true; # Block access to kernel log buffer (/dev/kmsg, dmesg)
|
|
ProtectControlGroups = true; # Make /sys/fs/cgroup read-only, prevent cgroup manipulation
|
|
ProtectClock = true; # Prevent setting system/hardware clock
|
|
ProtectHostname = true; # Prevent changing hostname/domainname
|
|
|
|
# Only allow IPv4, IPv6, and Unix sockets; block netlink, bluetooth, packet sockets, etc.
|
|
RestrictAddressFamilies = [
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
"AF_UNIX"
|
|
"AF_NETLINK" # Required for querying network interfaces
|
|
];
|
|
|
|
# Drop all capabilities since ports > 1024 don't need CAP_NET_BIND_SERVICE
|
|
CapabilityBoundingSet = "";
|
|
AmbientCapabilities = "";
|
|
|
|
# System calls
|
|
SystemCallArchitectures = "native"; # Only allow native arch syscalls, block 32-bit compat (common exploit vector)
|
|
|
|
# Allowlist syscalls typical for services, minus privileged ops (reboot, mount) and resource control
|
|
SystemCallFilter = [
|
|
"@system-service"
|
|
"~@privileged"
|
|
"~@resources"
|
|
];
|
|
|
|
SystemCallErrorNumber = "EPERM"; # Return "Permission denied" for blocked syscalls instead of killing process
|
|
|
|
# Other restrictions
|
|
LockPersonality = true; # Prevent changing execution domain (used for foreign binaries)
|
|
RestrictNamespaces = true; # Prevent creating new namespaces; blocks container-escape techniques
|
|
RestrictRealtime = true; # Prevent realtime scheduling; stops potential DoS via CPU monopolization
|
|
RestrictSUIDSGID = true; # Prevent creating/running SUID/SGID files
|
|
UMask = "0027"; # Files created are 0640 (owner rw, group r), directories 0750
|
|
};
|
|
};
|
|
}
|
|
);
|
|
|
|
}
|