nix-files/hosts/common/programs/sane-scripts.nix

291 lines
9.0 KiB
Nix

{ config, lib, ... }:
let
declPackageSet = pkgs: {
packageUnwrapped = null;
suggestedPrograms = pkgs;
};
in
{
sane.programs = {
"sane-scripts.backup" = declPackageSet [
"sane-scripts.backup-ls"
"sane-scripts.backup-restore"
];
"sane-scripts.bittorrent" = declPackageSet [
"sane-scripts.bt-add"
"sane-scripts.bt-rm"
"sane-scripts.bt-search"
"sane-scripts.bt-show"
];
"sane-scripts.dev" = declPackageSet [
"sane-scripts.clone"
"sane-scripts.dev-cargo-loop"
];
"sane-scripts.cli" = declPackageSet [
"sane-scripts.deadlines"
"sane-scripts.find-dotfiles"
"sane-scripts.ip-check"
"sane-scripts.private-change-passwd"
"sane-scripts.private-do"
"sane-scripts.private-init"
"sane-scripts.private-lock"
"sane-scripts.private-unlock"
"sane-scripts.rcp"
"sane-scripts.reboot"
"sane-scripts.reclaim-boot-space"
"sane-scripts.reclaim-disk-space"
"sane-scripts.secrets-dump"
"sane-scripts.secrets-unlock"
"sane-scripts.secrets-update-keys"
"sane-scripts.shutdown"
"sane-scripts.sudo-redirect"
"sane-scripts.tag-music"
"sane-scripts.vpn"
"sane-scripts.which"
"sane-scripts.wipe"
];
"sane-scripts.sys-utils" = declPackageSet [
"sane-scripts.ip-port-forward"
"sane-scripts.sync-music"
];
"sane-scripts.backup-ls" = {};
"sane-scripts.backup-restore" = {};
"sane-scripts.bt-add".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
net = "clearnet";
# TODO: migrate `transmission_passwd` to `secrets` api
extraPaths = [ "/run/secrets/transmission_passwd" ];
};
"sane-scripts.bt-rm".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
net = "clearnet";
# TODO: migrate `transmission_passwd` to `secrets` api
extraPaths = [ "/run/secrets/transmission_passwd" ];
};
"sane-scripts.bt-search".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
net = "clearnet";
# TODO: migrate `jackett_apikey` to `secrets` api
extraPaths = [ "/run/secrets/jackett_apikey" ];
};
"sane-scripts.bt-show".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
net = "clearnet";
# TODO: migrate `transmission_passwd` to `secrets` api
extraPaths = [ "/run/secrets/transmission_passwd" ];
};
# the idea of this script is to `cd` into a fresh clone...
# but that's an ephemeral operation that would be lost when the sandbox closes.
"sane-scripts.clone".sandbox.enable = false;
"sane-scripts.deadlines".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraHomePaths = [ "knowledge/planner/deadlines.tsv" ];
};
"sane-scripts.dev-cargo-loop".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
net = "clearnet";
whitelistPwd = true;
extraPaths = [
# a build script can do a lot... but a well-written one will be confined
# to XDG dirs and the local dir, and maybe the internet for fetching dependencies.
".cache"
".config"
".local"
];
};
"sane-scripts.find-dotfiles".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraHomePaths = [
"/"
".persist/ephemeral"
".persist/plaintext"
];
};
"sane-scripts.ip-check".sandbox = {
method = "landlock";
wrapperType = "wrappedDerivation";
net = "all";
};
"sane-scripts.ip-port-forward" = {};
"sane-scripts.private-change-passwd".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
autodetectCliPaths = "existing"; #< for the new `private` location
capabilities = [ "sys_admin" ]; # it needs to mount the new store
extraHomePaths = [
".persist/private"
];
};
"sane-scripts.private-do".sandbox = {
# because `mount` is a cap_sys_admin syscall, there's no great way to mount stuff dynamically like this.
# instead, we put ourselves in a mount namespace, do the mount, and drop into a shell or run a command.
# this actually has an OK side effect, that the mount isn't shared, and so we avoid contention/interleaving that would cause the ending `umount` to fail.
method = "bwrap";
wrapperType = "wrappedDerivation";
# cap_sys_admin is needed to mount stuff.
# ordinarily /run/wrappers/bin/mount would do that via setuid, but sandboxes have no_new_privs by default.
capabilities = [ "sys_admin" ];
# `sane-private-do` acts as a launcher, so give it access to anything it could possibly need.
# (crucially, that includes the backing store)
net = "all";
extraPaths = [ "/" ];
};
"sane-scripts.private-init".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
capabilities = [ "sys_admin" ]; # it needs to mount the new store
extraHomePaths = [
".persist/private"
];
};
"sane-scripts.private-lock".sandbox.enable = false;
"sane-scripts.private-unlock".sandbox.enable = false;
"sane-scripts.reclaim-boot-space".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraPaths = [ "/boot" ];
};
# it's just a thin wrapper around rsync, which is already sandboxed
"sane-scripts.rcp".sandbox.enable = false;
# but make sure rsync is always on PATH, so that we actually do get sandboxing :)
"sane-scripts.rcp".suggestedPrograms = [ "rsync" ];
"sane-scripts.reboot".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraPaths = [
"/run/dbus"
"/run/systemd"
];
};
"sane-scripts.reclaim-disk-space".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraPaths = [ "/nix/var/nix" ];
};
"sane-scripts.secrets-unlock".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraHomePaths = [
".ssh/id_ed25519"
".ssh/id_ed25519.pub"
".config/sops"
];
};
# sane-secrets-dump is a thin wrapper around sops + some utilities.
# really i should sandbox just the utilities
"sane-scripts.secrets-dump".sandbox.enable = false;
"sane-scripts.secrets-dump".suggestedPrograms = [
"gnugrep"
"oath-toolkit"
"sops"
];
# sane-secrets-update-keys is a thin wrapper around sops + some utilities.
# really i should sandbox just the utilities
"sane-scripts.secrets-update-keys".sandbox.enable = false;
"sane-scripts.secrets-update-keys".suggestedPrograms = [
"findutils"
"sops"
];
"sane-scripts.shutdown".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraPaths = [
"/run/dbus"
"/run/systemd"
];
};
"sane-scripts.stop-all-servo" = {};
# if `tee` isn't trustworthy we have bigger problems
"sane-scripts.sudo-redirect".sandbox.enable = false;
"sane-scripts.sync-music" = {};
"sane-scripts.sync-from-iphone" = {};
"sane-scripts.tag-music".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
autodetectCliPaths = "existing";
};
"sane-scripts.vpn".fs = lib.foldl'
(acc: vpn:
let
vpnCfg = config.sane.vpn."${vpn}";
in acc // {
".config/sane-vpn/vpns/${vpn}".symlink.text = ''
id=${builtins.toString vpnCfg.id}
fwmark=${builtins.toString vpnCfg.fwmark}
priorityMain=${builtins.toString vpnCfg.priorityMain}
priorityFwMark=${builtins.toString vpnCfg.priorityFwMark}
bridgeDevice=${vpnCfg.bridgeDevice}
dns=(${lib.concatStringsSep " " vpnCfg.dns})
'';
} // (lib.optionalAttrs vpnCfg.isDefault {
".config/sane-vpn/default".symlink.text = vpn;
})
)
{}
(builtins.attrNames config.sane.vpn);
"sane-scripts.vpn".sandbox = {
method = "landlock"; #< bwrap can't handle `ip link` stuff even with cap_net_admin
wrapperType = "wrappedDerivation";
net = "all";
capabilities = [ "net_admin" ];
extraHomePaths = [ ".config/sane-vpn" ];
};
"sane-scripts.which".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
extraHomePaths = [
# for SXMO
".config/sxmo/hooks"
];
};
"sane-scripts.wipe".sandbox = {
method = "bwrap";
wrapperType = "wrappedDerivation";
whitelistDbus = [ "user" ]; #< for `secret-tool` and `systemd --user stop <service>
extraHomePaths = [
# could be more specific, but at a maintenance cost.
# TODO: needs updating, now that persisted data lives behind symlinks!
# both this list AND the script need patching for that.
".cache"
".config"
".local/share"
".librewolf"
".mozilla"
];
};
};
}