Compare commits

..

10 Commits

Author SHA1 Message Date
Ophestra Umiker f831948bca
release: 0.1.0
release / release (push) Successful in 28s Details
test / test (push) Successful in 21s Details
This release significantly changes the command line interface, and updates the NixOS module to finally produce meaningful sandbox configuration.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:37:43 +09:00
Ophestra Umiker 2e31b3d3a1
update README document
test / test (push) Successful in 32s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:35:52 +09:00
Ophestra Umiker 4d90e73366
nix: generate strict sandbox configuration
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:25:15 +09:00
Ophestra Umiker 3dfc1fcd56
app: support full /dev access
test / test (push) Successful in 22s Details
Also moved /dev/fortify to /fortify since it is impossible to create new directories in /dev from the init namespace and bind mounting its contents has undesirable side effects.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 03:49:39 +09:00
Ophestra Umiker 89bafd0c22
fortify: root check before command handling
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 12:57:03 +09:00
Ophestra Umiker 861bb1274f
fortify: override default usage function
test / test (push) Successful in 23s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 00:12:31 +09:00
Ophestra Umiker 714818c8aa
fortify: implement cleaner argument structure
test / test (push) Successful in 24s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 00:07:36 +09:00
Ophestra Umiker 69cc64ef56
linux: provide access to stdout
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 22:55:46 +09:00
Ophestra Umiker fc25ac2523
app: separate auto etc from permissive defaults
test / test (push) Successful in 23s Details
Populating /etc with symlinks is quite useful even outside the permissive defaults usage pattern.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 22:18:05 +09:00
Ophestra Umiker d909b1190a
app/config: UseRealUID as true in template
test / test (push) Successful in 24s Details
The template is based on a Chromium setup, which this workaround was created for.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 19:45:31 +09:00
13 changed files with 670 additions and 524 deletions

View File

