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:
@@ -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;
|
||||
|
@@ -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 "";
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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 = {};
|
||||
|
||||
|
Reference in New Issue
Block a user