sane.fs: remove public access to the "unit" fields

fs entries soon won't correspond to systemd units, and hence that option's a bit nonsensical
This commit is contained in:
2024-09-30 09:10:40 +00:00
parent e7cf14cc4c
commit 48c81610a5
4 changed files with 103 additions and 129 deletions

View File

@@ -68,10 +68,6 @@ let
if this unit fails, it will not block the targets in this list.
'';
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which ensures this entry";
};
};
config = let
default-acl = {
@@ -80,10 +76,6 @@ let
mode = lib.mkDefault (parent-acl.mode or "0755");
};
in {
# we put this here instead of as a `default` to ensure that users who specify additional
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
generated.depends = if has-parent then [ parent-cfg.unit ] else [];
# populate generated items from `dir` or `symlink` shorthands
generated.acl = lib.mkMerge [
default-acl
@@ -102,21 +94,8 @@ let
(lib.mkIf (config.symlink != null) (lib.escapeShellArgs [ "${ensure-symlink}/bin/ensure-symlink" name config.symlink.target ]))
];
# make the unit file which generates the underlying thing available so that `mount` can use it.
generated.unit = (serviceNameFor name) + ".service";
# if we were asked to mount, make sure we create the dir that we mount over
dir = lib.mkIf (config.mount != null) {};
# if defaulted, this module is responsible for finalizing the entry.
# the user could override this if, say, they finalize some aspect of the entry
# with a custom service.
unit = lib.mkDefault (
if config.mount != null then
config.mount.unit
else
config.generated.unit
);
};
});
@@ -207,10 +186,6 @@ let
or a string, in which case it's passed onto the CLI unescaped.
'';
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which ensures this directory";
};
};
};
@@ -229,11 +204,6 @@ let
'';
default = [];
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which mounts this path";
default = mountNameFor path;
};
mountConfig = mkOption {
type = types.attrs;
description = ''
@@ -284,16 +254,13 @@ let
# given a mountEntry definition, evaluate its toplevel `config` output.
mkMountConfig = path: opt: let
fsEntry = config.fileSystems."${path}";
underlying = cfg."${fsEntry.device}";
isBind = opt.mount.bind != null;
ifBind = lib.mkIf isBind;
# before mounting:
# - create the target directory
# - 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/" fsEntry.device || lib.hasPrefix "fuse" (fsEntry.fsType or "unknown") then [] else [ underlying.unit ])
++ opt.mount.depends;
requires = opt.mount.depends;
in {
fileSystems."${path}" = {
device = ifBind opt.mount.bind;

View File

@@ -231,7 +231,6 @@ in
(lib.optionalAttrs (opt.type == "dir") {
# create the backing path as a dir
sane.fs."${fsPathToBackingPath fspath}" = {
wantedBeforeBy = [ config.sane.fs."${fspath}".unit ];
dir.acl = config.sane.fs."${fspath}".generated.acl;
};
})
@@ -240,7 +239,6 @@ in
# the old way was to create the parent directory and leave the file empty, expecting the program to create it.
# that doesn't work well with sandboxing, where the fs handles we want to give the program have to exist before launch.
sane.fs."${fsPathToBackingPath fspath}" = {
wantedBeforeBy = [ config.sane.fs."${fspath}".unit ];
file.acl = config.sane.fs."${fspath}".generated.acl;
file.text = lib.mkDefault "";
};

View File

@@ -4,6 +4,17 @@ let
persist-base = "/nix/persist";
origin = config.sane.persist.stores."ephemeral".origin;
backing = sane-lib.path.concat [ persist-base "ephemeral" ];
# fileSystems.* options
device = "gocryptfs-ephemeral#${backing}";
fsType = "fuse3.sane";
options = [
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"pass_fuse_fd"
];
in
lib.mkIf config.sane.persist.enable
{
@@ -44,55 +55,53 @@ lib.mkIf config.sane.persist.enable
};
fileSystems."${origin}" = {
device = "gocryptfs-ephemeral#${backing}";
fsType = "fuse3.sane";
options = [
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"pass_fuse_fd"
];
inherit device fsType options;
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}" = {
wantedBeforeBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
];
# tell systemd about the mount so that i can sandbox it
systemd.mounts = [{
where = origin;
what = device;
type = fsType;
options = lib.concatStringsSep "," options;
wantedBy = [ "local-fs.target" ];
before = [ "local-fs.target" ];
unitConfig.RequiresMountsFor = [ backing ];
# hardening (systemd-analyze security mnt-persist-ephemeral.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
# 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
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
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs";
# mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
mountConfig.RestrictFileSystems = "@common-block @basic-api 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" "@sandbox" "~@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";
mountConfig.IPAddressDeny = "any";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse";
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?
};
# mountConfig.PrivateNetwork = true BREAKS the mount action; i think systemd or udev needs that internally to communicate with the service manager?
}];
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}".dir = {};
sane.fs."${backing}".dir = {};
sane.programs.gocryptfs-ephemeral.enableFor.system = true;

