app: integrate container autoroot
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m6s
Test / Hakurei (push) Successful in 3m10s
Test / Sandbox (race detector) (push) Successful in 4m5s
Test / Hpkg (push) Successful in 4m23s
Test / Hakurei (race detector) (push) Successful in 4m44s
Test / Flake checks (push) Successful in 1m24s

Doing this instead of mounting directly on / because it's impossible to ensure a parent is available for every path hakurei wants to mount to. This situation is similar to autoetc hence the similar name, however a symlink mirror will not work in this case.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-08-01 04:10:33 +09:00
parent 4e85643865
commit cd11de81a1
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
5 changed files with 86 additions and 85 deletions

View File

@ -45,10 +45,17 @@ type (
// create symlinks inside container filesystem // create symlinks inside container filesystem
Link [][2]string `json:"symlink"` Link [][2]string `json:"symlink"`
// automatically bind mount top-level directories to container root;
// the zero value disables this behaviour
AutoRoot string `json:"auto_root,omitempty"`
// extra flags for AutoRoot
RootFlags int `json:"root_flags,omitempty"`
// read-only /etc directory // read-only /etc directory
Etc string `json:"etc,omitempty"` Etc string `json:"etc,omitempty"`
// automatically set up /etc symlinks // automatically set up /etc symlinks
AutoEtc bool `json:"auto_etc"` AutoEtc bool `json:"auto_etc"`
// cover these paths or create them if they do not already exist // cover these paths or create them if they do not already exist
Cover []string `json:"cover"` Cover []string `json:"cover"`
} }

View File

@ -42,21 +42,10 @@ var testCasesPd = []sealTestCase{
"XDG_SESSION_TYPE=tty", "XDG_SESSION_TYPE=tty",
}, },
Ops: new(container.Ops). Ops: new(container.Ops).
Root("/", "4a450b6596d7bc15bd01780eb9a607ac", container.BindWritable).
Proc("/proc"). Proc("/proc").
Tmpfs(hst.Tmp, 4096, 0755). Tmpfs(hst.Tmp, 4096, 0755).
Dev("/dev").Mqueue("/dev/mqueue"). Dev("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", container.BindWritable).
Bind("/boot", "/boot", container.BindWritable).
Bind("/home", "/home", container.BindWritable).
Bind("/lib", "/lib", container.BindWritable).
Bind("/lib64", "/lib64", container.BindWritable).
Bind("/nix", "/nix", container.BindWritable).
Bind("/root", "/root", container.BindWritable).
Bind("/run", "/run", container.BindWritable).
Bind("/srv", "/srv", container.BindWritable).
Bind("/sys", "/sys", container.BindWritable).
Bind("/usr", "/usr", container.BindWritable).
Bind("/var", "/var", container.BindWritable).
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional). Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
Tmpfs("/run/user/1971", 8192, 0755). Tmpfs("/run/user/1971", 8192, 0755).
Tmpfs("/run/dbus", 8192, 0755). Tmpfs("/run/dbus", 8192, 0755).
@ -186,21 +175,10 @@ var testCasesPd = []sealTestCase{
"XDG_SESSION_TYPE=tty", "XDG_SESSION_TYPE=tty",
}, },
Ops: new(container.Ops). Ops: new(container.Ops).
Root("/", "ebf083d1b175911782d413369b64ce7c", container.BindWritable).
Proc("/proc"). Proc("/proc").
Tmpfs(hst.Tmp, 4096, 0755). Tmpfs(hst.Tmp, 4096, 0755).
Dev("/dev").Mqueue("/dev/mqueue"). Dev("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", container.BindWritable).
Bind("/boot", "/boot", container.BindWritable).
Bind("/home", "/home", container.BindWritable).
Bind("/lib", "/lib", container.BindWritable).
Bind("/lib64", "/lib64", container.BindWritable).
Bind("/nix", "/nix", container.BindWritable).
Bind("/root", "/root", container.BindWritable).
Bind("/run", "/run", container.BindWritable).
Bind("/srv", "/srv", container.BindWritable).
Bind("/sys", "/sys", container.BindWritable).
Bind("/usr", "/usr", container.BindWritable).
Bind("/var", "/var", container.BindWritable).
Bind("/dev/dri", "/dev/dri", container.BindWritable|container.BindDevice|container.BindOptional). Bind("/dev/dri", "/dev/dri", container.BindWritable|container.BindDevice|container.BindOptional).
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional). Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
Tmpfs("/run/user/1971", 8192, 0755). Tmpfs("/run/user/1971", 8192, 0755).

View File

