aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-01-23 17:04:19 +0900
committerOphestra <cat@gensokyo.uk>2025-01-23 17:04:19 +0900
commit134247b57d2ff93c7b70815158474d94bed1074b (patch)
treeffbe509e339a521b3ba58cd9d89a335e18bef564
parentb5bb7654da074dc92c354582d086b68d2d45024c (diff)
nix: configure target users via nixos
This makes patching home-manager no longer necessary. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/fuserdb/main.go69
-rw-r--r--cmd/fuserdb/payload.go64
-rwxr-xr-xdist/install.sh2
-rw-r--r--nixos.nix86
-rw-r--r--options.nix13
-rw-r--r--test.nix96
6 files changed, 143 insertions, 187 deletions
diff --git a/cmd/fuserdb/main.go b/cmd/fuserdb/main.go
deleted file mode 100644
index 2c3dd24e..00000000
--- a/cmd/fuserdb/main.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package main
-
-import (
- "bytes"
- "errors"
- "flag"
- "fmt"
- "os"
- "path"
- "strconv"
-
- "git.gensokyo.uk/security/fortify/internal/fmsg"
-)
-
-func main() {
- fmsg.SetPrefix("fuserdb")
-
- const varEmpty = "/var/empty"
-
- out := flag.String("o", "userdb", "output directory")
- homeDir := flag.String("d", varEmpty, "parent of home directories")
- shell := flag.String("s", "/sbin/nologin", "absolute path to subordinate user shell")
- flag.Parse()
-
- type user struct {
- name string
- fid int
- }
-
- users := make([]user, len(flag.Args()))
- for i, s := range flag.Args() {
- f := bytes.SplitN([]byte(s), []byte{':'}, 2)
- if len(f) != 2 {
- fmsg.Fatalf("invalid entry at index %d", i)
- }
- users[i].name = string(f[0])
- if fid, err := strconv.Atoi(string(f[1])); err != nil {
- fmsg.Fatal(err.Error())
- } else {
- users[i].fid = fid
- }
- }
-
- if err := os.MkdirAll(*out, 0755); err != nil && !errors.Is(err, os.ErrExist) {
- fmsg.Fatalf("cannot create output: %v", err)
- }
-
- for _, u := range users {
- fidString := strconv.Itoa(u.fid)
- for aid := 0; aid < 10000; aid++ {
- userName := fmt.Sprintf("u%d_a%d", u.fid, aid)
- uid := 1000000 + u.fid*10000 + aid
- us := strconv.Itoa(uid)
- realName := fmt.Sprintf("Fortify subordinate user %d (%s)", aid, u.name)
- var homeDirectory string
- if *homeDir != varEmpty {
- homeDirectory = path.Join(*homeDir, "u"+fidString, "a"+strconv.Itoa(aid))
- } else {
- homeDirectory = varEmpty
- }
-
- writeUser(userName, uid, us, realName, homeDirectory, *shell, *out)
- writeGroup(userName, uid, us, nil, *out)
- }
- }
-
- fmsg.Printf("created %d entries", len(users)*2*10000)
- fmsg.Exit(0)
-}
diff --git a/cmd/fuserdb/payload.go b/cmd/fuserdb/payload.go
deleted file mode 100644
index dc8a0729..00000000
--- a/cmd/fuserdb/payload.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "os"
- "path"
-
- "git.gensokyo.uk/security/fortify/internal/fmsg"
-)
-
-type payloadU struct {
- UserName string `json:"userName"`
- Uid int `json:"uid"`
- Gid int `json:"gid"`
- MemberOf []string `json:"memberOf,omitempty"`
- RealName string `json:"realName"`
- HomeDirectory string `json:"homeDirectory"`
- Shell string `json:"shell"`
-}
-
-func writeUser(userName string, uid int, us string, realName, homeDirectory, shell string, out string) {
- userFileName := userName + ".user"
- if f, err := os.OpenFile(path.Join(out, userFileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
- fmsg.Fatalf("cannot create %s: %v", userName, err)
- } else if err = json.NewEncoder(f).Encode(&payloadU{
- UserName: userName,
- Uid: uid,
- Gid: uid,
- RealName: realName,
- HomeDirectory: homeDirectory,
- Shell: shell,
- }); err != nil {
- fmsg.Fatalf("cannot serialise %s: %v", userName, err)
- } else if err = f.Close(); err != nil {
- fmsg.Printf("cannot close %s: %v", userName, err)
- }
- if err := os.Symlink(userFileName, path.Join(out, us+".user")); err != nil {
- fmsg.Fatalf("cannot link %s: %v", userName, err)
- }
-}
-
-type payloadG struct {
- GroupName string `json:"groupName"`
- Gid int `json:"gid"`
- Members []string `json:"members,omitempty"`
-}
-
-func writeGroup(groupName string, gid int, gs string, members []string, out string) {
- groupFileName := groupName + ".group"
- if f, err := os.OpenFile(path.Join(out, groupFileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
- fmsg.Fatalf("cannot create %s: %v", groupName, err)
- } else if err = json.NewEncoder(f).Encode(&payloadG{
- GroupName: groupName,
- Gid: gid,
- Members: members,
- }); err != nil {
- fmsg.Fatalf("cannot serialise %s: %v", groupName, err)
- } else if err = f.Close(); err != nil {
- fmsg.Printf("cannot close %s: %v", groupName, err)
- }
- if err := os.Symlink(groupFileName, path.Join(out, gs+".group")); err != nil {
- fmsg.Fatalf("cannot link %s: %v", groupName, err)
- }
-}
diff --git a/dist/install.sh b/dist/install.sh
index 06412265..bd1e8a28 100755
--- a/dist/install.sh
+++ b/dist/install.sh
@@ -4,8 +4,6 @@ cd "$(dirname -- "$0")" || exit 1
install -vDm0755 "bin/fortify" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fortify"
install -vDm0755 "bin/fpkg" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fpkg"
-install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb"
-
install -vDm6511 "bin/fsu" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fsu"
if [ ! -f "${FORTIFY_INSTALL_PREFIX}/etc/fsurc" ]; then
install -vDm0400 "fsurc.default" "${FORTIFY_INSTALL_PREFIX}/etc/fsurc"
diff --git a/nixos.nix b/nixos.nix
index cd4cf98c..84e58c79 100644
--- a/nixos.nix
+++ b/nixos.nix
@@ -7,6 +7,7 @@
let
inherit (lib)
+ mkMerge
mkIf
mkDefault
mapAttrs
@@ -19,6 +20,10 @@ let
;
cfg = config.environment.fortify;
+
+ getsubuid = fid: aid: 1000000 + fid * 10000 + aid;
+ getsubname = fid: aid: "u${toString fid}_a${toString aid}";
+ getsubhome = fid: aid: "${cfg.stateDir}/u${toString fid}/a${toString aid}";
in
{
@@ -33,23 +38,12 @@ in
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 "fortify-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
- }
- '';
+ environment.etc.fsurc = {
+ mode = "0400";
+ text = foldlAttrs (
+ acc: username: fid:
+ "${toString config.users.users.${username}.uid} ${toString fid}\n" + acc
+ ) "" cfg.users;
};
systemd.services.nix-daemon.unitConfig.RequiresMountsFor = [ "/etc/userdb" ];
@@ -114,8 +108,8 @@ in
confinement = {
app_id = aid;
inherit (app) groups;
- username = "u${toString fid}_a${toString aid}";
- home = "${cfg.stateDir}/u${toString fid}/a${toString aid}";
+ username = getsubname fid aid;
+ home = getsubhome fid aid;
sandbox = {
inherit (app)
userns
@@ -173,7 +167,9 @@ in
};
in
pkgs.writeShellScriptBin app.name ''
- exec fortify app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
+ exec fortify${
+ if app.verbose then " -v" else ""
+ } app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
''
) cfg.apps;
in
@@ -208,13 +204,57 @@ in
mergeAttrsList (
# aid 0 is reserved
imap1 (aid: app: {
- "u${toString fid}_a${toString aid}" = app.extraConfig // {
- home.packages = app.packages;
- };
+ ${getsubname fid aid} = mkMerge [
+ (cfg.home-manager (getsubname fid aid) (getsubuid fid aid))
+ app.extraConfig
+ { home.packages = app.packages; }
+ ];
}) cfg.apps
)
// acc
) privPackages cfg.users;
};
+
+ users =
+ let
+ getuser = fid: aid: {
+ isSystemUser = true;
+ createHome = true;
+ description = "Fortify subordinate user ${toString aid} (u${toString fid})";
+ group = getsubname fid aid;
+ home = getsubhome fid aid;
+ uid = getsubuid fid aid;
+ };
+ getgroup = fid: aid: { gid = getsubuid fid aid; };
+ in
+ {
+ users = foldlAttrs (
+ acc: _: fid:
+ mkMerge [
+ (mergeAttrsList (
+ # aid 0 is reserved
+ imap1 (aid: _: {
+ ${getsubname fid aid} = getuser fid aid;
+ }) cfg.apps
+ ))
+ { ${getsubname fid 0} = getuser fid 0; }
+ acc
+ ]
+ ) { } cfg.users;
+
+ groups = foldlAttrs (
+ acc: _: fid:
+ mkMerge [
+ (mergeAttrsList (
+ # aid 0 is reserved
+ imap1 (aid: _: {
+ ${getsubname fid aid} = getgroup fid aid;
+ }) cfg.apps
+ ))
+ { ${getsubname fid 0} = getgroup fid 0; }
+ acc
+ ]
+ ) { } cfg.users;
+ };
};
}
diff --git a/options.nix b/options.nix
index 616b24cf..578c7fd3 100644
--- a/options.nix
+++ b/options.nix
@@ -26,6 +26,17 @@ in
'';
};
+ home-manager = mkOption {
+ type =
+ let
+ inherit (types) functionTo attrsOf anything;
+ in
+ functionTo (functionTo (attrsOf anything));
+ description = ''
+ Target user shared home-manager configuration.
+ '';
+ };
+
apps = mkOption {
type =
let
@@ -50,6 +61,8 @@ in
'';
};
+ verbose = mkEnableOption "launchers with verbose output";
+
id = mkOption {
type = nullOr str;
default = null;
diff --git a/test.nix b/test.nix
index 47f7985d..e40ae726 100644
--- a/test.nix
+++ b/test.nix
@@ -44,7 +44,6 @@ nixosTest {
# For glinfo and wayland-info:
mesa-demos
wayland-utils
- alacritty
# For D-Bus tests:
libnotify
@@ -111,6 +110,43 @@ nixosTest {
enable = true;
stateDir = "/var/lib/fortify";
users.alice = 0;
+
+ home-manager = _: _: { home.stateVersion = "23.05"; };
+
+ apps = [
+ {
+ name = "ne-foot";
+ verbose = true;
+ share = pkgs.foot;
+ packages = [ pkgs.foot ];
+ command = "foot";
+ capability = {
+ dbus = false;
+ pulse = false;
+ };
+ }
+ {
+ name = "pa-foot";
+ verbose = true;
+ share = pkgs.foot;
+ packages = [ pkgs.foot ];
+ command = "foot";
+ capability.dbus = false;
+ }
+ {
+ name = "x11-alacritty";
+ verbose = true;
+ share = pkgs.alacritty;
+ packages = [ pkgs.alacritty ];
+ command = "alacritty";
+ capability = {
+ wayland = false;
+ x11 = true;
+ dbus = false;
+ pulse = false;
+ };
+ }
+ ];
};
imports = [
@@ -176,16 +212,18 @@ nixosTest {
machine.screenshot(name)
- def check_state(command, enablements):
+ def check_state(name, enablements):
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 fortify --json ps"))
if len(instances) != 1:
raise Exception(f"unexpected state length {len(instances)}")
instance = next(iter(instances.values()))
- if instance['config']['command'] != command:
+ config = instance['config']
+
+ if len(config['command']) != 1 or not(config['command'][0].startswith("/nix/store/")) or not(config['command'][0].endswith(f"{name}-start")):
raise Exception(f"unexpected command {instance['config']['command']}")
- if instance['config']['confinement']['enablements'] != enablements:
+ if config['confinement']['enablements'] != enablements:
raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")
@@ -212,60 +250,60 @@ nixosTest {
# Create fortify uid 0 state directory:
machine.succeed("install -dm 0755 -o u0_a0 -g users /var/lib/fortify/u0")
- # Start fortify outside Wayland session:
+ # Start fortify permissive defaults outside Wayland session:
print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare"))
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare")
- # Start fortify within Wayland session:
+ # Start fortify permissive defaults within Wayland session:
fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-done')
machine.wait_for_file("/tmp/dbus-done")
collect_state_ui("dbus_notify_exited")
machine.succeed("pkill -9 mako")
- # Start a terminal (foot) within fortify:
- fortify("run --wayland foot")
- wait_for_window("u0_a0@machine")
+ # Start app (foot) with Wayland enablement:
+ swaymsg("exec ne-foot")
+ wait_for_window("u0_a1@machine")
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
- machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client")
- collect_state_ui("foot_wayland_permissive")
- check_state(["foot"], 1)
+ machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client")
+ collect_state_ui("foot_wayland")
+ check_state("ne-foot", 1)
# Verify acl on XDG_RUNTIME_DIR:
- print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000000"))
+ print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001"))
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep foot")
# Verify acl cleanup on XDG_RUNTIME_DIR:
- machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000000")
+ machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001")
- # Start a terminal (foot) within fortify from a terminal:
- swaymsg("exec foot $SHELL -c '(fortify run --wayland foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'")
- wait_for_window("u0_a0@machine")
+ # Start app (foot) with Wayland enablement from a terminal:
+ swaymsg("exec foot $SHELL -c '(ne-foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'")
+ wait_for_window("u0_a1@machine")
machine.send_chars("clear; wayland-info && touch /tmp/success-client-term\n")
- machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client-term")
+ machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client-term")
machine.wait_for_file("/tmp/ps-show-ok")
- collect_state_ui("foot_wayland_permissive_term")
- check_state(["foot"], 1)
+ collect_state_ui("foot_wayland_term")
+ check_state("ne-foot", 1)
machine.send_chars("exit\n")
wait_for_window("foot")
machine.send_key("ctrl-c")
machine.wait_until_fails("pgrep foot")
# Test PulseAudio (fortify does not support PipeWire yet):
- fortify("run --wayland --pulse foot")
- wait_for_window("u0_a0@machine")
+ swaymsg("exec pa-foot")
+ wait_for_window("u0_a2@machine")
machine.send_chars("clear; pactl info && touch /tmp/success-pulse\n")
- machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-pulse")
+ machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-pulse")
collect_state_ui("pulse_wayland")
- check_state(["foot"], 9)
+ check_state("pa-foot", 9)
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep foot")
# Test XWayland (foot does not support X):
- fortify("run -X alacritty")
- wait_for_window("u0_a0@machine")
+ swaymsg("exec x11-alacritty")
+ wait_for_window("u0_a3@machine")
machine.send_chars("clear; glinfo && touch /tmp/success-client-x11\n")
- machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client-x11")
- collect_state_ui("alacritty_x11_permissive")
- check_state(["alacritty"], 2)
+ machine.wait_for_file("/tmp/fortify.1000/tmpdir/3/success-client-x11")
+ collect_state_ui("alacritty_x11")
+ check_state("x11-alacritty", 2)
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep alacritty")