internal: wrap calls to os standard library functions
All checks were successful
test / test (push) Successful in 19s
All checks were successful
test / test (push) Successful in 19s
This change helps tests stub out and simulate OS behaviour during the sealing process. This also removes dependency on XDG_RUNTIME_DIR as the internal.System implementation provided to App provides a compat directory inside the tmpdir-based share when XDG_RUNTIME_DIR is unavailable. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
@@ -3,6 +3,8 @@ package app
|
||||
import (
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"git.ophivana.moe/security/fortify/internal"
|
||||
)
|
||||
|
||||
type App interface {
|
||||
@@ -22,6 +24,8 @@ type App interface {
|
||||
type app struct {
|
||||
// application unique identifier
|
||||
id *ID
|
||||
// operating system interface
|
||||
os internal.System
|
||||
// underlying user switcher process
|
||||
cmd *exec.Cmd
|
||||
// shim setup abort reason and completion
|
||||
@@ -61,8 +65,9 @@ func (a *app) WaitErr() error {
|
||||
return a.waitErr
|
||||
}
|
||||
|
||||
func New() (App, error) {
|
||||
func New(os internal.System) (App, error) {
|
||||
a := new(app)
|
||||
a.id = new(ID)
|
||||
a.os = os
|
||||
return a, newAppID(a.id)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||
@@ -31,7 +30,7 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
|
||||
args = append(args, "--", ".host")
|
||||
|
||||
// /bin/sh -c
|
||||
if sh, err := exec.LookPath("sh"); err != nil {
|
||||
if sh, err := a.os.LookPath("sh"); err != nil {
|
||||
// hardcode /bin/sh path since it exists more often than not
|
||||
args = append(args, "/bin/sh", "-c")
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||
)
|
||||
|
||||
@@ -17,7 +15,7 @@ func (a *app) commandBuilderSudo(shimEnv string) (args []string) {
|
||||
args = append(args, "-Hiu", a.seal.sys.user.Username)
|
||||
|
||||
// -A?
|
||||
if _, ok := os.LookupEnv(sudoAskPass); ok {
|
||||
if _, ok := a.os.LookupEnv(sudoAskPass); ok {
|
||||
fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
|
||||
args = append(args, "-A")
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"io/fs"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -67,8 +66,7 @@ type appSeal struct {
|
||||
// seal system-level component
|
||||
sys *appSealSys
|
||||
|
||||
// used in various sealing operations
|
||||
internal.SystemConstants
|
||||
internal.Paths
|
||||
|
||||
// protected by upstream mutex
|
||||
}
|
||||
@@ -91,7 +89,7 @@ func (a *app) Seal(config *Config) error {
|
||||
seal := new(appSeal)
|
||||
|
||||
// fetch system constants
|
||||
seal.SystemConstants = internal.GetSC()
|
||||
seal.Paths = a.os.Paths()
|
||||
|
||||
// pass through config values
|
||||
seal.id = a.id.String()
|
||||
@@ -102,7 +100,7 @@ func (a *app) Seal(config *Config) error {
|
||||
switch config.Method {
|
||||
case method[LaunchMethodSudo]:
|
||||
seal.launchOption = LaunchMethodSudo
|
||||
if sudoPath, err := exec.LookPath("sudo"); err != nil {
|
||||
if sudoPath, err := a.os.LookPath("sudo"); err != nil {
|
||||
return fmsg.WrapError(ErrSudo,
|
||||
"sudo not found")
|
||||
} else {
|
||||
@@ -115,7 +113,7 @@ func (a *app) Seal(config *Config) error {
|
||||
"system has not been booted with systemd as init system")
|
||||
}
|
||||
|
||||
if machineCtlPath, err := exec.LookPath("machinectl"); err != nil {
|
||||
if machineCtlPath, err := a.os.LookPath("machinectl"); err != nil {
|
||||
return fmsg.WrapError(ErrMachineCtl,
|
||||
"machinectl not found")
|
||||
} else {
|
||||
@@ -130,14 +128,14 @@ func (a *app) Seal(config *Config) error {
|
||||
seal.sys = new(appSealSys)
|
||||
|
||||
// look up fortify executable path
|
||||
if p, err := os.Executable(); err != nil {
|
||||
if p, err := a.os.Executable(); err != nil {
|
||||
return fmsg.WrapErrorSuffix(err, "cannot look up fortify executable path:")
|
||||
} else {
|
||||
seal.sys.executable = p
|
||||
}
|
||||
|
||||
// look up user from system
|
||||
if u, err := user.Lookup(config.User); err != nil {
|
||||
if u, err := a.os.Lookup(config.User); err != nil {
|
||||
if errors.As(err, new(user.UnknownUserError)) {
|
||||
return fmsg.WrapError(ErrUser, "unknown user", config.User)
|
||||
} else {
|
||||
@@ -160,7 +158,7 @@ func (a *app) Seal(config *Config) error {
|
||||
NoNewSession: true,
|
||||
}
|
||||
// bind entries in /
|
||||
if d, err := os.ReadDir("/"); err != nil {
|
||||
if d, err := a.os.ReadDir("/"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
b := make([]*FilesystemConfig, 0, len(d))
|
||||
@@ -180,7 +178,7 @@ func (a *app) Seal(config *Config) error {
|
||||
conf.Filesystem = append(conf.Filesystem, b...)
|
||||
}
|
||||
// bind entries in /run
|
||||
if d, err := os.ReadDir("/run"); err != nil {
|
||||
if d, err := a.os.ReadDir("/run"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
b := make([]*FilesystemConfig, 0, len(d))
|
||||
@@ -198,7 +196,7 @@ func (a *app) Seal(config *Config) error {
|
||||
}
|
||||
// hide nscd from sandbox if present
|
||||
nscd := "/var/run/nscd"
|
||||
if _, err := os.Stat(nscd); !errors.Is(err, os.ErrNotExist) {
|
||||
if _, err := a.os.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
||||
conf.Override = append(conf.Override, nscd)
|
||||
}
|
||||
// bind GPU stuff
|
||||
@@ -222,7 +220,7 @@ func (a *app) Seal(config *Config) error {
|
||||
// open process state store
|
||||
// the simple store only starts holding an open file after first action
|
||||
// store activity begins after Start is called and must end before Wait
|
||||
seal.store = state.NewSimple(seal.SystemConstants.RunDirPath, seal.sys.user.Uid)
|
||||
seal.store = state.NewSimple(seal.RunDirPath, seal.sys.user.Uid)
|
||||
|
||||
// parse string UID
|
||||
if u, err := strconv.Atoi(seal.sys.user.Uid); err != nil {
|
||||
@@ -236,7 +234,7 @@ func (a *app) Seal(config *Config) error {
|
||||
seal.et = config.Confinement.Enablements
|
||||
|
||||
// this method calls all share methods in sequence
|
||||
if err := seal.shareAll([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}); err != nil {
|
||||
if err := seal.shareAll([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/security/fortify/acl"
|
||||
"git.ophivana.moe/security/fortify/internal"
|
||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||
"git.ophivana.moe/security/fortify/internal/system"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
ErrXDisplay = errors.New(display + " unset")
|
||||
)
|
||||
|
||||
func (seal *appSeal) shareDisplay() error {
|
||||
func (seal *appSeal) shareDisplay(os internal.System) error {
|
||||
// pass $TERM to launcher
|
||||
if t, ok := os.LookupEnv(term); ok {
|
||||
seal.sys.bwrap.SetEnv[term] = t
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/security/fortify/internal"
|
||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||
"git.ophivana.moe/security/fortify/internal/system"
|
||||
)
|
||||
@@ -25,7 +25,7 @@ var (
|
||||
ErrPulseMode = errors.New("unexpected pulse socket mode")
|
||||
)
|
||||
|
||||
func (seal *appSeal) sharePulse() error {
|
||||
func (seal *appSeal) sharePulse(os internal.System) error {
|
||||
if !seal.et.Has(system.EPulse) {
|
||||
return nil
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (seal *appSeal) sharePulse() error {
|
||||
seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p
|
||||
|
||||
// publish current user's pulse cookie for target user
|
||||
if src, err := discoverPulseCookie(); err != nil {
|
||||
if src, err := discoverPulseCookie(os); err != nil {
|
||||
return err
|
||||
} else {
|
||||
dst := path.Join(seal.share, "pulse-cookie")
|
||||
@@ -78,7 +78,7 @@ func (seal *appSeal) sharePulse() error {
|
||||
}
|
||||
|
||||
// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
|
||||
func discoverPulseCookie() (string, error) {
|
||||
func discoverPulseCookie(os internal.System) (string, error) {
|
||||
if p, ok := os.LookupEnv(pulseCookie); ok {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func (seal *appSeal) shareRuntime() {
|
||||
seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute)
|
||||
|
||||
// ensure runtime directory ACL (e.g. `/run/user/%d`)
|
||||
seal.sys.Ensure(seal.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
|
||||
seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute)
|
||||
|
||||
// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/security/fortify/acl"
|
||||
"git.ophivana.moe/security/fortify/internal"
|
||||
"git.ophivana.moe/security/fortify/internal/system"
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ func (seal *appSeal) shareSystem() {
|
||||
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
|
||||
}
|
||||
|
||||
func (seal *appSeal) sharePasswd() {
|
||||
func (seal *appSeal) sharePasswd(os internal.System) {
|
||||
// look up shell
|
||||
sh := "/bin/sh"
|
||||
if s, ok := os.LookupEnv(shell); ok {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"git.ophivana.moe/security/fortify/dbus"
|
||||
"git.ophivana.moe/security/fortify/helper/bwrap"
|
||||
"git.ophivana.moe/security/fortify/internal"
|
||||
"git.ophivana.moe/security/fortify/internal/system"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,7 @@ type appSealSys struct {
|
||||
}
|
||||
|
||||
// shareAll calls all share methods in sequence
|
||||
func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
|
||||
func (seal *appSeal) shareAll(bus [2]*dbus.Config, os internal.System) error {
|
||||
if seal.shared {
|
||||
panic("seal shared twice")
|
||||
}
|
||||
@@ -35,11 +36,11 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
|
||||
|
||||
seal.shareSystem()
|
||||
seal.shareRuntime()
|
||||
seal.sharePasswd()
|
||||
if err := seal.shareDisplay(); err != nil {
|
||||
seal.sharePasswd(os)
|
||||
if err := seal.shareDisplay(os); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seal.sharePulse(); err != nil {
|
||||
if err := seal.sharePulse(os); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user