View File

@@ -4,6 +4,27 @@ let
persist-base = "/nix/persist";
origin = config.sane.persist.stores."private".origin;
backing = sane-lib.path.concat [ persist-base "private" ];
# fileSystems.* options
device = "gocryptfs-private#${backing}";
fsType = "fuse3.sane";
options = [
# "auto"
"nofail"
# "noexec" # handful of scripts in ~/knowledge that are executable
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "quiet"
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
# "passfile=/run/gocryptfs/private.key"
# options so that we can block for the password file *without* systemd killing us.
# see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174>
# "x-systemd.mount-timeout=infinity"
# "retry=10000"
# "fg"
"pass_fuse_fd"
];
in
lib.mkIf config.sane.persist.enable
{
@@ -56,76 +77,55 @@ lib.mkIf config.sane.persist.enable
can be auto-unlocked at login.
'';
origin = lib.mkDefault "/mnt/persist/private";
defaultOrdering = let
private-unit = config.sane.fs."${origin}".unit;
in {
# auto create only after the store is mounted
wantedBy = [ private-unit ];
# we can't create things in private before local-fs.target
wantedBeforeBy = [ ];
};
defaultMethod = "symlink";
};
fileSystems."${origin}" = {
device = "gocryptfs-private#${backing}";
fsType = "fuse3.sane";
options = [
# "auto"
"nofail"
# "noexec" # handful of scripts in ~/knowledge that are executable
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "quiet"
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
# "passfile=/run/gocryptfs/private.key"
# options so that we can block for the password file *without* systemd killing us.
# see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174>
# "x-systemd.mount-timeout=infinity"
# "retry=10000"
# "fg"
"pass_fuse_fd"
];
inherit device fsType options;
noCheck = true;
};
# let sane.fs know about the mount
sane.fs."${origin}" = {
wantedBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
"gocryptfs-private-key.service"
];
# tell systemd about the mount so that i can sandbox it
systemd.mounts = [{
where = origin;
what = device;
type = fsType;
options = lib.concatStringsSep "," options;
after = [ "gocryptfs-private-key.service" ];
wants = [ "gocryptfs-private-key.service" ];
unitConfig.RequiresMountsFor = [ backing ];
# unitConfig.DefaultDependencies = "no";
# mount.mountConfig.TimeoutSec = "infinity";
# mountConfig.TimeoutSec = "infinity";
# hardening (systemd-analyze security mnt-persist-private.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
# 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 @basic-api fuse pipefs";
# mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
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 @basic-api 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" "@sandbox" "~@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";
};
mountConfig.IPAddressDeny = "any";
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mountConfig.DeviceAllow = "/dev/fuse";
mountConfig.SocketBindDeny = "any";
}];
# let sane.fs know about the mount
sane.fs."${origin}" = { };
# it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}".dir = {};