@ -6,6 +6,7 @@ import (
"io/fs" "io/fs"
"maps" "maps"
"path" "path"
"slices"
"syscall" "syscall"
"hakurei.app/container" "hakurei.app/container"
@ -21,7 +22,7 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig]. // newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller. // Note that remaining container setup must be queued by the caller.
func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*container.Params, map[string]string, error) { func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
if s == nil { if s == nil {
return nil, nil, syscall.EBADE return nil, nil, syscall.EBADE
} }
@ -72,6 +73,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
*gid = container.OverflowGid() *gid = container.OverflowGid()
} }
if s.AutoRoot != "" {
if !path.IsAbs(s.AutoRoot) {
return nil, nil, fmt.Errorf("auto root target %q not absolute", s.AutoRoot)
}
params.Root(s.AutoRoot, prefix, s.RootFlags)
}
params. params.
Proc("/proc"). Proc("/proc").
Tmpfs(hst.Tmp, 1<<12, 0755) Tmpfs(hst.Tmp, 1<<12, 0755)
@ -120,6 +128,28 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
return nil, nil, err return nil, nil, err
} }
} }
// evaluated path, input path
hidePathSource := make([][2]string, 0, len(s.Filesystem))
// AutoRoot is a collection of many BindMountOp internally
if s.AutoRoot != "" {
if d, err := os.ReadDir(s.AutoRoot); err != nil {
return nil, nil, err
} else {
hidePathSource = slices.Grow(hidePathSource, len(d))
for _, ent := range d {
name := ent.Name()
if container.IsAutoRootBindable(name) {
name = path.Join(s.AutoRoot, name)
srcP := [2]string{name, name}
if err = evalSymlinks(os, &srcP[0]); err != nil {
return nil, nil, err
}
hidePathSource = append(hidePathSource, srcP)
}
}
}
}
for _, c := range s.Filesystem { for _, c := range s.Filesystem {
if c == nil { if c == nil {
@ -137,24 +167,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest) return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
} }
srcH := c.Src p := [2]string{c.Src, c.Src}
if err := evalSymlinks(os, &srcH); err != nil { if err := evalSymlinks(os, &p[0]); err != nil {
return nil, nil, err return nil, nil, err
} }
hidePathSource = append(hidePathSource, p)
for i := range hidePaths {
// skip matched entries
if hidePathMatch[i] {
continue
}
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
return nil, nil, err
} else if ok {
hidePathMatch[i] = true
os.Printf("hiding paths from %q", c.Src)
}
}
var flags int var flags int
if c.Write { if c.Write {
@ -169,6 +186,22 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
params.Bind(c.Src, dest, flags) params.Bind(c.Src, dest, flags)
} }
for _, p := range hidePathSource {
for i := range hidePaths {
// skip matched entries
if hidePathMatch[i] {
continue
}
if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil {
return nil, nil, err
} else if ok {
hidePathMatch[i] = true
os.Printf("hiding path %q from %q", hidePaths[i], p[1])
}
}
}
// cover matched paths // cover matched paths
for i, ok := range hidePathMatch { for i, ok := range hidePathMatch {
if ok { if ok {
@ -180,6 +213,18 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
params.Link(l[0], l[1]) params.Link(l[0], l[1])
} }
if !s.AutoEtc {
if s.Etc != "" {
params.Bind(s.Etc, "/etc", 0)
}
} else {
etcPath := s.Etc
if etcPath == "" {
etcPath = "/etc"
}
params.Etc(etcPath, prefix)
}
return params, maps.Clone(s.Env), nil return params, maps.Clone(s.Env), nil
} }

View File

@ -241,26 +241,9 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
Net: true, Net: true,
Tty: true, Tty: true,
AutoEtc: true, AutoEtc: true,
}
// bind entries in /
if d, err := sys.ReadDir("/"); err != nil {
return err
} else {
b := make([]*hst.FilesystemConfig, 0, len(d))
for _, ent := range d {
p := "/" + ent.Name()
switch p {
case "/proc":
case "/dev":
case "/tmp":
case "/mnt":
case "/etc":
default: AutoRoot: "/",
b = append(b, &hst.FilesystemConfig{Src: p, Write: true, Must: true}) RootFlags: container.BindWritable,
}
}
conf.Filesystem = append(conf.Filesystem, b...)
} }
// hide nscd from sandbox if present // hide nscd from sandbox if present
@ -282,7 +265,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
{ {
var uid, gid int var uid, gid int
var err error var err error
seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid) seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
seal.waitDelay = config.Container.WaitDelay seal.waitDelay = config.Container.WaitDelay
if err != nil { if err != nil {
return hlog.WrapErrSuffix(err, return hlog.WrapErrSuffix(err,
@ -305,18 +288,6 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
} }
} }
if !config.Container.AutoEtc {
if config.Container.Etc != "" {
seal.container.Bind(config.Container.Etc, "/etc", 0)
}
} else {
etcPath := config.Container.Etc
if etcPath == "" {
etcPath = "/etc"
}
seal.container.Etc(etcPath, seal.id.String())
}
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid // inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
innerRuntimeDir := path.Join("/run/user", mapuid.String()) innerRuntimeDir := path.Join("/run/user", mapuid.String())
seal.env[xdgRuntimeDir] = innerRuntimeDir seal.env[xdgRuntimeDir] = innerRuntimeDir

View File

@ -139,18 +139,6 @@
mount = [ mount = [
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000") (ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000000,gid=1000000")
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/random" "/dev/random" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/bin" "/bin" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/bin" "/bin" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/home" "/home" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/home" "/home" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/lib64" "/lib64" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/lib64" "/lib64" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
@ -180,6 +168,18 @@
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw") (ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
(ent "/usr" "/usr" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/usr" "/usr" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/var" "/var" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/var" "/var" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000000,gid=1000000")
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/random" "/dev/random" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000") (ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
(ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000") (ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000")