Compare commits
No commits in common. "f831948bcab3ee8f9848ef14ea4062cfef6341c2" and "cfd05b10f17388fcc088c28e4a5fdd70a6182b44" have entirely different histories.
f831948bca
...
cfd05b10f1
57
README.md
57
README.md
|
@ -2,7 +2,6 @@ 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.
|
||||
|
@ -78,30 +77,12 @@ 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 = {
|
||||
id = "org.chromium.Chromium";
|
||||
userns = true;
|
||||
useRealUid = true;
|
||||
dbus = {
|
||||
system = {
|
||||
chromium.dbus = {
|
||||
configSystem = {
|
||||
filter = true;
|
||||
talk = [
|
||||
"org.bluez"
|
||||
|
@ -109,7 +90,8 @@ This adds the `environment.fortify` option:
|
|||
"org.freedesktop.UPower"
|
||||
];
|
||||
};
|
||||
session = f: f {
|
||||
config = {
|
||||
filter = true;
|
||||
talk = [
|
||||
"org.freedesktop.DBus"
|
||||
"org.freedesktop.FileManager1"
|
||||
|
@ -124,8 +106,11 @@ This adds the `environment.fortify` option:
|
|||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
|
||||
"org.mpris.MediaPlayer2.chromium.*"
|
||||
];
|
||||
call = { };
|
||||
broadcast = { };
|
||||
call = {
|
||||
"org.freedesktop.portal.*" = "*";
|
||||
};
|
||||
broadcast = {
|
||||
"org.freedesktop.portal.*" = "@/org/freedesktop/portal/*";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -171,29 +156,15 @@ 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.session`, D-Bus session proxy custom configuration.
|
||||
* `dbus.config`, D-Bus proxy custom configuration.
|
||||
|
||||
* `dbus.configSystem`, D-Bus system proxy custom configuration, null to disable.
|
||||
* `dbus.configSystem`, D-Bus system bus custom configuration, null to disable.
|
||||
|
||||
* `env`, attrset of environment variables to set for the initial process in the sandbox.
|
||||
* `dbus.id`, D-Bus application id, 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.
|
||||
* `dbus.mpris`, whether to enable MPRIS defaults, has no effect if `dbus.config` is set.
|
||||
|
||||
* `capability.wayland`, whether to share the Wayland socket.
|
||||
|
||||
|
@ -205,4 +176,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 `"sudo"`, `"systemd"`, `"simple"`.
|
||||
* `method`, the launch method for the sandboxed program, can be `"fortify"`, `"fortify-sudo"`, `"sudo"`.
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
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
|
||||
}
|
|
@ -2,7 +2,6 @@ package app_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
@ -55,11 +54,11 @@ var testCasesNixos = []sealTestCase{
|
|||
DieWithParent: true,
|
||||
AsInit: true,
|
||||
}).SetUID(65534).SetGID(65534).
|
||||
Procfs("/proc").
|
||||
Tmpfs("/fortify", 4096).
|
||||
DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Tmpfs("/dev/fortify", 4096).
|
||||
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).
|
||||
|
@ -103,85 +102,84 @@ 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).
|
||||
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("/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").
|
||||
Symlink("/proc/mounts", "/etc/mtab").
|
||||
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").
|
||||
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").
|
||||
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
|
||||
Tmpfs("/tmp/fortify.1971", 1048576).
|
||||
Tmpfs("/run/user", 1048576).
|
||||
|
@ -306,11 +304,11 @@ var testCasesNixos = []sealTestCase{
|
|||
DieWithParent: true,
|
||||
AsInit: true,
|
||||
}).SetUID(65534).SetGID(65534).
|
||||
Procfs("/proc").
|
||||
Tmpfs("/fortify", 4096).
|
||||
DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Tmpfs("/dev/fortify", 4096).
|
||||
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).
|
||||
|
@ -355,85 +353,84 @@ 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).
|
||||
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("/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").
|
||||
Symlink("/proc/mounts", "/etc/mtab").
|
||||
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").
|
||||
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").
|
||||
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
|
||||
Tmpfs("/tmp/fortify.1971", 1048576).
|
||||
Tmpfs("/run/user", 1048576).
|
||||
|
@ -582,10 +579,6 @@ 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"
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"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
|
||||
|
@ -50,8 +47,6 @@ 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
|
||||
|
@ -65,8 +60,6 @@ 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"`
|
||||
}
|
||||
|
@ -86,16 +79,13 @@ 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(os linux.System) (*bwrap.Config, error) {
|
||||
func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
|
||||
if s == nil {
|
||||
return nil, errors.New("nil sandbox config")
|
||||
return nil
|
||||
}
|
||||
|
||||
var uid int
|
||||
if !s.UseRealUID {
|
||||
uid = 65534
|
||||
} else {
|
||||
uid = os.Geteuid()
|
||||
}
|
||||
|
||||
conf := (&bwrap.Config{
|
||||
|
@ -109,21 +99,11 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||
AsInit: true,
|
||||
|
||||
// initialise map
|
||||
Chmod: make(bwrap.ChmodConfig),
|
||||
Chmod: make(map[string]os.FileMode),
|
||||
}).
|
||||
SetUID(uid).SetGID(uid).
|
||||
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")
|
||||
}
|
||||
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Tmpfs("/dev/fortify", 4*1024)
|
||||
|
||||
for _, c := range s.Filesystem {
|
||||
if c == nil {
|
||||
|
@ -141,29 +121,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||
conf.Symlink(l[0], l[1])
|
||||
}
|
||||
|
||||
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
|
||||
return conf
|
||||
}
|
||||
|
||||
// Template returns a fully populated instance of Config.
|
||||
|
@ -185,8 +143,6 @@ 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
|
||||
|
@ -196,15 +152,12 @@ func Template() *Config {
|
|||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||
},
|
||||
Filesystem: []*FilesystemConfig{
|
||||
{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},
|
||||
{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},
|
||||
},
|
||||
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
|
||||
AutoEtc: true,
|
||||
Link: [][2]string{{"/dev/fortify/etc", "/etc"}},
|
||||
Override: []string{"/var/run/nscd"},
|
||||
},
|
||||
SystemBus: &dbus.Config{
|
||||
|
|
|
@ -159,7 +159,6 @@ 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 {
|
||||
|
@ -174,8 +173,9 @@ func (a *app) Seal(config *Config) error {
|
|||
case "/run":
|
||||
case "/tmp":
|
||||
case "/mnt":
|
||||
case "/etc":
|
||||
|
||||
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,14 +208,35 @@ 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
|
||||
}
|
||||
if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil {
|
||||
return err
|
||||
} else {
|
||||
seal.sys.bwrap = b
|
||||
}
|
||||
seal.sys.bwrap = config.Confinement.Sandbox.Bwrap(a.os.Geteuid())
|
||||
seal.sys.override = config.Confinement.Sandbox.Override
|
||||
if seal.sys.bwrap.SetEnv == nil {
|
||||
seal.sys.bwrap.SetEnv = make(map[string]string)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package linux
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os/user"
|
||||
"path"
|
||||
|
@ -32,8 +31,6 @@ 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
|
||||
|
|
|
@ -2,7 +2,6 @@ package linux
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -35,7 +34,6 @@ 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"
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
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)
|
||||
}
|
||||
}
|
203
main.go
203
main.go
|
@ -1,26 +1,16 @@
|
|||
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() {
|
||||
|
@ -35,194 +25,32 @@ 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")
|
||||
}
|
||||
|
||||
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)
|
||||
// version/license/template command early exit
|
||||
tryVersion()
|
||||
tryLicense()
|
||||
tryTemplate()
|
||||
|
||||
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:]...)
|
||||
// state query command early exit
|
||||
tryState()
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
a, err := app.New(os)
|
||||
if err != nil {
|
||||
fmsg.Fatalf("cannot create app: %s\n", err)
|
||||
} else if err = a.Seal(config); err != nil {
|
||||
} else if err = a.Seal(loadConfig()); err != nil {
|
||||
logBaseError(err, "cannot seal app:")
|
||||
fmsg.Exit(1)
|
||||
} else if err = a.Start(); err != nil {
|
||||
|
@ -241,5 +69,4 @@ func runApp(config *app.Config) {
|
|||
fmsg.Println("inner wait failed:", err)
|
||||
}
|
||||
fmsg.Exit(r)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
|
218
nixos.nix
218
nixos.nix
|
@ -15,7 +15,6 @@ let
|
|||
mapAttrsToList
|
||||
foldlAttrs
|
||||
optional
|
||||
optionals
|
||||
;
|
||||
|
||||
cfg = config.environment.fortify;
|
||||
|
@ -40,7 +39,6 @@ in
|
|||
listOf
|
||||
attrsOf
|
||||
nullOr
|
||||
functionTo
|
||||
;
|
||||
in
|
||||
attrsOf (submodule {
|
||||
|
@ -56,14 +54,6 @@ 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;
|
||||
|
@ -73,29 +63,17 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
method = mkOption {
|
||||
type = enum [
|
||||
"simple"
|
||||
"sudo"
|
||||
"systemd"
|
||||
];
|
||||
default = "systemd";
|
||||
description = ''
|
||||
Launch method for the sandboxed program.
|
||||
'';
|
||||
};
|
||||
|
||||
dbus = {
|
||||
session = mkOption {
|
||||
type = nullOr (functionTo anything);
|
||||
config = mkOption {
|
||||
type = nullOr anything;
|
||||
default = null;
|
||||
description = ''
|
||||
D-Bus session bus custom configuration.
|
||||
D-Bus custom configuration.
|
||||
Setting this to null will enable built-in defaults.
|
||||
'';
|
||||
};
|
||||
|
||||
system = mkOption {
|
||||
configSystem = mkOption {
|
||||
type = nullOr anything;
|
||||
default = null;
|
||||
description = ''
|
||||
|
@ -103,55 +81,24 @@ in
|
|||
Setting this to null will disable the system bus proxy.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
env = mkOption {
|
||||
type = nullOr (attrsOf str);
|
||||
id = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Environment variables to set for the initial process in the sandbox.
|
||||
D-Bus application id.
|
||||
Setting this to null will disable own path in defaults.
|
||||
Has no effect if custom configuration is set.
|
||||
'';
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
gpu = mkOption {
|
||||
type = nullOr bool;
|
||||
default = null;
|
||||
mpris = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Target process GPU and driver access.
|
||||
Setting this to null will enable GPU whenever X or Wayland is enabled.
|
||||
Whether to enable MPRIS in D-Bus defaults.
|
||||
'';
|
||||
};
|
||||
|
||||
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 = {
|
||||
|
@ -196,6 +143,18 @@ 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 = { };
|
||||
|
@ -263,113 +222,26 @@ in
|
|||
name: launcher:
|
||||
with launcher.capability;
|
||||
let
|
||||
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 =
|
||||
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;
|
||||
};
|
||||
};
|
||||
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 "");
|
||||
in
|
||||
pkgs.writeShellScriptBin name (
|
||||
if launcher.method == "simple" then
|
||||
|
@ -378,7 +250,7 @@ in
|
|||
''
|
||||
else
|
||||
''
|
||||
exec fortify app ${pkgs.writeText "fortify-${name}.json" (builtins.toJSON conf)} $@
|
||||
exec fortify${capArgs} --method ${launcher.method} -u ${user} $SHELL -c "exec ${command} $@"
|
||||
''
|
||||
)
|
||||
) launchers;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
buildGoModule rec {
|
||||
pname = "fortify";
|
||||
version = "0.1.0";
|
||||
version = "0.0.11";
|
||||
|
||||
src = ./.;
|
||||
vendorHash = null;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue