439 lines
13 KiB
Nix
439 lines
13 KiB
Nix
{
|
|
lib,
|
|
pkgs,
|
|
config,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
types
|
|
mkOption
|
|
mkEnableOption
|
|
mkIf
|
|
mkDefault
|
|
mapAttrs
|
|
mapAttrsToList
|
|
mergeAttrsList
|
|
imap1
|
|
foldr
|
|
foldlAttrs
|
|
optional
|
|
optionals
|
|
;
|
|
|
|
cfg = config.environment.fortify;
|
|
in
|
|
|
|
{
|
|
options = {
|
|
environment.fortify = {
|
|
enable = mkEnableOption "fortify";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.callPackage ./package.nix { };
|
|
description = "Package providing fortify.";
|
|
};
|
|
|
|
users = mkOption {
|
|
type =
|
|
let
|
|
inherit (types) attrsOf ints;
|
|
in
|
|
attrsOf (ints.between 0 99);
|
|
description = ''
|
|
Users allowed to spawn fortify apps, as well as their fortify ID value.
|
|
'';
|
|
};
|
|
|
|
apps = mkOption {
|
|
type =
|
|
let
|
|
inherit (types)
|
|
str
|
|
enum
|
|
bool
|
|
package
|
|
anything
|
|
submodule
|
|
listOf
|
|
attrsOf
|
|
nullOr
|
|
functionTo
|
|
;
|
|
in
|
|
listOf (submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = str;
|
|
description = ''
|
|
App name, typically command.
|
|
'';
|
|
};
|
|
|
|
id = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = ''
|
|
Freedesktop application ID.
|
|
'';
|
|
};
|
|
|
|
packages = mkOption {
|
|
type = listOf package;
|
|
default = [ ];
|
|
description = ''
|
|
List of extra packages to install via home-manager.
|
|
'';
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = anything;
|
|
default = { };
|
|
description = "Extra home-manager configuration.";
|
|
};
|
|
|
|
script = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = ''
|
|
Application launch script.
|
|
'';
|
|
};
|
|
|
|
command = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = ''
|
|
Command to run as the target user.
|
|
Setting this to null will default command to wrapper name.
|
|
Has no effect when script is set.
|
|
'';
|
|
};
|
|
|
|
groups = mkOption {
|
|
type = listOf str;
|
|
default = [ ];
|
|
description = ''
|
|
List of groups to inherit from the privileged user.
|
|
'';
|
|
};
|
|
|
|
dbus = {
|
|
session = mkOption {
|
|
type = nullOr (functionTo anything);
|
|
default = null;
|
|
description = ''
|
|
D-Bus session bus custom configuration.
|
|
Setting this to null will enable built-in defaults.
|
|
'';
|
|
};
|
|
|
|
system = mkOption {
|
|
type = nullOr anything;
|
|
default = null;
|
|
description = ''
|
|
D-Bus system bus custom configuration.
|
|
Setting this to null will disable the system bus proxy.
|
|
'';
|
|
};
|
|
};
|
|
|
|
env = mkOption {
|
|
type = nullOr (attrsOf str);
|
|
default = null;
|
|
description = ''
|
|
Environment variables to set for the initial process in the sandbox.
|
|
'';
|
|
};
|
|
|
|
nix = mkEnableOption ''
|
|
Whether to allow nix daemon connections from within sandbox.
|
|
'';
|
|
|
|
userns = mkEnableOption ''
|
|
Whether to allow userns within sandbox.
|
|
'';
|
|
|
|
mapRealUid = mkEnableOption ''
|
|
Whether to map to fortify's real UID within the sandbox.
|
|
'';
|
|
|
|
net =
|
|
mkEnableOption ''
|
|
Whether to allow network access within sandbox.
|
|
''
|
|
// {
|
|
default = true;
|
|
};
|
|
|
|
gpu = mkOption {
|
|
type = nullOr bool;
|
|
default = null;
|
|
description = ''
|
|
Target process GPU and driver access.
|
|
Setting this to null will enable GPU whenever X or Wayland is enabled.
|
|
'';
|
|
};
|
|
|
|
dev = mkEnableOption ''
|
|
Whether to allow access to all devices within sandbox.
|
|
'';
|
|
|
|
extraPaths = mkOption {
|
|
type = listOf anything;
|
|
default = [ ];
|
|
description = ''
|
|
Extra paths to make available inside the sandbox.
|
|
'';
|
|
};
|
|
|
|
capability = {
|
|
wayland = mkOption {
|
|
type = bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to share the Wayland socket.
|
|
'';
|
|
};
|
|
|
|
x11 = mkOption {
|
|
type = bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to share the X11 socket and allow connection.
|
|
'';
|
|
};
|
|
|
|
dbus = mkOption {
|
|
type = bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to proxy D-Bus.
|
|
'';
|
|
};
|
|
|
|
pulse = mkOption {
|
|
type = bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to share the PulseAudio socket and cookie.
|
|
'';
|
|
};
|
|
};
|
|
|
|
share = mkOption {
|
|
type = nullOr package;
|
|
default = null;
|
|
description = ''
|
|
Package containing share files.
|
|
Setting this to null will default package name to wrapper name.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
default = [ ];
|
|
description = "Applications managed by fortify.";
|
|
};
|
|
|
|
stateDir = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
The path to persistent storage where per-user state should be stored.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
security.wrappers.fsu = {
|
|
source = "${cfg.package}/libexec/fsu";
|
|
setuid = true;
|
|
owner = "root";
|
|
setgid = true;
|
|
group = "root";
|
|
};
|
|
|
|
environment.etc = {
|
|
fsurc = {
|
|
mode = "0400";
|
|
text = foldlAttrs (
|
|
acc: username: fid:
|
|
"${toString config.users.users.${username}.uid} ${toString fid}\n" + acc
|
|
) "" cfg.users;
|
|
};
|
|
|
|
userdb.source = pkgs.runCommand "generate-userdb" { } ''
|
|
${cfg.package}/libexec/fuserdb -o $out ${
|
|
foldlAttrs (
|
|
acc: username: fid:
|
|
acc + " ${username}:${toString fid}"
|
|
) "-s /run/current-system/sw/bin/nologin -d ${cfg.stateDir}" cfg.users
|
|
}
|
|
'';
|
|
};
|
|
|
|
services.userdbd.enable = mkDefault true;
|
|
|
|
home-manager =
|
|
let
|
|
privPackages = mapAttrs (username: fid: {
|
|
home.packages =
|
|
let
|
|
# aid 0 is reserved
|
|
wrappers = imap1 (
|
|
aid: app:
|
|
let
|
|
extendDBusDefault = id: ext: {
|
|
filter = true;
|
|
|
|
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
|
|
own =
|
|
(optionals (app.id != null) [
|
|
"${id}.*"
|
|
"org.mpris.MediaPlayer2.${id}.*"
|
|
])
|
|
++ ext.own;
|
|
|
|
inherit (ext) call broadcast;
|
|
};
|
|
dbusConfig =
|
|
let
|
|
default = {
|
|
talk = [ ];
|
|
own = [ ];
|
|
call = { };
|
|
broadcast = { };
|
|
};
|
|
in
|
|
{
|
|
session_bus =
|
|
if app.dbus.session != null then
|
|
(app.dbus.session (extendDBusDefault app.id))
|
|
else
|
|
(extendDBusDefault app.id default);
|
|
system_bus = app.dbus.system;
|
|
};
|
|
command = if app.command == null then app.name else app.command;
|
|
script = if app.script == null then ("exec " + command + " $@") else app.script;
|
|
enablements =
|
|
with app.capability;
|
|
(if wayland then 1 else 0)
|
|
+ (if x11 then 2 else 0)
|
|
+ (if dbus then 4 else 0)
|
|
+ (if pulse then 8 else 0);
|
|
conf = {
|
|
inherit (app) id;
|
|
command = [
|
|
(pkgs.writeScript "${app.name}-start" ''
|
|
#!${pkgs.zsh}${pkgs.zsh.shellPath}
|
|
${script}
|
|
'')
|
|
];
|
|
confinement = {
|
|
app_id = aid;
|
|
inherit (app) groups;
|
|
username = "u${toString fid}_a${toString aid}";
|
|
home = "${cfg.stateDir}/${toString fid}/${toString aid}";
|
|
sandbox = {
|
|
inherit (app)
|
|
userns
|
|
net
|
|
dev
|
|
env
|
|
;
|
|
map_real_uid = app.mapRealUid;
|
|
filesystem =
|
|
[
|
|
{ src = "/bin"; }
|
|
{ src = "/usr/bin"; }
|
|
{ src = "/nix/store"; }
|
|
{ src = "/run/current-system"; }
|
|
{
|
|
src = "/sys/block";
|
|
require = false;
|
|
}
|
|
{
|
|
src = "/sys/bus";
|
|
require = false;
|
|
}
|
|
{
|
|
src = "/sys/class";
|
|
require = false;
|
|
}
|
|
{
|
|
src = "/sys/dev";
|
|
require = false;
|
|
}
|
|
{
|
|
src = "/sys/devices";
|
|
require = false;
|
|
}
|
|
]
|
|
++ optionals app.nix [
|
|
{ src = "/nix/var"; }
|
|
{ src = "/var/db/nix-channels"; }
|
|
]
|
|
++ optionals (if app.gpu != null then app.gpu else app.capability.wayland || app.capability.x11) [
|
|
{ src = "/run/opengl-driver"; }
|
|
{
|
|
src = "/dev/dri";
|
|
dev = true;
|
|
}
|
|
]
|
|
++ app.extraPaths;
|
|
auto_etc = true;
|
|
override = [ "/var/run/nscd" ];
|
|
};
|
|
inherit enablements;
|
|
inherit (dbusConfig) session_bus system_bus;
|
|
};
|
|
};
|
|
in
|
|
pkgs.writeShellScriptBin app.name ''
|
|
exec fortify app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
|
|
''
|
|
) cfg.apps;
|
|
in
|
|
foldr (
|
|
app: acc:
|
|
let
|
|
pkg = if app.share != null then app.share else pkgs.${app.name};
|
|
copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true";
|
|
in
|
|
optional (app.capability.wayland || app.capability.x11) (
|
|
pkgs.runCommand "${app.name}-share" { } ''
|
|
mkdir -p $out/share
|
|
${copy "${pkg}/share/applications"}
|
|
${copy "${pkg}/share/icons"}
|
|
${copy "${pkg}/share/man"}
|
|
|
|
substituteInPlace $out/share/applications/* \
|
|
--replace-warn '${pkg}/bin/' "" \
|
|
--replace-warn '${pkg}/libexec/' ""
|
|
''
|
|
)
|
|
++ acc
|
|
) (wrappers ++ [ cfg.package ]) cfg.apps;
|
|
}) cfg.users;
|
|
in
|
|
{
|
|
useUserPackages = false; # prevent users.users entries from being added
|
|
|
|
users = foldlAttrs (
|
|
acc: _: fid:
|
|
mergeAttrsList (
|
|
# aid 0 is reserved
|
|
imap1 (aid: app: {
|
|
"u${toString fid}_a${toString aid}" = app.extraConfig // {
|
|
home.packages = app.packages;
|
|
};
|
|
}) cfg.apps
|
|
)
|
|
// acc
|
|
) privPackages cfg.users;
|
|
};
|
|
};
|
|
}
|