sane.fs: take in the role of generating systemd.mounts files

This commit is contained in:
2024-08-02 07:33:21 +00:00
parent 113b107d73
commit 9dbb2a6266
4 changed files with 118 additions and 179 deletions

View File

@@ -126,11 +126,6 @@ let
systemdName = utils.escapeSystemdPath localPath;
in {
sane.programs.curlftpfs.enableFor.system = true;
sane.fs."${localPath}" = sane-lib.fs.wanted {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
};
fileSystems."${localPath}" = {
device = "ftp://servo-hn:/${subdir}";
noCheck = true;
@@ -139,43 +134,37 @@ let
# fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount;
};
systemd.mounts = let
fsEntry = config.fileSystems."${localPath}";
in [{
#VVV repeat what systemd would ordinarily scrape from /etc/fstab
where = localPath;
what = fsEntry.device;
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
wantedBy = [ "default.target" ]; #< TODO: move this into nixos fileSystems
sane.fs."${localPath}" = {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
wantedBy = [ "default.target" ];
mount.depends = [ "network-online.target" ];
#VVV patch so that when the mount fails, we start a timer to remount it.
# and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
onFailure = [ "${systemdName}.timer" ];
onSuccess = [ "${systemdName}-restart-timer.target" ];
mount.unitConfig.OnFailure = [ "${systemdName}.timer" ];
mount.unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
mountConfig.User = "colin";
mountConfig.Group = "users";
mount.mountConfig.User = "colin";
mount.mountConfig.Group = "users";
# hardening (systemd-analyze security mnt-servo-playground.mount)
mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN";
mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN";
mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN";
mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse";
mountConfig.RestrictNamespaces = true;
mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [
mount.mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse";
mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
"@system-service"
"@mount"
"~@chown"
@@ -184,71 +173,12 @@ let
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
# so that's pretty useless as a way to prevent write access
];
mountConfig.IPAddressDeny = "any";
mountConfig.IPAddressAllow = "10.0.10.5";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse";
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.IPAddressAllow = "10.0.10.5";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
};
# SystemCallFilter=~@aio
# SystemCallFilter=~@basic-io
# SystemCallFilter=write
# SystemCallFilter=writev
# SystemCallFilter=_llseek
# SystemCallFilter=close
# SystemCallFilter=close_range
# SystemCallFilter=dup
# SystemCallFilter=dup2
# SystemCallFilter=dup3
# SystemCallFilter=lseek
# SystemCallFilter=pread64
# SystemCallFilter=preadv
# SystemCallFilter=preadv2
# SystemCallFilter=read
# SystemCallFilter=readv
# SystemCallFilter=access
# SystemCallFilter=chdir
# SystemCallFilter=close
# SystemCallFilter=faccessat
# SystemCallFilter=faccessat2
# SystemCallFilter=fchdir
# SystemCallFilter=fcntl
# SystemCallFilter=fcntl64
# SystemCallFilter=fgetxattr
# SystemCallFilter=flistxattr
# SystemCallFilter=fstat
# SystemCallFilter=fstat64
# SystemCallFilter=fstatat64
# SystemCallFilter=fstatfs
# SystemCallFilter=fstatfs64
# SystemCallFilter=futimesat
# SystemCallFilter=getcwd
# SystemCallFilter=getdents
# SystemCallFilter=getdents64
# SystemCallFilter=getxattr
# SystemCallFilter=lgetxattr
# SystemCallFilter=listxattr
# SystemCallFilter=llistxattr
# SystemCallFilter=lstat
# SystemCallFilter=lstat64
# SystemCallFilter=newfstatat
# SystemCallFilter=oldfstat
# SystemCallFilter=oldlstat
# SystemCallFilter=oldstat
# SystemCallFilter=open
# SystemCallFilter=openat
# SystemCallFilter=openat2
# SystemCallFilter=readlink
# SystemCallFilter=readlinkat
# SystemCallFilter=stat
# SystemCallFilter=stat64
# SystemCallFilter=statfs
# SystemCallFilter=statfs64
# SystemCallFilter=statx
# SystemCallFilter=utime
# SystemCallFilter=utimensat
# SystemCallFilter=utimensat_time64
# SystemCallFilter=utimes
}];
systemd.targets."${systemdName}-restart-timer" = {
# hack unit which, when started, stops the timer (if running), and then starts it again.
after = [ "${systemdName}.timer" ];

View File

@@ -234,6 +234,20 @@ let
description = "name of the systemd unit which mounts this path";
default = mountNameFor path;
};
mountConfig = mkOption {
type = types.attrs;
description = ''
attrset to add to the [Mount] section of the systemd unit file.
'';
default = {};
};
unitConfig = mkOption {
type = types.attrs;
description = ''
attrset to add to the [Unit] section of the systemd unit file.
'';
default = {};
};
};
};
@@ -278,7 +292,7 @@ let
# - prepare the source directory -- assuming it's not an external device
# - satisfy any user-specified prerequisites ("depends")
requires = [ opt.generated.unit ]
++ (if lib.hasPrefix "/dev/disk/" device then [] else [ underlying.unit ])
++ (if lib.hasPrefix "/dev/disk/" device || lib.hasPrefix "ftp://" device then [] else [ underlying.unit ])
++ opt.mount.depends;
in {
fileSystems."${path}" = {
@@ -298,6 +312,19 @@ let
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
noCheck = ifBind true;
};
systemd.mounts = let
fsEntry = config.fileSystems."${path}";
in [{
where = path;
what = if fsEntry.device != null then fsEntry.device else "";
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
after = requires;
requires = requires;
before = opt.wantedBeforeBy;
wantedBy = opt.wantedBeforeBy;
inherit (opt.mount) mountConfig unitConfig;
}];
});
@@ -358,7 +385,9 @@ in {
let
configs = lib.mapAttrsToList mkFsConfig cfg;
take = f: {
systemd.services = f.systemd.services;
systemd = f.systemd;
# systemd.mounts = f.systemd.mounts;
# systemd.services = f.systemd.services;
fileSystems = f.fileSystems;
};
in take (sane-lib.mkTypedMerge take configs);

View File

@@ -51,50 +51,43 @@ lib.mkIf config.sane.persist.enable
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}".mount = { };
sane.fs."${backing}" = sane-lib.fs.wantedDir;
systemd.mounts = let
fsEntry = config.fileSystems."${origin}";
in [{
#VVV repeat what systemd would ordinarily scrape from /etc/fstab
where = origin;
what = fsEntry.device;
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
wantedBy = [ "local-fs.target" ];
sane.fs."${origin}" = {
wantedBeforeBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
];
# hardening (systemd-analyze security mnt-persist-ephemeral.mount)
mountConfig.AmbientCapabilities = "";
mount.mountConfig.AmbientCapabilities = "";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mountConfig.RestrictNamespaces = true;
mountConfig.RestrictNetworkInterfaces = "";
mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [
mount.mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
"@system-service" "@mount" "~@cpu-emulation" "~@keyring"
];
mountConfig.IPAddressDeny = "any";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse";
mountConfig.SocketBindDeny = "any";
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
mount.mountConfig.SocketBindDeny = "any";
# note that anything which requires mount namespaces (ProtectHome, ReadWritePaths, ...) does NOT work.
# it's in theory possible, via mount propagation, but systemd provides no way for that.
# PrivateNetwork = true BREAKS the mount action; i think systemd or udev needs that internally to communicate with the service manager?
}];
};
sane.fs."${backing}".dir = {};
system.fsPackages = [ gocryptfs-ephemeral ]; # fuse needs to find gocryptfs
}

