hst/container: pack boolean options
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m12s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 4m2s
Test / Hakurei (race detector) (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 2m11s
Test / Flake checks (push) Successful in 1m37s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m12s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 4m2s
Test / Hakurei (race detector) (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 2m11s
Test / Flake checks (push) Successful in 1m37s
The memory saving is relatively insignificant, however this increases serialisation efficiency. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
151
hst/container.go
151
hst/container.go
@@ -1,6 +1,8 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
@@ -29,6 +31,37 @@ const (
|
||||
ShimExitOrphan = 3
|
||||
)
|
||||
|
||||
const (
|
||||
// FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
|
||||
FMultiarch uintptr = 1 << iota
|
||||
|
||||
// FSeccompCompat causes emitted seccomp filter programs to be identical to Flatpak.
|
||||
FSeccompCompat
|
||||
// FDevel unblocks ptrace and friends.
|
||||
FDevel
|
||||
// FUserns unblocks userns creation and container setup syscalls.
|
||||
FUserns
|
||||
// FHostNet skips net namespace creation.
|
||||
FHostNet
|
||||
// FHostAbstract skips setting up abstract unix socket scope.
|
||||
FHostAbstract
|
||||
// FTty unblocks dangerous terminal I/O (faking input).
|
||||
FTty
|
||||
|
||||
// FMapRealUID maps the target user uid to the privileged user uid in the container user namespace.
|
||||
// Some programs fail to connect to dbus session running as a different uid,
|
||||
// this option works around it by mapping priv-side caller uid in container.
|
||||
FMapRealUID
|
||||
|
||||
// FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
||||
FDevice
|
||||
|
||||
fMax
|
||||
|
||||
// FAll is [ContainerConfig.Flags] with all currently defined bits set.
|
||||
FAll = fMax - 1
|
||||
)
|
||||
|
||||
// ContainerConfig describes the container configuration to be applied to an underlying [container].
|
||||
type ContainerConfig struct {
|
||||
// Container UTS namespace hostname.
|
||||
@@ -39,33 +72,9 @@ type ContainerConfig struct {
|
||||
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
|
||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||
|
||||
// Emit Flatpak-compatible seccomp filter programs.
|
||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||
// Allow ptrace and friends.
|
||||
Devel bool `json:"devel,omitempty"`
|
||||
// Allow userns creation and container setup syscalls.
|
||||
Userns bool `json:"userns,omitempty"`
|
||||
// Share host net namespace.
|
||||
HostNet bool `json:"host_net,omitempty"`
|
||||
// Share abstract unix socket scope.
|
||||
HostAbstract bool `json:"host_abstract,omitempty"`
|
||||
// Allow dangerous terminal I/O (faking input).
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
// Allow multiarch.
|
||||
Multiarch bool `json:"multiarch,omitempty"`
|
||||
|
||||
// Initial process environment variables.
|
||||
Env map[string]string `json:"env"`
|
||||
|
||||
/* Map target user uid to privileged user uid in the container user namespace.
|
||||
|
||||
Some programs fail to connect to dbus session running as a different uid,
|
||||
this option works around it by mapping priv-side caller uid in container. */
|
||||
MapRealUID bool `json:"map_real_uid"`
|
||||
|
||||
// Mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
||||
Device bool `json:"device,omitempty"`
|
||||
|
||||
/* Container mount points.
|
||||
|
||||
If the first element targets /, it is inserted early and excluded from path hiding. */
|
||||
@@ -83,4 +92,98 @@ type ContainerConfig struct {
|
||||
Path *check.Absolute `json:"path,omitempty"`
|
||||
// Final args passed to the initial program.
|
||||
Args []string `json:"args"`
|
||||
|
||||
// Flags holds boolean options of [ContainerConfig].
|
||||
Flags uintptr `json:"-"`
|
||||
}
|
||||
|
||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||
type ContainerConfigF ContainerConfig
|
||||
|
||||
// containerConfigJSON is the [json] representation of [ContainerConfig].
|
||||
type containerConfigJSON = struct {
|
||||
*ContainerConfigF
|
||||
|
||||
// Corresponds to [FSeccompCompat].
|
||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||
// Corresponds to [FDevel].
|
||||
Devel bool `json:"devel,omitempty"`
|
||||
// Corresponds to [FUserns].
|
||||
Userns bool `json:"userns,omitempty"`
|
||||
// Corresponds to [FHostNet].
|
||||
HostNet bool `json:"host_net,omitempty"`
|
||||
// Corresponds to [FHostAbstract].
|
||||
HostAbstract bool `json:"host_abstract,omitempty"`
|
||||
// Corresponds to [FTty].
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
|
||||
// Corresponds to [FMultiarch].
|
||||
Multiarch bool `json:"multiarch,omitempty"`
|
||||
|
||||
// Corresponds to [FMapRealUID].
|
||||
MapRealUID bool `json:"map_real_uid"`
|
||||
|
||||
// Corresponds to [FDevice].
|
||||
Device bool `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
||||
if c == nil {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return json.Marshal(&containerConfigJSON{
|
||||
ContainerConfigF: (*ContainerConfigF)(c),
|
||||
|
||||
SeccompCompat: c.Flags&FSeccompCompat != 0,
|
||||
Devel: c.Flags&FDevel != 0,
|
||||
Userns: c.Flags&FUserns != 0,
|
||||
HostNet: c.Flags&FHostNet != 0,
|
||||
HostAbstract: c.Flags&FHostAbstract != 0,
|
||||
Tty: c.Flags&FTty != 0,
|
||||
Multiarch: c.Flags&FMultiarch != 0,
|
||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||
Device: c.Flags&FDevice != 0,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
||||
if c == nil {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
v := new(containerConfigJSON)
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*c = *(*ContainerConfig)(v.ContainerConfigF)
|
||||
if v.SeccompCompat {
|
||||
c.Flags |= FSeccompCompat
|
||||
}
|
||||
if v.Devel {
|
||||
c.Flags |= FDevel
|
||||
}
|
||||
if v.Userns {
|
||||
c.Flags |= FUserns
|
||||
}
|
||||
if v.HostNet {
|
||||
c.Flags |= FHostNet
|
||||
}
|
||||
if v.HostAbstract {
|
||||
c.Flags |= FHostAbstract
|
||||
}
|
||||
if v.Tty {
|
||||
c.Flags |= FTty
|
||||
}
|
||||
if v.Multiarch {
|
||||
c.Flags |= FMultiarch
|
||||
}
|
||||
if v.MapRealUID {
|
||||
c.Flags |= FMapRealUID
|
||||
}
|
||||
if v.Device {
|
||||
c.Flags |= FDevice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
76
hst/container_test.go
Normal file
76
hst/container_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package hst_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestContainerConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c *hst.ContainerConfig
|
||||
data string
|
||||
}{
|
||||
{"nil", nil, "null"},
|
||||
{"zero", new(hst.ContainerConfig),
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"map_real_uid":false}`},
|
||||
{"seccomp compat", &hst.ContainerConfig{Flags: hst.FSeccompCompat},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"map_real_uid":false}`},
|
||||
{"hostnet hostabstract", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":false}`},
|
||||
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
||||
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true}`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, err := json.Marshal(tc.c); err != nil {
|
||||
t.Fatalf("Marshal: error = %v", err)
|
||||
} else if string(got) != tc.data {
|
||||
t.Errorf("Marshal:\n%s, want\n%s", string(got), tc.data)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
{
|
||||
got := new(hst.ContainerConfig)
|
||||
if err := json.Unmarshal([]byte(tc.data), &got); err != nil {
|
||||
t.Fatalf("Unmarshal: error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tc.c) {
|
||||
t.Errorf("Unmarshal: %v, want %v", got, tc.c)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := (*hst.ContainerConfig)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("MarshalJSON: error = %v", err)
|
||||
}
|
||||
if err := (*hst.ContainerConfig)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
if err := new(hst.ContainerConfig).UnmarshalJSON([]byte{}); err == nil {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
17
hst/hst.go
17
hst/hst.go
@@ -3,6 +3,7 @@ package hst
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
@@ -96,17 +97,8 @@ func Template() *Config {
|
||||
Groups: []string{"video", "dialout", "plugdev"},
|
||||
|
||||
Container: &ContainerConfig{
|
||||
Hostname: "localhost",
|
||||
Devel: true,
|
||||
Userns: true,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Device: true,
|
||||
WaitDelay: -1,
|
||||
SeccompCompat: true,
|
||||
Tty: true,
|
||||
Multiarch: true,
|
||||
MapRealUID: true,
|
||||
Hostname: "localhost",
|
||||
WaitDelay: -1,
|
||||
// example API credentials pulled from Google Chrome
|
||||
// DO NOT USE THESE IN A REAL BROWSER
|
||||
Env: map[string]string{
|
||||
@@ -143,6 +135,9 @@ func Template() *Config {
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland",
|
||||
},
|
||||
|
||||
// Set all bits here so new flags trip the template test.
|
||||
Flags: math.MaxUint,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
@@ -164,20 +165,11 @@ func TestTemplate(t *testing.T) {
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
},
|
||||
"map_real_uid": true,
|
||||
"device": true,
|
||||
"filesystem": [
|
||||
{
|
||||
"type": "bind",
|
||||
@@ -243,14 +235,41 @@ func TestTemplate(t *testing.T) {
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
]
|
||||
],
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"device": true
|
||||
}
|
||||
}`
|
||||
|
||||
if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
|
||||
t.Fatalf("cannot marshal: %v", err)
|
||||
} else if s := string(p); s != want {
|
||||
t.Fatalf("Template:\n%s\nwant:\n%s",
|
||||
s, want)
|
||||
}
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
|
||||
t.Fatalf("cannot marshal: %v", err)
|
||||
} else if s := string(p); s != want {
|
||||
t.Fatalf("Template:\n%s\nwant:\n%s",
|
||||
s, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got *hst.Config
|
||||
if err := json.Unmarshal([]byte(want), &got); err != nil {
|
||||
t.Fatalf("Unmarshal: error = %v", err)
|
||||
}
|
||||
|
||||
wantVal := hst.Template()
|
||||
wantVal.Container.Flags = hst.FAll
|
||||
if !reflect.DeepEqual(got, wantVal) {
|
||||
t.Fatalf("Unmarshal: %#v, want %#v", got, wantVal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user