From 6a2802cf309fb9758756c56ef05c9354d9cb9003 Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Mon, 7 Oct 2024 14:40:35 +0900 Subject: helper: move bwrap into helper Signed-off-by: Ophestra Umiker --- bwrap/config.bool.go | 64 ---------- bwrap/config.go | 265 --------------------------------------- bwrap/config.int.go | 22 ---- bwrap/config.interface.go | 34 ----- bwrap/config.pair.go | 54 -------- bwrap/config.string.go | 35 ------ bwrap/config_test.go | 88 ------------- helper/bwrap/config.bool.go | 64 ++++++++++ helper/bwrap/config.go | 265 +++++++++++++++++++++++++++++++++++++++ helper/bwrap/config.int.go | 22 ++++ helper/bwrap/config.interface.go | 34 +++++ helper/bwrap/config.pair.go | 54 ++++++++ helper/bwrap/config.string.go | 35 ++++++ helper/bwrap/config_test.go | 88 +++++++++++++ 14 files changed, 562 insertions(+), 562 deletions(-) delete mode 100644 bwrap/config.bool.go delete mode 100644 bwrap/config.go delete mode 100644 bwrap/config.int.go delete mode 100644 bwrap/config.interface.go delete mode 100644 bwrap/config.pair.go delete mode 100644 bwrap/config.string.go delete mode 100644 bwrap/config_test.go create mode 100644 helper/bwrap/config.bool.go create mode 100644 helper/bwrap/config.go create mode 100644 helper/bwrap/config.int.go create mode 100644 helper/bwrap/config.interface.go create mode 100644 helper/bwrap/config.pair.go create mode 100644 helper/bwrap/config.string.go create mode 100644 helper/bwrap/config_test.go diff --git a/bwrap/config.bool.go b/bwrap/config.bool.go deleted file mode 100644 index 81e6ab7b..00000000 --- a/bwrap/config.bool.go +++ /dev/null @@ -1,64 +0,0 @@ -package bwrap - -const ( - UnshareAll = iota - UnshareUser - UnshareIPC - UnsharePID - UnshareNet - UnshareUTS - UnshareCGroup - ShareNet - - UserNS - Clearenv - - NewSession - DieWithParent - AsInit - - boolC -) - -var boolArgs = func() (b [boolC][]string) { - b[UnshareAll] = []string{"--unshare-all"} - b[UnshareUser] = []string{"--unshare-user"} - b[UnshareIPC] = []string{"--unshare-ipc"} - b[UnsharePID] = []string{"--unshare-pid"} - b[UnshareNet] = []string{"--unshare-net"} - b[UnshareUTS] = []string{"--unshare-uts"} - b[UnshareCGroup] = []string{"--unshare-cgroup"} - b[ShareNet] = []string{"--share-net"} - - b[UserNS] = []string{"--disable-userns", "--assert-userns-disabled"} - b[Clearenv] = []string{"--clearenv"} - - b[NewSession] = []string{"--new-session"} - b[DieWithParent] = []string{"--die-with-parent"} - b[AsInit] = []string{"--as-pid-1"} - - return -}() - -func (c *Config) boolArgs() (b [boolC]bool) { - if c.Unshare == nil { - b[UnshareAll] = true - b[ShareNet] = c.Net - } else { - b[UnshareUser] = c.Unshare.User - b[UnshareIPC] = c.Unshare.IPC - b[UnsharePID] = c.Unshare.PID - b[UnshareNet] = c.Unshare.Net - b[UnshareUTS] = c.Unshare.UTS - b[UnshareCGroup] = c.Unshare.CGroup - } - - b[UserNS] = !c.UserNS - b[Clearenv] = c.Clearenv - - b[NewSession] = c.NewSession - b[DieWithParent] = c.DieWithParent - b[AsInit] = c.AsInit - - return -} diff --git a/bwrap/config.go b/bwrap/config.go deleted file mode 100644 index c20a33b0..00000000 --- a/bwrap/config.go +++ /dev/null @@ -1,265 +0,0 @@ -package bwrap - -import ( - "os" - "strconv" -) - -func (c *Config) Args() (args []string) { - b := c.boolArgs() - n := c.intArgs() - s := c.stringArgs() - p := c.pairArgs() - g := c.interfaceArgs() - - argc := 0 - for i, arg := range b { - if arg { - argc += len(boolArgs[i]) - } - } - for _, arg := range n { - if arg != nil { - argc += 2 - } - } - for _, arg := range s { - argc += len(arg) * 2 - } - for _, arg := range p { - argc += len(arg) * 3 - } - - args = make([]string, 0, argc) - for i, arg := range b { - if arg { - args = append(args, boolArgs[i]...) - } - } - for i, arg := range n { - if arg != nil { - args = append(args, intArgs[i], strconv.Itoa(*arg)) - } - } - for i, arg := range s { - for _, v := range arg { - args = append(args, stringArgs[i], v) - } - } - for i, arg := range p { - for _, v := range arg { - args = append(args, pairArgs[i], v[0], v[1]) - } - } - for i, arg := range g { - for _, v := range arg { - args = append(args, v.Value(interfaceArgs[i])...) - } - } - - return -} - -type Config struct { - // unshare every namespace we support by default if nil - // (--unshare-all) - Unshare *UnshareConfig `json:"unshare,omitempty"` - // retain the network namespace (can only combine with nil Unshare) - // (--share-net) - Net bool `json:"net"` - - // disable further use of user namespaces inside sandbox and fail unless - // further use of user namespace inside sandbox is disabled if false - // (--disable-userns) (--assert-userns-disabled) - UserNS bool `json:"userns"` - - // custom uid in the sandbox, requires new user namespace - // (--uid UID) - UID *int `json:"uid,omitempty"` - // custom gid in the sandbox, requires new user namespace - // (--gid GID) - GID *int `json:"gid,omitempty"` - // custom hostname in the sandbox, requires new uts namespace - // (--hostname NAME) - Hostname string `json:"hostname,omitempty"` - - // change directory - // (--chdir DIR) - Chdir string `json:"chdir,omitempty"` - // unset all environment variables - // (--clearenv) - Clearenv bool `json:"clearenv"` - // set environment variable - // (--setenv VAR VALUE) - SetEnv map[string]string `json:"setenv,omitempty"` - // unset environment variables - // (--unsetenv VAR) - UnsetEnv []string `json:"unsetenv,omitempty"` - - // take a lock on file while sandbox is running - // (--lock-file DEST) - LockFile []string `json:"lock_file,omitempty"` - - // bind mount host path on sandbox - // (--bind SRC DEST) - Bind [][2]string `json:"bind,omitempty"` - // equal to Bind but ignores non-existent host path - // (--bind-try SRC DEST) - BindTry [][2]string `json:"bind_try,omitempty"` - - // bind mount host path on sandbox, allowing device access - // (--dev-bind SRC DEST) - DevBind [][2]string `json:"dev_bind,omitempty"` - // equal to DevBind but ignores non-existent host path - // (--dev-bind-try SRC DEST) - DevBindTry [][2]string `json:"dev_bind_try,omitempty"` - - // bind mount host path readonly on sandbox - // (--ro-bind SRC DEST) - ROBind [][2]string `json:"ro_bind,omitempty"` - // equal to ROBind but ignores non-existent host path - // (--ro-bind-try SRC DEST) - ROBindTry [][2]string `json:"ro_bind_try,omitempty"` - - // remount path as readonly; does not recursively remount - // (--remount-ro DEST) - RemountRO []string `json:"remount_ro,omitempty"` - - // mount new procfs in sandbox - // (--proc DEST) - Procfs []PermConfig[string] `json:"proc,omitempty"` - // mount new dev in sandbox - // (--dev DEST) - DevTmpfs []PermConfig[string] `json:"dev,omitempty"` - // mount new tmpfs in sandbox - // (--tmpfs DEST) - Tmpfs []PermConfig[TmpfsConfig] `json:"tmpfs,omitempty"` - // mount new mqueue in sandbox - // (--mqueue DEST) - Mqueue []PermConfig[string] `json:"mqueue,omitempty"` - // create dir in sandbox - // (--dir DEST) - Dir []PermConfig[string] `json:"dir,omitempty"` - // create symlink within sandbox - // (--symlink SRC DEST) - Symlink []PermConfig[[2]string] `json:"symlink,omitempty"` - - // change permissions (must already exist) - // (--chmod OCTAL PATH) - Chmod map[string]os.FileMode `json:"chmod,omitempty"` - - // create a new terminal session - // (--new-session) - NewSession bool `json:"new_session"` - // kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies. - // (--die-with-parent) - DieWithParent bool `json:"die_with_parent"` - // do not install a reaper process with PID=1 - // (--as-pid-1) - AsInit bool `json:"as_init"` - - /* unmapped options include: - --unshare-user-try Create new user namespace if possible else continue by skipping it - --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it - --userns FD Use this user namespace (cannot combine with --unshare-user) - --userns2 FD After setup switch to this user namespace, only useful with --userns - --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid) - --sync-fd FD Keep this fd open while sandbox is running - --exec-label LABEL Exec label for the sandbox - --file-label LABEL File label for temporary sandbox content - --file FD DEST Copy from FD to destination DEST - --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST - --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST - --seccomp FD Load and use seccomp rules from FD (not repeatable) - --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable) - --block-fd FD Block on FD until some data to read is available - --userns-block-fd FD Block on FD until the user namespace is ready - --info-fd FD Write information about the running container to FD - --json-status-fd FD Write container status to FD as multiple JSON documents - --cap-add CAP Add cap CAP when running as privileged user - --cap-drop CAP Drop cap CAP when running as privileged user - - among which --args is used internally for passing arguments */ -} - -type UnshareConfig struct { - // (--unshare-user) - // create new user namespace - User bool `json:"user"` - // (--unshare-ipc) - // create new ipc namespace - IPC bool `json:"ipc"` - // (--unshare-pid) - // create new pid namespace - PID bool `json:"pid"` - // (--unshare-net) - // create new network namespace - Net bool `json:"net"` - // (--unshare-uts) - // create new uts namespace - UTS bool `json:"uts"` - // (--unshare-cgroup) - // create new cgroup namespace - CGroup bool `json:"cgroup"` -} - -type TmpfsConfig struct { - // set size of tmpfs - // (--size BYTES) - Size int `json:"size,omitempty"` - // mount point of new tmpfs - // (--tmpfs DEST) - Dir string `json:"dir"` -} - -type argOf interface { - Value(arg string) (args []string) -} - -func copyToArgOfSlice[T [2]string | string | TmpfsConfig](src []PermConfig[T]) (dst []argOf) { - dst = make([]argOf, len(src)) - for i, arg := range src { - dst[i] = arg - } - return -} - -type PermConfig[T [2]string | string | TmpfsConfig] struct { - // set permissions of next argument - // (--perms OCTAL) - Mode *os.FileMode `json:"mode,omitempty"` - // path to get the new permission - // (--bind-data, --file, etc.) - Path T -} - -func (p PermConfig[T]) Value(arg string) (args []string) { - // max possible size - if p.Mode != nil { - args = make([]string, 0, 6) - args = append(args, "--perms", strconv.Itoa(int(*p.Mode))) - } else { - args = make([]string, 0, 4) - } - - switch v := any(p.Path).(type) { - case string: - args = append(args, arg, v) - return - case [2]string: - args = append(args, arg, v[0], v[1]) - return - case TmpfsConfig: - if arg != "--tmpfs" { - panic("unreachable") - } - - if v.Size > 0 { - args = append(args, "--size", strconv.Itoa(v.Size)) - } - args = append(args, arg, v.Dir) - return - default: - panic("unreachable") - } -} diff --git a/bwrap/config.int.go b/bwrap/config.int.go deleted file mode 100644 index 27c88c36..00000000 --- a/bwrap/config.int.go +++ /dev/null @@ -1,22 +0,0 @@ -package bwrap - -const ( - UID = iota - GID - - intC -) - -var intArgs = func() (n [intC]string) { - n[UID] = "--uid" - n[GID] = "--gid" - - return -}() - -func (c *Config) intArgs() (n [intC]*int) { - n[UID] = c.UID - n[GID] = c.GID - - return -} diff --git a/bwrap/config.interface.go b/bwrap/config.interface.go deleted file mode 100644 index eb559c1d..00000000 --- a/bwrap/config.interface.go +++ /dev/null @@ -1,34 +0,0 @@ -package bwrap - -const ( - Procfs = iota - DevTmpfs - Tmpfs - Mqueue - Dir - Symlink - - interfaceC -) - -var interfaceArgs = func() (g [interfaceC]string) { - g[Procfs] = "--proc" - g[DevTmpfs] = "--dev" - g[Tmpfs] = "--tmpfs" - g[Mqueue] = "--mqueue" - g[Dir] = "--dir" - g[Symlink] = "--symlink" - - return -}() - -func (c *Config) interfaceArgs() (g [interfaceC][]argOf) { - g[Procfs] = copyToArgOfSlice(c.Procfs) - g[DevTmpfs] = copyToArgOfSlice(c.DevTmpfs) - g[Tmpfs] = copyToArgOfSlice(c.Tmpfs) - g[Mqueue] = copyToArgOfSlice(c.Mqueue) - g[Dir] = copyToArgOfSlice(c.Dir) - g[Symlink] = copyToArgOfSlice(c.Symlink) - - return -} diff --git a/bwrap/config.pair.go b/bwrap/config.pair.go deleted file mode 100644 index b0b1001b..00000000 --- a/bwrap/config.pair.go +++ /dev/null @@ -1,54 +0,0 @@ -package bwrap - -import "strconv" - -const ( - SetEnv = iota - - Bind - BindTry - DevBind - DevBindTry - ROBind - ROBindTry - - Chmod - - pairC -) - -var pairArgs = func() (n [pairC]string) { - n[SetEnv] = "--setenv" - - n[Bind] = "--bind" - n[BindTry] = "--bind-try" - n[DevBind] = "--dev-bind" - n[DevBindTry] = "--dev-bind-try" - n[ROBind] = "--ro-bind" - n[ROBindTry] = "--ro-bind-try" - - n[Chmod] = "--chmod" - - return -}() - -func (c *Config) pairArgs() (n [pairC][][2]string) { - n[SetEnv] = make([][2]string, 0, len(c.SetEnv)) - for k, v := range c.SetEnv { - n[SetEnv] = append(n[SetEnv], [2]string{k, v}) - } - - n[Bind] = c.Bind - n[BindTry] = c.BindTry - n[DevBind] = c.DevBind - n[DevBindTry] = c.DevBindTry - n[ROBind] = c.ROBind - n[ROBindTry] = c.ROBindTry - - n[Chmod] = make([][2]string, 0, len(c.Chmod)) - for path, octal := range c.Chmod { - n[Chmod] = append(n[Chmod], [2]string{strconv.Itoa(int(octal)), path}) - } - - return -} diff --git a/bwrap/config.string.go b/bwrap/config.string.go deleted file mode 100644 index c5c7a41e..00000000 --- a/bwrap/config.string.go +++ /dev/null @@ -1,35 +0,0 @@ -package bwrap - -const ( - Hostname = iota - Chdir - UnsetEnv - LockFile - RemountRO - - stringC -) - -var stringArgs = func() (n [stringC]string) { - n[Hostname] = "--hostname" - n[Chdir] = "--chdir" - n[UnsetEnv] = "--unsetenv" - n[LockFile] = "--lock-file" - n[RemountRO] = "--remount-ro" - - return -}() - -func (c *Config) stringArgs() (n [stringC][]string) { - if c.Hostname != "" { - n[Hostname] = []string{c.Hostname} - } - if c.Chdir != "" { - n[Chdir] = []string{c.Chdir} - } - n[UnsetEnv] = c.UnsetEnv - n[LockFile] = c.LockFile - n[RemountRO] = c.RemountRO - - return -} diff --git a/bwrap/config_test.go b/bwrap/config_test.go deleted file mode 100644 index d43c671d..00000000 --- a/bwrap/config_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package bwrap - -import ( - "slices" - "testing" -) - -func TestConfig_Args(t *testing.T) { - testCases := []struct { - name string - conf *Config - want []string - }{ - { - name: "xdg-dbus-proxy constraint sample", - conf: &Config{ - Unshare: nil, - UserNS: false, - Clearenv: true, - Symlink: []PermConfig[[2]string]{ - {Path: [2]string{"usr/bin", "/bin"}}, - {Path: [2]string{"var/home", "/home"}}, - {Path: [2]string{"usr/lib", "/lib"}}, - {Path: [2]string{"usr/lib64", "/lib64"}}, - {Path: [2]string{"run/media", "/media"}}, - {Path: [2]string{"var/mnt", "/mnt"}}, - {Path: [2]string{"var/opt", "/opt"}}, - {Path: [2]string{"sysroot/ostree", "/ostree"}}, - {Path: [2]string{"var/roothome", "/root"}}, - {Path: [2]string{"usr/sbin", "/sbin"}}, - {Path: [2]string{"var/srv", "/srv"}}, - }, - Bind: [][2]string{ - {"/run", "/run"}, - {"/tmp", "/tmp"}, - {"/var", "/var"}, - {"/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/"}, - }, - ROBind: [][2]string{ - {"/boot", "/boot"}, - {"/dev", "/dev"}, - {"/proc", "/proc"}, - {"/sys", "/sys"}, - {"/sysroot", "/sysroot"}, - {"/usr", "/usr"}, - {"/etc", "/etc"}, - }, - DieWithParent: true, - }, - want: []string{ - "--unshare-all", - "--disable-userns", - "--assert-userns-disabled", - "--clearenv", - "--die-with-parent", - "--bind", "/run", "/run", - "--bind", "/tmp", "/tmp", - "--bind", "/var", "/var", - "--bind", "/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/", - "--ro-bind", "/boot", "/boot", - "--ro-bind", "/dev", "/dev", - "--ro-bind", "/proc", "/proc", - "--ro-bind", "/sys", "/sys", - "--ro-bind", "/sysroot", "/sysroot", - "--ro-bind", "/usr", "/usr", - "--ro-bind", "/etc", "/etc", - "--symlink", "usr/bin", "/bin", - "--symlink", "var/home", "/home", - "--symlink", "usr/lib", "/lib", - "--symlink", "usr/lib64", "/lib64", - "--symlink", "run/media", "/media", - "--symlink", "var/mnt", "/mnt", - "--symlink", "var/opt", "/opt", - "--symlink", "sysroot/ostree", "/ostree", - "--symlink", "var/roothome", "/root", - "--symlink", "usr/sbin", "/sbin", - "--symlink", "var/srv", "/srv"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := tc.conf.Args(); !slices.Equal(got, tc.want) { - t.Errorf("Args() = %#v, want %#v", got, tc.want) - } - }) - } -} diff --git a/helper/bwrap/config.bool.go b/helper/bwrap/config.bool.go new file mode 100644 index 00000000..81e6ab7b --- /dev/null +++ b/helper/bwrap/config.bool.go @@ -0,0 +1,64 @@ +package bwrap + +const ( + UnshareAll = iota + UnshareUser + UnshareIPC + UnsharePID + UnshareNet + UnshareUTS + UnshareCGroup + ShareNet + + UserNS + Clearenv + + NewSession + DieWithParent + AsInit + + boolC +) + +var boolArgs = func() (b [boolC][]string) { + b[UnshareAll] = []string{"--unshare-all"} + b[UnshareUser] = []string{"--unshare-user"} + b[UnshareIPC] = []string{"--unshare-ipc"} + b[UnsharePID] = []string{"--unshare-pid"} + b[UnshareNet] = []string{"--unshare-net"} + b[UnshareUTS] = []string{"--unshare-uts"} + b[UnshareCGroup] = []string{"--unshare-cgroup"} + b[ShareNet] = []string{"--share-net"} + + b[UserNS] = []string{"--disable-userns", "--assert-userns-disabled"} + b[Clearenv] = []string{"--clearenv"} + + b[NewSession] = []string{"--new-session"} + b[DieWithParent] = []string{"--die-with-parent"} + b[AsInit] = []string{"--as-pid-1"} + + return +}() + +func (c *Config) boolArgs() (b [boolC]bool) { + if c.Unshare == nil { + b[UnshareAll] = true + b[ShareNet] = c.Net + } else { + b[UnshareUser] = c.Unshare.User + b[UnshareIPC] = c.Unshare.IPC + b[UnsharePID] = c.Unshare.PID + b[UnshareNet] = c.Unshare.Net + b[UnshareUTS] = c.Unshare.UTS + b[UnshareCGroup] = c.Unshare.CGroup + } + + b[UserNS] = !c.UserNS + b[Clearenv] = c.Clearenv + + b[NewSession] = c.NewSession + b[DieWithParent] = c.DieWithParent + b[AsInit] = c.AsInit + + return +} diff --git a/helper/bwrap/config.go b/helper/bwrap/config.go new file mode 100644 index 00000000..c20a33b0 --- /dev/null +++ b/helper/bwrap/config.go @@ -0,0 +1,265 @@ +package bwrap + +import ( + "os" + "strconv" +) + +func (c *Config) Args() (args []string) { + b := c.boolArgs() + n := c.intArgs() + s := c.stringArgs() + p := c.pairArgs() + g := c.interfaceArgs() + + argc := 0 + for i, arg := range b { + if arg { + argc += len(boolArgs[i]) + } + } + for _, arg := range n { + if arg != nil { + argc += 2 + } + } + for _, arg := range s { + argc += len(arg) * 2 + } + for _, arg := range p { + argc += len(arg) * 3 + } + + args = make([]string, 0, argc) + for i, arg := range b { + if arg { + args = append(args, boolArgs[i]...) + } + } + for i, arg := range n { + if arg != nil { + args = append(args, intArgs[i], strconv.Itoa(*arg)) + } + } + for i, arg := range s { + for _, v := range arg { + args = append(args, stringArgs[i], v) + } + } + for i, arg := range p { + for _, v := range arg { + args = append(args, pairArgs[i], v[0], v[1]) + } + } + for i, arg := range g { + for _, v := range arg { + args = append(args, v.Value(interfaceArgs[i])...) + } + } + + return +} + +type Config struct { + // unshare every namespace we support by default if nil + // (--unshare-all) + Unshare *UnshareConfig `json:"unshare,omitempty"` + // retain the network namespace (can only combine with nil Unshare) + // (--share-net) + Net bool `json:"net"` + + // disable further use of user namespaces inside sandbox and fail unless + // further use of user namespace inside sandbox is disabled if false + // (--disable-userns) (--assert-userns-disabled) + UserNS bool `json:"userns"` + + // custom uid in the sandbox, requires new user namespace + // (--uid UID) + UID *int `json:"uid,omitempty"` + // custom gid in the sandbox, requires new user namespace + // (--gid GID) + GID *int `json:"gid,omitempty"` + // custom hostname in the sandbox, requires new uts namespace + // (--hostname NAME) + Hostname string `json:"hostname,omitempty"` + + // change directory + // (--chdir DIR) + Chdir string `json:"chdir,omitempty"` + // unset all environment variables + // (--clearenv) + Clearenv bool `json:"clearenv"` + // set environment variable + // (--setenv VAR VALUE) + SetEnv map[string]string `json:"setenv,omitempty"` + // unset environment variables + // (--unsetenv VAR) + UnsetEnv []string `json:"unsetenv,omitempty"` + + // take a lock on file while sandbox is running + // (--lock-file DEST) + LockFile []string `json:"lock_file,omitempty"` + + // bind mount host path on sandbox + // (--bind SRC DEST) + Bind [][2]string `json:"bind,omitempty"` + // equal to Bind but ignores non-existent host path + // (--bind-try SRC DEST) + BindTry [][2]string `json:"bind_try,omitempty"` + + // bind mount host path on sandbox, allowing device access + // (--dev-bind SRC DEST) + DevBind [][2]string `json:"dev_bind,omitempty"` + // equal to DevBind but ignores non-existent host path + // (--dev-bind-try SRC DEST) + DevBindTry [][2]string `json:"dev_bind_try,omitempty"` + + // bind mount host path readonly on sandbox + // (--ro-bind SRC DEST) + ROBind [][2]string `json:"ro_bind,omitempty"` + // equal to ROBind but ignores non-existent host path + // (--ro-bind-try SRC DEST) + ROBindTry [][2]string `json:"ro_bind_try,omitempty"` + + // remount path as readonly; does not recursively remount + // (--remount-ro DEST) + RemountRO []string `json:"remount_ro,omitempty"` + + // mount new procfs in sandbox + // (--proc DEST) + Procfs []PermConfig[string] `json:"proc,omitempty"` + // mount new dev in sandbox + // (--dev DEST) + DevTmpfs []PermConfig[string] `json:"dev,omitempty"` + // mount new tmpfs in sandbox + // (--tmpfs DEST) + Tmpfs []PermConfig[TmpfsConfig] `json:"tmpfs,omitempty"` + // mount new mqueue in sandbox + // (--mqueue DEST) + Mqueue []PermConfig[string] `json:"mqueue,omitempty"` + // create dir in sandbox + // (--dir DEST) + Dir []PermConfig[string] `json:"dir,omitempty"` + // create symlink within sandbox + // (--symlink SRC DEST) + Symlink []PermConfig[[2]string] `json:"symlink,omitempty"` + + // change permissions (must already exist) + // (--chmod OCTAL PATH) + Chmod map[string]os.FileMode `json:"chmod,omitempty"` + + // create a new terminal session + // (--new-session) + NewSession bool `json:"new_session"` + // kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies. + // (--die-with-parent) + DieWithParent bool `json:"die_with_parent"` + // do not install a reaper process with PID=1 + // (--as-pid-1) + AsInit bool `json:"as_init"` + + /* unmapped options include: + --unshare-user-try Create new user namespace if possible else continue by skipping it + --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it + --userns FD Use this user namespace (cannot combine with --unshare-user) + --userns2 FD After setup switch to this user namespace, only useful with --userns + --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid) + --sync-fd FD Keep this fd open while sandbox is running + --exec-label LABEL Exec label for the sandbox + --file-label LABEL File label for temporary sandbox content + --file FD DEST Copy from FD to destination DEST + --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST + --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST + --seccomp FD Load and use seccomp rules from FD (not repeatable) + --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable) + --block-fd FD Block on FD until some data to read is available + --userns-block-fd FD Block on FD until the user namespace is ready + --info-fd FD Write information about the running container to FD + --json-status-fd FD Write container status to FD as multiple JSON documents + --cap-add CAP Add cap CAP when running as privileged user + --cap-drop CAP Drop cap CAP when running as privileged user + + among which --args is used internally for passing arguments */ +} + +type UnshareConfig struct { + // (--unshare-user) + // create new user namespace + User bool `json:"user"` + // (--unshare-ipc) + // create new ipc namespace + IPC bool `json:"ipc"` + // (--unshare-pid) + // create new pid namespace + PID bool `json:"pid"` + // (--unshare-net) + // create new network namespace + Net bool `json:"net"` + // (--unshare-uts) + // create new uts namespace + UTS bool `json:"uts"` + // (--unshare-cgroup) + // create new cgroup namespace + CGroup bool `json:"cgroup"` +} + +type TmpfsConfig struct { + // set size of tmpfs + // (--size BYTES) + Size int `json:"size,omitempty"` + // mount point of new tmpfs + // (--tmpfs DEST) + Dir string `json:"dir"` +} + +type argOf interface { + Value(arg string) (args []string) +} + +func copyToArgOfSlice[T [2]string | string | TmpfsConfig](src []PermConfig[T]) (dst []argOf) { + dst = make([]argOf, len(src)) + for i, arg := range src { + dst[i] = arg + } + return +} + +type PermConfig[T [2]string | string | TmpfsConfig] struct { + // set permissions of next argument + // (--perms OCTAL) + Mode *os.FileMode `json:"mode,omitempty"` + // path to get the new permission + // (--bind-data, --file, etc.) + Path T +} + +func (p PermConfig[T]) Value(arg string) (args []string) { + // max possible size + if p.Mode != nil { + args = make([]string, 0, 6) + args = append(args, "--perms", strconv.Itoa(int(*p.Mode))) + } else { + args = make([]string, 0, 4) + } + + switch v := any(p.Path).(type) { + case string: + args = append(args, arg, v) + return + case [2]string: + args = append(args, arg, v[0], v[1]) + return + case TmpfsConfig: + if arg != "--tmpfs" { + panic("unreachable") + } + + if v.Size > 0 { + args = append(args, "--size", strconv.Itoa(v.Size)) + } + args = append(args, arg, v.Dir) + return + default: + panic("unreachable") + } +} diff --git a/helper/bwrap/config.int.go b/helper/bwrap/config.int.go new file mode 100644 index 00000000..27c88c36 --- /dev/null +++ b/helper/bwrap/config.int.go @@ -0,0 +1,22 @@ +package bwrap + +const ( + UID = iota + GID + + intC +) + +var intArgs = func() (n [intC]string) { + n[UID] = "--uid" + n[GID] = "--gid" + + return +}() + +func (c *Config) intArgs() (n [intC]*int) { + n[UID] = c.UID + n[GID] = c.GID + + return +} diff --git a/helper/bwrap/config.interface.go b/helper/bwrap/config.interface.go new file mode 100644 index 00000000..eb559c1d --- /dev/null +++ b/helper/bwrap/config.interface.go @@ -0,0 +1,34 @@ +package bwrap + +const ( + Procfs = iota + DevTmpfs + Tmpfs + Mqueue + Dir + Symlink + + interfaceC +) + +var interfaceArgs = func() (g [interfaceC]string) { + g[Procfs] = "--proc" + g[DevTmpfs] = "--dev" + g[Tmpfs] = "--tmpfs" + g[Mqueue] = "--mqueue" + g[Dir] = "--dir" + g[Symlink] = "--symlink" + + return +}() + +func (c *Config) interfaceArgs() (g [interfaceC][]argOf) { + g[Procfs] = copyToArgOfSlice(c.Procfs) + g[DevTmpfs] = copyToArgOfSlice(c.DevTmpfs) + g[Tmpfs] = copyToArgOfSlice(c.Tmpfs) + g[Mqueue] = copyToArgOfSlice(c.Mqueue) + g[Dir] = copyToArgOfSlice(c.Dir) + g[Symlink] = copyToArgOfSlice(c.Symlink) + + return +} diff --git a/helper/bwrap/config.pair.go b/helper/bwrap/config.pair.go new file mode 100644 index 00000000..b0b1001b --- /dev/null +++ b/helper/bwrap/config.pair.go @@ -0,0 +1,54 @@ +package bwrap + +import "strconv" + +const ( + SetEnv = iota + + Bind + BindTry + DevBind + DevBindTry + ROBind + ROBindTry + + Chmod + + pairC +) + +var pairArgs = func() (n [pairC]string) { + n[SetEnv] = "--setenv" + + n[Bind] = "--bind" + n[BindTry] = "--bind-try" + n[DevBind] = "--dev-bind" + n[DevBindTry] = "--dev-bind-try" + n[ROBind] = "--ro-bind" + n[ROBindTry] = "--ro-bind-try" + + n[Chmod] = "--chmod" + + return +}() + +func (c *Config) pairArgs() (n [pairC][][2]string) { + n[SetEnv] = make([][2]string, 0, len(c.SetEnv)) + for k, v := range c.SetEnv { + n[SetEnv] = append(n[SetEnv], [2]string{k, v}) + } + + n[Bind] = c.Bind + n[BindTry] = c.BindTry + n[DevBind] = c.DevBind + n[DevBindTry] = c.DevBindTry + n[ROBind] = c.ROBind + n[ROBindTry] = c.ROBindTry + + n[Chmod] = make([][2]string, 0, len(c.Chmod)) + for path, octal := range c.Chmod { + n[Chmod] = append(n[Chmod], [2]string{strconv.Itoa(int(octal)), path}) + } + + return +} diff --git a/helper/bwrap/config.string.go b/helper/bwrap/config.string.go new file mode 100644 index 00000000..c5c7a41e --- /dev/null +++ b/helper/bwrap/config.string.go @@ -0,0 +1,35 @@ +package bwrap + +const ( + Hostname = iota + Chdir + UnsetEnv + LockFile + RemountRO + + stringC +) + +var stringArgs = func() (n [stringC]string) { + n[Hostname] = "--hostname" + n[Chdir] = "--chdir" + n[UnsetEnv] = "--unsetenv" + n[LockFile] = "--lock-file" + n[RemountRO] = "--remount-ro" + + return +}() + +func (c *Config) stringArgs() (n [stringC][]string) { + if c.Hostname != "" { + n[Hostname] = []string{c.Hostname} + } + if c.Chdir != "" { + n[Chdir] = []string{c.Chdir} + } + n[UnsetEnv] = c.UnsetEnv + n[LockFile] = c.LockFile + n[RemountRO] = c.RemountRO + + return +} diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go new file mode 100644 index 00000000..d43c671d --- /dev/null +++ b/helper/bwrap/config_test.go @@ -0,0 +1,88 @@ +package bwrap + +import ( + "slices" + "testing" +) + +func TestConfig_Args(t *testing.T) { + testCases := []struct { + name string + conf *Config + want []string + }{ + { + name: "xdg-dbus-proxy constraint sample", + conf: &Config{ + Unshare: nil, + UserNS: false, + Clearenv: true, + Symlink: []PermConfig[[2]string]{ + {Path: [2]string{"usr/bin", "/bin"}}, + {Path: [2]string{"var/home", "/home"}}, + {Path: [2]string{"usr/lib", "/lib"}}, + {Path: [2]string{"usr/lib64", "/lib64"}}, + {Path: [2]string{"run/media", "/media"}}, + {Path: [2]string{"var/mnt", "/mnt"}}, + {Path: [2]string{"var/opt", "/opt"}}, + {Path: [2]string{"sysroot/ostree", "/ostree"}}, + {Path: [2]string{"var/roothome", "/root"}}, + {Path: [2]string{"usr/sbin", "/sbin"}}, + {Path: [2]string{"var/srv", "/srv"}}, + }, + Bind: [][2]string{ + {"/run", "/run"}, + {"/tmp", "/tmp"}, + {"/var", "/var"}, + {"/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/"}, + }, + ROBind: [][2]string{ + {"/boot", "/boot"}, + {"/dev", "/dev"}, + {"/proc", "/proc"}, + {"/sys", "/sys"}, + {"/sysroot", "/sysroot"}, + {"/usr", "/usr"}, + {"/etc", "/etc"}, + }, + DieWithParent: true, + }, + want: []string{ + "--unshare-all", + "--disable-userns", + "--assert-userns-disabled", + "--clearenv", + "--die-with-parent", + "--bind", "/run", "/run", + "--bind", "/tmp", "/tmp", + "--bind", "/var", "/var", + "--bind", "/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/", + "--ro-bind", "/boot", "/boot", + "--ro-bind", "/dev", "/dev", + "--ro-bind", "/proc", "/proc", + "--ro-bind", "/sys", "/sys", + "--ro-bind", "/sysroot", "/sysroot", + "--ro-bind", "/usr", "/usr", + "--ro-bind", "/etc", "/etc", + "--symlink", "usr/bin", "/bin", + "--symlink", "var/home", "/home", + "--symlink", "usr/lib", "/lib", + "--symlink", "usr/lib64", "/lib64", + "--symlink", "run/media", "/media", + "--symlink", "var/mnt", "/mnt", + "--symlink", "var/opt", "/opt", + "--symlink", "sysroot/ostree", "/ostree", + "--symlink", "var/roothome", "/root", + "--symlink", "usr/sbin", "/sbin", + "--symlink", "var/srv", "/srv"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.conf.Args(); !slices.Equal(got, tc.want) { + t.Errorf("Args() = %#v, want %#v", got, tc.want) + } + }) + } +} -- cgit v1.3.1