View File

@@ -92,7 +92,7 @@ lib.mkIf config.sane.persist.enable
device = backing;
fsType = "fuse.gocryptfs-private";
options = [
"auto"
# "auto"
"nofail"
# "nodev" # "Unknown parameter 'nodev'". gocryptfs requires this be passed as `-ko nodev`
# "noexec" # handful of scripts in ~/knowledge that are executable
@@ -112,11 +112,39 @@ lib.mkIf config.sane.persist.enable
# let sane.fs know about the mount
sane.fs."${origin}" = {
wantedBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
config.sane.fs."/run/gocryptfs".unit
];
wantedBy = [ "local-fs.target" ];
# unitConfig.DefaultDependencies = "no";
mount.mountConfig.TimeoutSec = "infinity";
# hardening (systemd-analyze security mnt-persist-private.mount)
mount.mountConfig.AmbientCapabilities = "";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
mount.mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
"@system-service" "@mount" "~@cpu-emulation" "~@keyring"
];
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
mount.mountConfig.SocketBindDeny = "any";
};
# it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}".dir.acl.user = config.sane.defaultUser;
@@ -125,47 +153,6 @@ lib.mkIf config.sane.persist.enable
mode = "0700";
};
systemd.mounts = let
fsEntry = config.fileSystems."${origin}";
in [{
#VVV repeat what systemd would ordinarily scrape from /etc/fstab
where = origin;
what = fsEntry.device;
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
after = [ config.sane.fs."${backing}".unit config.sane.fs."/run/gocryptfs".unit ];
wants = [ config.sane.fs."${backing}".unit config.sane.fs."/run/gocryptfs".unit ];
wantedBy = [ "local-fs.target" ];
# before = [ "local-fs.target" ];
# unitConfig.DefaultDependencies = "no";
# hardening (systemd-analyze security mnt-persist-private.mount)
mountConfig.AmbientCapabilities = "";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mountConfig.RestrictNamespaces = true;
mountConfig.RestrictNetworkInterfaces = "";
mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
"@system-service" "@mount" "~@cpu-emulation" "~@keyring"
];
mountConfig.IPAddressDeny = "any";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse";
mountConfig.SocketBindDeny = "any";
}];
system.fsPackages = [ gocryptfs-private ];
sane.user.services.gocryptfs-private = {