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; systemdName = utils.escapeSystemdPath localPath;
in { in {
sane.programs.curlftpfs.enableFor.system = true; 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}" = { fileSystems."${localPath}" = {
device = "ftp://servo-hn:/${subdir}"; device = "ftp://servo-hn:/${subdir}";
noCheck = true; noCheck = true;
@@ -139,43 +134,37 @@ let
# fsType = "nfs"; # fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount; # options = fsOpts.nfs ++ fsOpts.lazyMount;
}; };
sane.fs."${localPath}" = {
systemd.mounts = let dir.acl.user = "colin";
fsEntry = config.fileSystems."${localPath}"; dir.acl.group = "users";
in [{ dir.acl.mode = "0750";
#VVV repeat what systemd would ordinarily scrape from /etc/fstab wantedBy = [ "default.target" ];
where = localPath; mount.depends = [ "network-online.target" ];
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
#VVV patch so that when the mount fails, we start a timer to remount it. #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 # and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
onFailure = [ "${systemdName}.timer" ]; mount.unitConfig.OnFailure = [ "${systemdName}.timer" ];
onSuccess = [ "${systemdName}-restart-timer.target" ]; mount.unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
mountConfig.User = "colin"; mount.mountConfig.User = "colin";
mountConfig.Group = "users"; mount.mountConfig.Group = "users";
# hardening (systemd-analyze security mnt-servo-playground.mount) # hardening (systemd-analyze security mnt-servo-playground.mount)
mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN"; mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN";
mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN"; mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN";
mountConfig.LockPersonality = true; mount.mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true; mount.mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true; mount.mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true; mount.mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true; mount.mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true; mount.mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/... #VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list # see `systemd-analyze filesystems` for a full list
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse"; mount.mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse";
mountConfig.RestrictNamespaces = true; mount.mountConfig.RestrictNamespaces = true;
mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true; mount.mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native"; mount.mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [ mount.mountConfig.SystemCallFilter = [
"@system-service" "@system-service"
"@mount" "@mount"
"~@chown" "~@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. # 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 # so that's pretty useless as a way to prevent write access
]; ];
mountConfig.IPAddressDeny = "any"; mount.mountConfig.IPAddressDeny = "any";
mountConfig.IPAddressAllow = "10.0.10.5"; mount.mountConfig.IPAddressAllow = "10.0.10.5";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom} mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse"; 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" = { systemd.targets."${systemdName}-restart-timer" = {
# hack unit which, when started, stops the timer (if running), and then starts it again. # hack unit which, when started, stops the timer (if running), and then starts it again.
after = [ "${systemdName}.timer" ]; after = [ "${systemdName}.timer" ];

View File

@@ -234,6 +234,20 @@ let
description = "name of the systemd unit which mounts this path"; description = "name of the systemd unit which mounts this path";
default = mountNameFor 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 # - prepare the source directory -- assuming it's not an external device
# - satisfy any user-specified prerequisites ("depends") # - satisfy any user-specified prerequisites ("depends")
requires = [ opt.generated.unit ] 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; ++ opt.mount.depends;
in { in {
fileSystems."${path}" = { fileSystems."${path}" = {
@@ -298,6 +312,19 @@ let
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy)); ++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
noCheck = ifBind true; 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 let
configs = lib.mapAttrsToList mkFsConfig cfg; configs = lib.mapAttrsToList mkFsConfig cfg;
take = f: { take = f: {
systemd.services = f.systemd.services; systemd = f.systemd;
# systemd.mounts = f.systemd.mounts;
# systemd.services = f.systemd.services;
fileSystems = f.fileSystems; fileSystems = f.fileSystems;
}; };
in take (sane-lib.mkTypedMerge take configs); in take (sane-lib.mkTypedMerge take configs);

View File

@@ -51,50 +51,43 @@ lib.mkIf config.sane.persist.enable
noCheck = true; noCheck = true;
}; };
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies # let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}".mount = { }; sane.fs."${origin}" = {
sane.fs."${backing}" = sane-lib.fs.wantedDir; wantedBeforeBy = [ "local-fs.target" ];
mount.depends = [
systemd.mounts = let config.sane.fs."${backing}".unit
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" ];
# hardening (systemd-analyze security mnt-persist-ephemeral.mount) # 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? # 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"; 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";
mountConfig.LockPersonality = true; mount.mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true; mount.mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true; mount.mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true; mount.mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true; mount.mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true; mount.mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger 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/... #VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list # see `systemd-analyze filesystems` for a full list
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs"; mount.mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mountConfig.RestrictNamespaces = true; mount.mountConfig.RestrictNamespaces = true;
mountConfig.RestrictNetworkInterfaces = ""; mount.mountConfig.RestrictNetworkInterfaces = "";
mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true; mount.mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native"; mount.mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [ mount.mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?). # 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" "@system-service" "@mount" "~@cpu-emulation" "~@keyring"
]; ];
mountConfig.IPAddressDeny = "any"; mount.mountConfig.IPAddressDeny = "any";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom} mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse"; mount.mountConfig.DeviceAllow = "/dev/fuse";
mountConfig.SocketBindDeny = "any"; mount.mountConfig.SocketBindDeny = "any";
# note that anything which requires mount namespaces (ProtectHome, ReadWritePaths, ...) does NOT work. # 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. # 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? # 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 system.fsPackages = [ gocryptfs-ephemeral ]; # fuse needs to find gocryptfs
} }

View File

@@ -92,7 +92,7 @@ lib.mkIf config.sane.persist.enable
device = backing; device = backing;
fsType = "fuse.gocryptfs-private"; fsType = "fuse.gocryptfs-private";
options = [ options = [
"auto" # "auto"
"nofail" "nofail"
# "nodev" # "Unknown parameter 'nodev'". gocryptfs requires this be passed as `-ko nodev` # "nodev" # "Unknown parameter 'nodev'". gocryptfs requires this be passed as `-ko nodev`
# "noexec" # handful of scripts in ~/knowledge that are executable # "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 # let sane.fs know about the mount
sane.fs."${origin}" = { sane.fs."${origin}" = {
wantedBy = [ "local-fs.target" ];
mount.depends = [ mount.depends = [
config.sane.fs."${backing}".unit config.sane.fs."${backing}".unit
config.sane.fs."/run/gocryptfs".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 # it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}".dir.acl.user = config.sane.defaultUser; sane.fs."${backing}".dir.acl.user = config.sane.defaultUser;
@@ -125,47 +153,6 @@ lib.mkIf config.sane.persist.enable
mode = "0700"; 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 ]; system.fsPackages = [ gocryptfs-private ];
sane.user.services.gocryptfs-private = { sane.user.services.gocryptfs-private = {