@ -2,6 +2,7 @@ Fortify
=======
[![Go Reference](https://pkg.go.dev/badge/git.ophivana.moe/security/fortify.svg)](https://pkg.go.dev/git.ophivana.moe/security/fortify)
[![Go Report Card](https://goreportcard.com/badge/git.ophivana.moe/security/fortify)](https://goreportcard.com/report/git.ophivana.moe/security/fortify)
Lets you run graphical applications as another user in a confined environment with a nice NixOS
module to configure target users and provide launchers and desktop files for your privileged user.
@ -77,40 +78,54 @@ This adds the `environment.fortify` option:
claws-mail.capability.pulse = false;
discord = {
id = "dev.vencord.Vesktop";
command = "vesktop --ozone-platform-hint=wayland";
userns = true;
useRealUid = true;
dbus = {
session =
f:
f {
talk = [ "org.kde.StatusNotifierWatcher" ];
own = [ ];
call = { };
broadcast = { };
};
system.filter = true;
};
share = pkgs.vesktop;
};
chromium.dbus = {
configSystem = {
filter = true;
talk = [
"org.bluez"
"org.freedesktop.Avahi"
"org.freedesktop.UPower"
];
};
config = {
filter = true;
talk = [
"org.freedesktop.DBus"
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = {
"org.freedesktop.portal.*" = "*";
chromium = {
id = "org.chromium.Chromium";
userns = true;
useRealUid = true;
dbus = {
system = {
filter = true;
talk = [
"org.bluez"
"org.freedesktop.Avahi"
"org.freedesktop.UPower"
];
};
broadcast = {
"org.freedesktop.portal.*" = "@/org/freedesktop/portal/*";
session = f: f {
talk = [
"org.freedesktop.DBus"
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = { };
broadcast = { };
};
};
};
@ -156,15 +171,29 @@ This adds the `environment.fortify` option:
The available options are:
* `id`, the freedesktop application ID, primarily used by dbus, null to disable.
* `command`, the command to run as the target user. Defaults to launcher name.
* `dbus.config`, D-Bus proxy custom configuration.
* `dbus.session`, D-Bus session proxy custom configuration.
* `dbus.configSystem`, D-Bus system bus custom configuration, null to disable.
* `dbus.configSystem`, D-Bus system proxy custom configuration, null to disable.
* `dbus.id`, D-Bus application id, has no effect if `dbus.config` is set.
* `env`, attrset of environment variables to set for the initial process in the sandbox.
* `dbus.mpris`, whether to enable MPRIS defaults, has no effect if `dbus.config` is set.
* `nix`, whether to allow nix daemon connections from within the sandbox.
* `userns`, whether to allow userns within the sandbox.
* `useRealUid`, whether to map to the real UID within the sandbox.
* `net`, whether to allow network access within the sandbox.
* `gpu`, target process GPU and driver access, null to follow Wayland or X capability.
* `dev`, whether to allow full device access within the sandbox.
* `extraPaths`, a list of extra paths to make available inside the sandbox.
* `capability.wayland`, whether to share the Wayland socket.
@ -176,4 +205,4 @@ This adds the `environment.fortify` option:
* `share`, package containing desktop/icon files. Defaults to launcher name.
* `method`, the launch method for the sandboxed program, can be `"fortify"`, `"fortify-sudo"`, `"sudo"`.
* `method`, the launch method for the sandboxed program, can be `"sudo"`, `"systemd"`, `"simple"`.

135
config.go
View File

@ -1,135 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
)
var (
printTemplate bool
confPath string
dbusConfigSession string
dbusConfigSystem string
dbusID string
mpris bool
dbusVerbose bool
userName string
enablements [system.ELen]bool
launchMethodText string
)
func init() {
flag.BoolVar(&printTemplate, "template", false, "Print a full config template and exit")
// config file, disables every other flag here
flag.StringVar(&confPath, "c", "nil", "Path to full app configuration, or \"nil\" to configure from flags")
flag.StringVar(&dbusConfigSession, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults")
flag.StringVar(&dbusConfigSystem, "dbus-system", "nil", "Path to system D-Bus proxy config file, or \"nil\" to disable")
flag.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available")
flag.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available")
flag.BoolVar(&dbusVerbose, "dbus-log", false, "Force logging in the D-Bus proxy")
flag.StringVar(&userName, "u", "chronos", "Passwd name of user to run as")
flag.BoolVar(&enablements[system.EWayland], "wayland", false, "Share Wayland socket")
flag.BoolVar(&enablements[system.EX11], "X", false, "Share X11 socket and allow connection")
flag.BoolVar(&enablements[system.EDBus], "dbus", false, "Proxy D-Bus connection")
flag.BoolVar(&enablements[system.EPulse], "pulse", false, "Share PulseAudio socket and cookie")
}
func init() {
methodHelpString := "Method of launching the child process, can be one of \"sudo\""
if os.SdBooted() {
methodHelpString += ", \"systemd\""
}
flag.StringVar(&launchMethodText, "method", "sudo", methodHelpString)
}
func tryTemplate() {
if printTemplate {
if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil {
fmsg.Fatalf("cannot generate template: %v", err)
panic("unreachable")
} else {
fmt.Println(string(s))
}
fmsg.Exit(0)
}
}
func loadConfig() *app.Config {
if confPath == "nil" {
// config from flags
return configFromFlags()
} else {
// config from file
c := new(app.Config)
if f, err := os.Open(confPath); err != nil {
fmsg.Fatalf("cannot access config file %q: %s", confPath, err)
panic("unreachable")
} else if err = json.NewDecoder(f).Decode(&c); err != nil {
fmsg.Fatalf("cannot parse config file %q: %s", confPath, err)
panic("unreachable")
} else {
return c
}
}
}
func configFromFlags() (config *app.Config) {
// initialise config from flags
config = &app.Config{
ID: dbusID,
User: userName,
Command: flag.Args(),
Method: launchMethodText,
}
// enablements from flags
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
if enablements[i] {
config.Confinement.Enablements.Set(i)
}
}
// parse D-Bus config file from flags if applicable
if enablements[system.EDBus] {
if dbusConfigSession == "builtin" {
config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
}
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}
}
// override log from configuration
if dbusVerbose {
config.Confinement.SessionBus.Log = true
config.Confinement.SystemBus.Log = true
}
}
return
}

View File

@ -2,6 +2,7 @@ package app_test
import (
"fmt"
"io"
"io/fs"
"os/user"
"strconv"
@ -54,11 +55,11 @@ var testCasesNixos = []sealTestCase{
DieWithParent: true,
AsInit: true,
}).SetUID(65534).SetGID(65534).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
Tmpfs("/dev/fortify", 4096).
Procfs("/proc").
Tmpfs("/fortify", 4096).
DevTmpfs("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", false, true).
Bind("/boot", "/boot", false, true).
Bind("/etc", "/dev/fortify/etc").
Bind("/home", "/home", false, true).
Bind("/lib", "/lib", false, true).
Bind("/lib64", "/lib64", false, true).
@ -102,84 +103,85 @@ var testCasesNixos = []sealTestCase{
Bind("/run/wrappers", "/run/wrappers", false, true).
Bind("/run/zed.pid", "/run/zed.pid", false, true).
Bind("/run/zed.state", "/run/zed.state", false, true).
Symlink("/dev/fortify/etc/alsa", "/etc/alsa").
Symlink("/dev/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/dev/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/dev/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/dev/fortify/etc/default", "/etc/default").
Symlink("/dev/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/dev/fortify/etc/fonts", "/etc/fonts").
Symlink("/dev/fortify/etc/fstab", "/etc/fstab").
Symlink("/dev/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/dev/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/dev/fortify/etc/hostid", "/etc/hostid").
Symlink("/dev/fortify/etc/hostname", "/etc/hostname").
Symlink("/dev/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/dev/fortify/etc/hosts", "/etc/hosts").
Symlink("/dev/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/dev/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/dev/fortify/etc/issue", "/etc/issue").
Symlink("/dev/fortify/etc/kbd", "/etc/kbd").
Symlink("/dev/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/dev/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/dev/fortify/etc/localtime", "/etc/localtime").
Symlink("/dev/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/dev/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/dev/fortify/etc/lvm", "/etc/lvm").
Symlink("/dev/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/dev/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/dev/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/dev/fortify/etc/modules-load.d", "/etc/modules-load.d").
Bind("/etc", "/fortify/etc").
Symlink("/fortify/etc/alsa", "/etc/alsa").
Symlink("/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/fortify/etc/default", "/etc/default").
Symlink("/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/fortify/etc/fonts", "/etc/fonts").
Symlink("/fortify/etc/fstab", "/etc/fstab").
Symlink("/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/fortify/etc/hostid", "/etc/hostid").
Symlink("/fortify/etc/hostname", "/etc/hostname").
Symlink("/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/fortify/etc/hosts", "/etc/hosts").
Symlink("/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/fortify/etc/issue", "/etc/issue").
Symlink("/fortify/etc/kbd", "/etc/kbd").
Symlink("/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/fortify/etc/localtime", "/etc/localtime").
Symlink("/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/fortify/etc/lvm", "/etc/lvm").
Symlink("/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/fortify/etc/modules-load.d", "/etc/modules-load.d").
Symlink("/proc/mounts", "/etc/mtab").
Symlink("/dev/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/dev/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/dev/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/dev/fortify/etc/nix", "/etc/nix").
Symlink("/dev/fortify/etc/nixos", "/etc/nixos").
Symlink("/dev/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/dev/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/dev/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/dev/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/dev/fortify/etc/os-release", "/etc/os-release").
Symlink("/dev/fortify/etc/pam", "/etc/pam").
Symlink("/dev/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/dev/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/dev/fortify/etc/pki", "/etc/pki").
Symlink("/dev/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/dev/fortify/etc/profile", "/etc/profile").
Symlink("/dev/fortify/etc/protocols", "/etc/protocols").
Symlink("/dev/fortify/etc/qemu", "/etc/qemu").
Symlink("/dev/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/dev/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/dev/fortify/etc/rpc", "/etc/rpc").
Symlink("/dev/fortify/etc/samba", "/etc/samba").
Symlink("/dev/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/dev/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/dev/fortify/etc/services", "/etc/services").
Symlink("/dev/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/dev/fortify/etc/shadow", "/etc/shadow").
Symlink("/dev/fortify/etc/shells", "/etc/shells").
Symlink("/dev/fortify/etc/ssh", "/etc/ssh").
Symlink("/dev/fortify/etc/ssl", "/etc/ssl").
Symlink("/dev/fortify/etc/static", "/etc/static").
Symlink("/dev/fortify/etc/subgid", "/etc/subgid").
Symlink("/dev/fortify/etc/subuid", "/etc/subuid").
Symlink("/dev/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/dev/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/dev/fortify/etc/systemd", "/etc/systemd").
Symlink("/dev/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/dev/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/dev/fortify/etc/udev", "/etc/udev").
Symlink("/dev/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/dev/fortify/etc/UPower", "/etc/UPower").
Symlink("/dev/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/dev/fortify/etc/X11", "/etc/X11").
Symlink("/dev/fortify/etc/zfs", "/etc/zfs").
Symlink("/dev/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/dev/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/dev/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/dev/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/dev/fortify/etc/zshrc", "/etc/zshrc").
Symlink("/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/fortify/etc/nix", "/etc/nix").
Symlink("/fortify/etc/nixos", "/etc/nixos").
Symlink("/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/fortify/etc/os-release", "/etc/os-release").
Symlink("/fortify/etc/pam", "/etc/pam").
Symlink("/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/fortify/etc/pki", "/etc/pki").
Symlink("/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/fortify/etc/profile", "/etc/profile").
Symlink("/fortify/etc/protocols", "/etc/protocols").
Symlink("/fortify/etc/qemu", "/etc/qemu").
Symlink("/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/fortify/etc/rpc", "/etc/rpc").
Symlink("/fortify/etc/samba", "/etc/samba").
Symlink("/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/fortify/etc/services", "/etc/services").
Symlink("/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/fortify/etc/shadow", "/etc/shadow").
Symlink("/fortify/etc/shells", "/etc/shells").
Symlink("/fortify/etc/ssh", "/etc/ssh").
Symlink("/fortify/etc/ssl", "/etc/ssl").
Symlink("/fortify/etc/static", "/etc/static").
Symlink("/fortify/etc/subgid", "/etc/subgid").
Symlink("/fortify/etc/subuid", "/etc/subuid").
Symlink("/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/fortify/etc/systemd", "/etc/systemd").
Symlink("/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/fortify/etc/udev", "/etc/udev").
Symlink("/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/fortify/etc/UPower", "/etc/UPower").
Symlink("/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/fortify/etc/X11", "/etc/X11").
Symlink("/fortify/etc/zfs", "/etc/zfs").
Symlink("/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/fortify/etc/zshrc", "/etc/zshrc").
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576).
@ -304,11 +306,11 @@ var testCasesNixos = []sealTestCase{
DieWithParent: true,
AsInit: true,
}).SetUID(65534).SetGID(65534).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
Tmpfs("/dev/fortify", 4096).
Procfs("/proc").
Tmpfs("/fortify", 4096).
DevTmpfs("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", false, true).
Bind("/boot", "/boot", false, true).
Bind("/etc", "/dev/fortify/etc").
Bind("/home", "/home", false, true).
Bind("/lib", "/lib", false, true).
Bind("/lib64", "/lib64", false, true).
@ -353,84 +355,85 @@ var testCasesNixos = []sealTestCase{
Bind("/run/zed.pid", "/run/zed.pid", false, true).
Bind("/run/zed.state", "/run/zed.state", false, true).
Bind("/dev/dri", "/dev/dri", true, true, true).
Symlink("/dev/fortify/etc/alsa", "/etc/alsa").
Symlink("/dev/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/dev/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/dev/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/dev/fortify/etc/default", "/etc/default").
Symlink("/dev/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/dev/fortify/etc/fonts", "/etc/fonts").
Symlink("/dev/fortify/etc/fstab", "/etc/fstab").
Symlink("/dev/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/dev/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/dev/fortify/etc/hostid", "/etc/hostid").
Symlink("/dev/fortify/etc/hostname", "/etc/hostname").
Symlink("/dev/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/dev/fortify/etc/hosts", "/etc/hosts").
Symlink("/dev/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/dev/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/dev/fortify/etc/issue", "/etc/issue").
Symlink("/dev/fortify/etc/kbd", "/etc/kbd").
Symlink("/dev/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/dev/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/dev/fortify/etc/localtime", "/etc/localtime").
Symlink("/dev/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/dev/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/dev/fortify/etc/lvm", "/etc/lvm").
Symlink("/dev/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/dev/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/dev/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/dev/fortify/etc/modules-load.d", "/etc/modules-load.d").
Bind("/etc", "/fortify/etc").
Symlink("/fortify/etc/alsa", "/etc/alsa").
Symlink("/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/fortify/etc/default", "/etc/default").
Symlink("/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/fortify/etc/fonts", "/etc/fonts").
Symlink("/fortify/etc/fstab", "/etc/fstab").
Symlink("/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/fortify/etc/hostid", "/etc/hostid").
Symlink("/fortify/etc/hostname", "/etc/hostname").
Symlink("/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/fortify/etc/hosts", "/etc/hosts").
Symlink("/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/fortify/etc/issue", "/etc/issue").
Symlink("/fortify/etc/kbd", "/etc/kbd").
Symlink("/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/fortify/etc/localtime", "/etc/localtime").
Symlink("/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/fortify/etc/lvm", "/etc/lvm").
Symlink("/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/fortify/etc/modules-load.d", "/etc/modules-load.d").
Symlink("/proc/mounts", "/etc/mtab").
Symlink("/dev/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/dev/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/dev/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/dev/fortify/etc/nix", "/etc/nix").
Symlink("/dev/fortify/etc/nixos", "/etc/nixos").
Symlink("/dev/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/dev/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/dev/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/dev/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/dev/fortify/etc/os-release", "/etc/os-release").
Symlink("/dev/fortify/etc/pam", "/etc/pam").
Symlink("/dev/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/dev/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/dev/fortify/etc/pki", "/etc/pki").
Symlink("/dev/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/dev/fortify/etc/profile", "/etc/profile").
Symlink("/dev/fortify/etc/protocols", "/etc/protocols").
Symlink("/dev/fortify/etc/qemu", "/etc/qemu").
Symlink("/dev/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/dev/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/dev/fortify/etc/rpc", "/etc/rpc").
Symlink("/dev/fortify/etc/samba", "/etc/samba").
Symlink("/dev/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/dev/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/dev/fortify/etc/services", "/etc/services").
Symlink("/dev/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/dev/fortify/etc/shadow", "/etc/shadow").
Symlink("/dev/fortify/etc/shells", "/etc/shells").
Symlink("/dev/fortify/etc/ssh", "/etc/ssh").
Symlink("/dev/fortify/etc/ssl", "/etc/ssl").
Symlink("/dev/fortify/etc/static", "/etc/static").
Symlink("/dev/fortify/etc/subgid", "/etc/subgid").
Symlink("/dev/fortify/etc/subuid", "/etc/subuid").
Symlink("/dev/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/dev/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/dev/fortify/etc/systemd", "/etc/systemd").
Symlink("/dev/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/dev/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/dev/fortify/etc/udev", "/etc/udev").
Symlink("/dev/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/dev/fortify/etc/UPower", "/etc/UPower").
Symlink("/dev/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/dev/fortify/etc/X11", "/etc/X11").
Symlink("/dev/fortify/etc/zfs", "/etc/zfs").
Symlink("/dev/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/dev/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/dev/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/dev/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/dev/fortify/etc/zshrc", "/etc/zshrc").
Symlink("/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/fortify/etc/nix", "/etc/nix").
Symlink("/fortify/etc/nixos", "/etc/nixos").
Symlink("/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/fortify/etc/os-release", "/etc/os-release").
Symlink("/fortify/etc/pam", "/etc/pam").
Symlink("/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/fortify/etc/pki", "/etc/pki").
Symlink("/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/fortify/etc/profile", "/etc/profile").
Symlink("/fortify/etc/protocols", "/etc/protocols").
Symlink("/fortify/etc/qemu", "/etc/qemu").
Symlink("/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/fortify/etc/rpc", "/etc/rpc").
Symlink("/fortify/etc/samba", "/etc/samba").
Symlink("/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/fortify/etc/services", "/etc/services").
Symlink("/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/fortify/etc/shadow", "/etc/shadow").
Symlink("/fortify/etc/shells", "/etc/shells").
Symlink("/fortify/etc/ssh", "/etc/ssh").
Symlink("/fortify/etc/ssl", "/etc/ssl").
Symlink("/fortify/etc/static", "/etc/static").
Symlink("/fortify/etc/subgid", "/etc/subgid").
Symlink("/fortify/etc/subuid", "/etc/subuid").
Symlink("/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/fortify/etc/systemd", "/etc/systemd").
Symlink("/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/fortify/etc/udev", "/etc/udev").
Symlink("/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/fortify/etc/UPower", "/etc/UPower").
Symlink("/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/fortify/etc/X11", "/etc/X11").
Symlink("/fortify/etc/zfs", "/etc/zfs").
Symlink("/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/fortify/etc/zshrc", "/etc/zshrc").
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576).
@ -579,6 +582,10 @@ func (s *stubNixOS) Exit(code int) {
panic("called exit on stub with code " + strconv.Itoa(code))
}
func (s *stubNixOS) Stdout() io.Writer {
panic("requested stdout")
}
func (s *stubNixOS) FshimPath() string {
return "/nix/store/00000000000000000000000000000000-fortify-0.0.10/bin/.fshim"
}

View File

@ -1,13 +1,16 @@
package app
import (
"os"
"errors"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap"
"git.ophivana.moe/security/fortify/internal/linux"
"git.ophivana.moe/security/fortify/internal/system"
)
const fTmp = "/fortify"
// Config is used to seal an *App
type Config struct {
// D-Bus application ID
@ -47,6 +50,8 @@ type SandboxConfig struct {
UserNS bool `json:"userns,omitempty"`
// share net namespace
Net bool `json:"net,omitempty"`
// share all devices
Dev bool `json:"dev,omitempty"`
// do not run in new session
NoNewSession bool `json:"no_new_session,omitempty"`
// map target user uid to privileged user uid in the user namespace
@ -60,6 +65,8 @@ type SandboxConfig struct {
Filesystem []*FilesystemConfig `json:"filesystem"`
// symlinks created inside the sandbox
Link [][2]string `json:"symlink"`
// automatically set up /etc symlinks
AutoEtc bool `json:"auto_etc"`
// paths to override by mounting tmpfs over them
Override []string `json:"override"`
}
@ -79,13 +86,16 @@ type FilesystemConfig struct {
// Bwrap returns the address of the corresponding bwrap.Config to s.
// Note that remaining tmpfs entries must be queued by the caller prior to launch.
func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
if s == nil {
return nil
return nil, errors.New("nil sandbox config")
}
var uid int
if !s.UseRealUID {
uid = 65534
} else {
uid = os.Geteuid()
}
conf := (&bwrap.Config{
@ -99,11 +109,21 @@ func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
AsInit: true,
// initialise map
Chmod: make(map[string]os.FileMode),
Chmod: make(bwrap.ChmodConfig),
}).
SetUID(uid).SetGID(uid).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
Tmpfs("/dev/fortify", 4*1024)
Procfs("/proc").
Tmpfs(fTmp, 4*1024)
if !s.Dev {
conf.DevTmpfs("/dev").Mqueue("/dev/mqueue")
} else {
conf.Bind("/dev", "/dev", false, true, true)
}
if !s.AutoEtc {
conf.Dir("/etc")
}
for _, c := range s.Filesystem {
if c == nil {
@ -121,7 +141,29 @@ func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
conf.Symlink(l[0], l[1])
}
return conf
if s.AutoEtc {
conf.Bind("/etc", fTmp+"/etc")
// link host /etc contents to prevent passwd/group from being overwritten
if d, err := os.ReadDir("/etc"); err != nil {
return nil, err
} else {
for _, ent := range d {
name := ent.Name()
switch name {
case "passwd":
case "group":
case "mtab":
conf.Symlink("/proc/mounts", "/etc/"+name)
default:
conf.Symlink(fTmp+"/etc/"+name, "/etc/"+name)
}
}
}
}
return conf, nil
}
// Template returns a fully populated instance of Config.
@ -143,6 +185,8 @@ func Template() *Config {
UserNS: true,
Net: true,
NoNewSession: true,
UseRealUID: true,
Dev: true,
Wayland: false,
// example API credentials pulled from Google Chrome
// DO NOT USE THESE IN A REAL BROWSER
@ -152,12 +196,15 @@ func Template() *Config {
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
},
Filesystem: []*FilesystemConfig{
{Src: "/nix"},
{Src: "/storage/emulated/0", Write: true, Must: true},
{Src: "/data/user/0", Dst: "/data/data", Write: true, Must: true},
{Src: "/var/tmp", Write: true},
{Src: "/nix/store"},
{Src: "/run/current-system"},
{Src: "/run/opengl-driver"},
{Src: "/var/db/nix-channels"},
{Src: "/home/chronos", Write: true, Must: true},
{Src: "/dev/dri", Device: true},
},
Link: [][2]string{{"/dev/fortify/etc", "/etc"}},
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
AutoEtc: true,
Override: []string{"/var/run/nscd"},
},
SystemBus: &dbus.Config{

View File

@ -159,6 +159,7 @@ func (a *app) Seal(config *Config) error {
UserNS: true,
Net: true,
NoNewSession: true,
AutoEtc: true,
}
// bind entries in /
if d, err := a.os.ReadDir("/"); err != nil {
@ -173,9 +174,8 @@ func (a *app) Seal(config *Config) error {
case "/run":
case "/tmp":
case "/mnt":
case "/etc":
b = append(b, &FilesystemConfig{Src: p, Dst: "/dev/fortify/etc", Write: false, Must: true})
default:
b = append(b, &FilesystemConfig{Src: p, Write: true, Must: true})
}
@ -208,35 +208,14 @@ func (a *app) Seal(config *Config) error {
if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) {
conf.Filesystem = append(conf.Filesystem, &FilesystemConfig{Src: "/dev/dri", Device: true})
}
// link host /etc to prevent passwd/group from being overwritten
if d, err := a.os.ReadDir("/etc"); err != nil {
return err
} else {
b := make([][2]string, 0, len(d))
for _, ent := range d {
name := ent.Name()
switch name {
case "passwd":
case "group":
case "mtab":
b = append(b, [2]string{
"/proc/mounts",
"/etc/" + name,
})
default:
b = append(b, [2]string{
"/dev/fortify/etc/" + name,
"/etc/" + name,
})
}
}
conf.Link = append(conf.Link, b...)
}
config.Confinement.Sandbox = conf
}
seal.sys.bwrap = config.Confinement.Sandbox.Bwrap(a.os.Geteuid())
if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil {
return err
} else {
seal.sys.bwrap = b
}
seal.sys.override = config.Confinement.Sandbox.Override
if seal.sys.bwrap.SetEnv == nil {
seal.sys.bwrap.SetEnv = make(map[string]string)

View File

@ -1,6 +1,7 @@
package linux
import (
"io"
"io/fs"
"os/user"
"path"
@ -31,6 +32,8 @@ type System interface {
Open(name string) (fs.File, error)
// Exit provides [os.Exit].
Exit(code int)
// Stdout provides [os.Stdout].
Stdout() io.Writer
// FshimPath returns an absolute path to the fshim binary.
FshimPath() string

View File

@ -2,6 +2,7 @@ package linux
import (
"errors"
"io"
"io/fs"
"os"
"os/exec"
@ -34,6 +35,7 @@ func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(na
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
func (s *Std) Exit(code int) { fmsg.Exit(code) }
func (s *Std) Stdout() io.Writer { return os.Stdout }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"

View File

@ -1,25 +0,0 @@
package main
import (
_ "embed"
"flag"
"fmt"
)
var (
//go:embed LICENSE
license string
printLicense bool
)
func init() {
flag.BoolVar(&printLicense, "license", false, "Print license")
}
func tryLicense() {
if printLicense {
fmt.Println(license)
os.Exit(0)
}
}

205
main.go
View File

@ -1,16 +1,26 @@
package main
import (
_ "embed"
"encoding/json"
"flag"
"fmt"
"text/tabwriter"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/linux"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
)
var (
flagVerbose bool
//go:embed LICENSE
license string
)
func init() {
@ -25,32 +35,194 @@ func main() {
// not fatal: this program runs as the privileged user
}
flag.Parse()
fmsg.SetVerbose(flagVerbose)
if os.SdBooted() {
fmsg.VPrintln("system booted with systemd as init system")
}
// root check
if os.Geteuid() == 0 {
fmsg.Fatal("this program must not run as root")
panic("unreachable")
}
// version/license/template command early exit
tryVersion()
tryLicense()
tryTemplate()
flag.CommandLine.Usage = func() {
fmt.Println()
fmt.Println("Usage:\tfortify [-v] COMMAND [OPTIONS]")
fmt.Println()
fmt.Println("Commands:")
w := tabwriter.NewWriter(os.Stdout(), 0, 1, 4, ' ', 0)
commands := [][2]string{
{"app", "Launch app defined by the specified config file"},
{"run", "Configure and start a permissive default sandbox"},
{"ps", "List active apps and their state"},
{"version", "Show fortify version"},
{"license", "Show full license text"},
{"template", "Produce a config template"},
{"help", "Show this help message"},
}
for _, c := range commands {
_, _ = fmt.Fprintf(w, "\t%s\t%s\n", c[0], c[1])
}
if err := w.Flush(); err != nil {
fmt.Printf("fortify: cannot write command list: %v\n", err)
}
fmt.Println()
}
flag.Parse()
fmsg.SetVerbose(flagVerbose)
// state query command early exit
tryState()
args := flag.Args()
if len(args) == 0 {
flag.CommandLine.Usage()
fmsg.Exit(0)
}
switch args[0] {
case "version": // print version string
if v, ok := internal.Check(internal.Version); ok {
fmt.Println(v)
} else {
fmt.Println("impure")
}
fmsg.Exit(0)
case "license": // print embedded license
fmt.Println(license)
fmsg.Exit(0)
case "template": // print full template configuration
if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil {
fmsg.Fatalf("cannot generate template: %v", err)
panic("unreachable")
} else {
fmt.Println(string(s))
}
fmsg.Exit(0)
case "help": // print help message
flag.CommandLine.Usage()
fmsg.Exit(0)
case "ps": // print all state info
var w *tabwriter.Writer
state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath)
if w != nil {
if err := w.Flush(); err != nil {
fmsg.Println("cannot format output:", err)
}
} else {
fmt.Println("No information available")
}
fmsg.Exit(0)
case "app": // launch app from configuration file
if len(args) < 2 {
fmsg.Fatal("app requires at least 1 argument")
}
config := new(app.Config)
if f, err := os.Open(args[1]); err != nil {
fmsg.Fatalf("cannot access config file %q: %s", args[1], err)
panic("unreachable")
} else if err = json.NewDecoder(f).Decode(&config); err != nil {
fmsg.Fatalf("cannot parse config file %q: %s", args[1], err)
panic("unreachable")
}
// append extra args
config.Command = append(config.Command, args[2:]...)
// invoke app
runApp(config)
case "run": // run app in permissive defaults usage pattern
set := flag.NewFlagSet("run", flag.ExitOnError)
var (
dbusConfigSession string
dbusConfigSystem string
dbusID string
mpris bool
dbusVerbose bool
userName string
enablements [system.ELen]bool
launchMethodText string
)
set.StringVar(&dbusConfigSession, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults")
set.StringVar(&dbusConfigSystem, "dbus-system", "nil", "Path to system D-Bus proxy config file, or \"nil\" to disable")
set.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available")
set.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available")
set.BoolVar(&dbusVerbose, "dbus-log", false, "Force logging in the D-Bus proxy")
set.StringVar(&userName, "u", "chronos", "Passwd name of user to run as")
set.BoolVar(&enablements[system.EWayland], "wayland", false, "Share Wayland socket")
set.BoolVar(&enablements[system.EX11], "X", false, "Share X11 socket and allow connection")
set.BoolVar(&enablements[system.EDBus], "dbus", false, "Proxy D-Bus connection")
set.BoolVar(&enablements[system.EPulse], "pulse", false, "Share PulseAudio socket and cookie")
methodHelpString := "Method of launching the child process, can be one of \"sudo\""
if os.SdBooted() {
methodHelpString += ", \"systemd\""
}
set.StringVar(&launchMethodText, "method", "sudo", methodHelpString)
// Ignore errors; set is set for ExitOnError.
_ = set.Parse(args[1:])
// initialise config from flags
config := &app.Config{
ID: dbusID,
User: userName,
Command: set.Args(),
Method: launchMethodText,
}
// enablements from flags
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
if enablements[i] {
config.Confinement.Enablements.Set(i)
}
}
// parse D-Bus config file from flags if applicable
if enablements[system.EDBus] {
if dbusConfigSession == "builtin" {
config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
}
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}
}
// override log from configuration
if dbusVerbose {
config.Confinement.SessionBus.Log = true
config.Confinement.SystemBus.Log = true
}
}
// invoke app
runApp(config)
default:
fmsg.Fatalf("%q is not a valid command", args[0])
}
panic("unreachable")
}
func runApp(config *app.Config) {
if os.SdBooted() {
fmsg.VPrintln("system booted with systemd as init system")
}
// invoke app
a, err := app.New(os)
if err != nil {
fmsg.Fatalf("cannot create app: %s\n", err)
} else if err = a.Seal(loadConfig()); err != nil {
} else if err = a.Seal(config); err != nil {
logBaseError(err, "cannot seal app:")
fmsg.Exit(1)
} else if err = a.Start(); err != nil {
@ -69,4 +241,5 @@ func main() {
fmsg.Println("inner wait failed:", err)
}
fmsg.Exit(r)
panic("unreachable")
}

230
nixos.nix
View File

@ -15,6 +15,7 @@ let
mapAttrsToList
foldlAttrs
optional
optionals
;
cfg = config.environment.fortify;
@ -39,6 +40,7 @@ in
listOf
attrsOf
nullOr
functionTo
;
in
attrsOf (submodule {
@ -54,6 +56,14 @@ in
launchers = mkOption {
type = attrsOf (submodule {
options = {
id = mkOption {
type = nullOr str;
default = null;
description = ''
Freedesktop application ID.
'';
};
command = mkOption {
type = nullOr str;
default = null;
@ -63,17 +73,29 @@ in
'';
};
method = mkOption {
type = enum [
"simple"
"sudo"
"systemd"
];
default = "systemd";
description = ''
Launch method for the sandboxed program.
'';
};
dbus = {
config = mkOption {
type = nullOr anything;
session = mkOption {
type = nullOr (functionTo anything);
default = null;
description = ''
D-Bus custom configuration.
D-Bus session bus custom configuration.
Setting this to null will enable built-in defaults.
'';
};
configSystem = mkOption {
system = mkOption {
type = nullOr anything;
default = null;
description = ''
@ -81,24 +103,55 @@ in
Setting this to null will disable the system bus proxy.
'';
};
};
id = mkOption {
type = nullOr str;
default = null;
description = ''
D-Bus application id.
Setting this to null will disable own path in defaults.
Has no effect if custom configuration is set.
'';
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.
'';
useRealUid = mkEnableOption ''
Whether to map to fortify's real UID within the sandbox.
'';
net =
mkEnableOption ''
Whether to allow network access within sandbox.
''
// {
default = true;
};
mpris = mkOption {
type = bool;
default = false;
description = ''
Whether to enable MPRIS in D-Bus defaults.
'';
};
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 = {
@ -143,18 +196,6 @@ in
Setting this to null will default package name to wrapper name.
'';
};
method = mkOption {
type = enum [
"simple"
"sudo"
"systemd"
];
default = "systemd";
description = ''
Launch method for the sandboxed program.
'';
};
};
});
default = { };
@ -222,26 +263,113 @@ in
name: launcher:
with launcher.capability;
let
command = if launcher.command == null then name else launcher.command;
extendDBusDefault = id: ext: {
filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own =
(optionals (launcher.id != null) [
"${id}.*"
"org.mpris.MediaPlayer2.${id}.*"
])
++ ext.own;
call = {
"org.freedesktop.portal.*" = "*";
} // ext.call;
broadcast = {
"org.freedesktop.portal.*" = "@/org/freedesktop/portal/*";
} // ext.broadcast;
};
dbusConfig =
if launcher.dbus.config != null then
pkgs.writeText "${name}-dbus.json" (builtins.toJSON launcher.dbus.config)
else
null;
dbusSystem =
if launcher.dbus.configSystem != null then
pkgs.writeText "${name}-dbus-system.json" (builtins.toJSON launcher.dbus.configSystem)
else
null;
capArgs =
(if wayland then " --wayland" else "")
+ (if x11 then " -X" else "")
+ (if dbus then " --dbus" else "")
+ (if pulse then " --pulse" else "")
+ (if launcher.dbus.mpris then " --mpris" else "")
+ (if launcher.dbus.id != null then " --dbus-id ${launcher.dbus.id}" else "")
+ (if dbusConfig != null then " --dbus-config ${dbusConfig}" else "")
+ (if dbusSystem != null then " --dbus-system ${dbusSystem}" else "");
let
default = {
talk = [ ];
own = [ ];
call = { };
broadcast = { };
};
in
{
session_bus =
if launcher.dbus.session != null then
(launcher.dbus.session (extendDBusDefault launcher.id))
else
(extendDBusDefault launcher.id default);
system_bus = launcher.dbus.system;
};
command = if launcher.command == null then name else launcher.command;
enablements =
(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 (launcher) id method;
inherit user;
command = [
"/run/current-system/sw/bin/zsh"
(pkgs.writeShellScript "${name}-start" ("exec " + command + " $@"))
];
confinement = {
sandbox = {
inherit (launcher)
userns
net
dev
env
;
use_real_uid = launcher.useRealUid;
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;
}
{
src = "/home/${user}";
write = true;
require = true;
}
]
++ optionals launcher.nix [
{ src = "/nix/var"; }
{ src = "/var/db/nix-channels"; }
]
++ optionals (if launcher.gpu != null then launcher.gpu else wayland || x11) [
{ src = "/run/opengl-driver"; }
{
src = "/dev/dri";
dev = true;
}
]
++ launcher.extraPaths;
auto_etc = true;
override = [ "/var/run/nscd" ];
};
inherit enablements;
inherit (dbusConfig) session_bus system_bus;
};
};
in
pkgs.writeShellScriptBin name (
if launcher.method == "simple" then
@ -250,7 +378,7 @@ in
''
else
''
exec fortify${capArgs} --method ${launcher.method} -u ${user} $SHELL -c "exec ${command} $@"
exec fortify app ${pkgs.writeText "fortify-${name}.json" (builtins.toJSON conf)} $@
''
)
) launchers;

View File

@ -10,7 +10,7 @@
buildGoModule rec {
pname = "fortify";
version = "0.0.11";
version = "0.1.0";
src = ./.;
vendorHash = null;

View File

@ -1,35 +0,0 @@
package main
import (
"flag"
"fmt"
"text/tabwriter"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/state"
)
var (
stateActionEarly bool
)
func init() {
flag.BoolVar(&stateActionEarly, "state", false, "print state information of active launchers")
}
// tryState is called after app initialisation
func tryState() {
if stateActionEarly {
var w *tabwriter.Writer
state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath)
if w != nil {
if err := w.Flush(); err != nil {
fmsg.Println("cannot format output:", err)
}
} else {
fmt.Println("No information available")
}
fmsg.Exit(0)
}
}

View File

@ -1,27 +0,0 @@
package main
import (
"flag"
"fmt"
"git.ophivana.moe/security/fortify/internal"
)
var (
printVersion bool
)
func init() {
flag.BoolVar(&printVersion, "V", false, "Print version")
}
func tryVersion() {
if printVersion {
if v, ok := internal.Check(internal.Version); ok {
fmt.Println(v)
} else {
fmt.Println("impure")
}
os.Exit(0)
}
}