All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m9s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hakurei (race detector) (push) Successful in 4m55s
Test / Flake checks (push) Successful in 1m25s
This reduces collision with local variable names, and generally makes sense for the new store package, since it no longer specifies the state struct. Signed-off-by: Ophestra <cat@gensokyo.uk>
337 lines
9.6 KiB
Go
337 lines
9.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
_ "unsafe"
|
|
|
|
"hakurei.app/command"
|
|
"hakurei.app/container/check"
|
|
"hakurei.app/container/fhs"
|
|
"hakurei.app/hst"
|
|
"hakurei.app/internal"
|
|
"hakurei.app/internal/env"
|
|
"hakurei.app/internal/outcome"
|
|
"hakurei.app/internal/store"
|
|
"hakurei.app/message"
|
|
"hakurei.app/system/dbus"
|
|
)
|
|
|
|
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap
|
|
func optionalErrorUnwrap(_ error) error
|
|
|
|
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
|
var (
|
|
flagVerbose bool
|
|
flagJSON bool
|
|
)
|
|
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
|
msg.SwapVerbose(flagVerbose)
|
|
|
|
if early.yamaLSM != nil {
|
|
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", early.yamaLSM)
|
|
// not fatal
|
|
}
|
|
|
|
if early.dumpable != nil {
|
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", early.dumpable)
|
|
// not fatal
|
|
}
|
|
|
|
return nil
|
|
}).
|
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
|
|
|
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
|
|
|
c.Command("app", "Load and start container from configuration file", func(args []string) error {
|
|
if len(args) < 1 {
|
|
log.Fatal("app requires at least 1 argument")
|
|
}
|
|
|
|
// config extraArgs...
|
|
config := tryPath(msg, args[0])
|
|
if config != nil && config.Container != nil {
|
|
config.Container.Args = append(config.Container.Args, args[1:]...)
|
|
}
|
|
|
|
outcome.Main(ctx, msg, config)
|
|
panic("unreachable")
|
|
})
|
|
|
|
{
|
|
var (
|
|
flagDBusConfigSession string
|
|
flagDBusConfigSystem string
|
|
flagDBusMpris bool
|
|
flagDBusVerbose bool
|
|
|
|
flagID string
|
|
flagIdentity int
|
|
flagGroups command.RepeatableFlag
|
|
flagHomeDir string
|
|
flagUserName string
|
|
|
|
flagPrivateRuntime, flagPrivateTmpdir bool
|
|
|
|
flagWayland, flagX11, flagDBus, flagPulse bool
|
|
)
|
|
|
|
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
|
|
if flagIdentity < hst.IdentityMin || flagIdentity > hst.IdentityMax {
|
|
log.Fatalf("identity %d out of range", flagIdentity)
|
|
}
|
|
|
|
// resolve home/username from os when flag is unset
|
|
var (
|
|
passwd *user.User
|
|
passwdOnce sync.Once
|
|
passwdFunc = func() {
|
|
us := strconv.Itoa(outcome.HsuUid(new(outcome.Hsu).MustID(msg), flagIdentity))
|
|
if u, err := user.LookupId(us); err != nil {
|
|
msg.Verbosef("cannot look up uid %s", us)
|
|
passwd = &user.User{
|
|
Uid: us,
|
|
Gid: us,
|
|
Username: "chronos",
|
|
Name: "Hakurei Permissive Default",
|
|
HomeDir: fhs.VarEmpty,
|
|
}
|
|
} else {
|
|
passwd = u
|
|
}
|
|
}
|
|
)
|
|
|
|
// paths are identical, resolve inner shell and program path
|
|
shell := fhs.AbsRoot.Append("bin", "sh")
|
|
if a, err := check.NewAbs(os.Getenv("SHELL")); err == nil {
|
|
shell = a
|
|
}
|
|
progPath := shell
|
|
if len(args) > 0 {
|
|
if p, err := exec.LookPath(args[0]); err != nil {
|
|
log.Fatal(optionalErrorUnwrap(err))
|
|
return err
|
|
} else if progPath, err = check.NewAbs(p); err != nil {
|
|
log.Fatal(err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
var et hst.Enablement
|
|
if flagWayland {
|
|
et |= hst.EWayland
|
|
}
|
|
if flagX11 {
|
|
et |= hst.EX11
|
|
}
|
|
if flagDBus {
|
|
et |= hst.EDBus
|
|
}
|
|
if flagPulse {
|
|
et |= hst.EPulse
|
|
}
|
|
|
|
config := &hst.Config{
|
|
ID: flagID,
|
|
Identity: flagIdentity,
|
|
Groups: flagGroups,
|
|
Enablements: hst.NewEnablements(et),
|
|
|
|
Container: &hst.ContainerConfig{
|
|
Filesystem: []hst.FilesystemConfigJSON{
|
|
// autoroot, includes the home directory
|
|
{FilesystemConfig: &hst.FSBind{
|
|
Target: fhs.AbsRoot,
|
|
Source: fhs.AbsRoot,
|
|
Write: true,
|
|
Special: true,
|
|
}},
|
|
},
|
|
|
|
Username: flagUserName,
|
|
Shell: shell,
|
|
|
|
Path: progPath,
|
|
Args: args,
|
|
|
|
Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty,
|
|
},
|
|
}
|
|
|
|
// bind GPU stuff
|
|
if et&(hst.EX11|hst.EWayland) != 0 {
|
|
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
Source: fhs.AbsDev.Append("dri"),
|
|
Device: true,
|
|
Optional: true,
|
|
}})
|
|
}
|
|
|
|
config.Container.Filesystem = append(config.Container.Filesystem,
|
|
// opportunistically bind kvm
|
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
Source: fhs.AbsDev.Append("kvm"),
|
|
Device: true,
|
|
Optional: true,
|
|
}},
|
|
|
|
// do autoetc last
|
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
Target: fhs.AbsEtc,
|
|
Source: fhs.AbsEtc,
|
|
Special: true,
|
|
}},
|
|
)
|
|
|
|
if config.Container.Username == "chronos" {
|
|
passwdOnce.Do(passwdFunc)
|
|
config.Container.Username = passwd.Username
|
|
}
|
|
|
|
{
|
|
homeDir := flagHomeDir
|
|
if homeDir == "os" {
|
|
passwdOnce.Do(passwdFunc)
|
|
homeDir = passwd.HomeDir
|
|
}
|
|
if a, err := check.NewAbs(homeDir); err != nil {
|
|
log.Fatal(err.Error())
|
|
return err
|
|
} else {
|
|
config.Container.Home = a
|
|
}
|
|
}
|
|
|
|
if !flagPrivateRuntime {
|
|
config.Container.Flags |= hst.FShareRuntime
|
|
}
|
|
if !flagPrivateTmpdir {
|
|
config.Container.Flags |= hst.FShareTmpdir
|
|
}
|
|
|
|
// parse D-Bus config file from flags if applicable
|
|
if flagDBus {
|
|
if flagDBusConfigSession == "builtin" {
|
|
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
|
|
} else {
|
|
if f, err := os.Open(flagDBusConfigSession); err != nil {
|
|
log.Fatal(err.Error())
|
|
} else {
|
|
decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus)
|
|
if err = f.Close(); err != nil {
|
|
log.Fatal(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// system bus proxy is optional
|
|
if flagDBusConfigSystem != "nil" {
|
|
if f, err := os.Open(flagDBusConfigSystem); err != nil {
|
|
log.Fatal(err.Error())
|
|
} else {
|
|
decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus)
|
|
if err = f.Close(); err != nil {
|
|
log.Fatal(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// override log from configuration
|
|
if flagDBusVerbose {
|
|
if config.SessionBus != nil {
|
|
config.SessionBus.Log = true
|
|
}
|
|
if config.SystemBus != nil {
|
|
config.SystemBus.Log = true
|
|
}
|
|
}
|
|
}
|
|
|
|
outcome.Main(ctx, msg, config)
|
|
panic("unreachable")
|
|
}).
|
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
|
"Path to session bus proxy config file, or \"builtin\" for defaults").
|
|
Flag(&flagDBusConfigSystem, "dbus-system", command.StringFlag("nil"),
|
|
"Path to system bus proxy config file, or \"nil\" to disable").
|
|
Flag(&flagDBusMpris, "mpris", command.BoolFlag(false),
|
|
"Allow owning MPRIS D-Bus path, has no effect if custom config is available").
|
|
Flag(&flagDBusVerbose, "dbus-log", command.BoolFlag(false),
|
|
"Force buffered logging in the D-Bus proxy").
|
|
Flag(&flagID, "id", command.StringFlag(""),
|
|
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
|
|
Flag(&flagIdentity, "a", command.IntFlag(0),
|
|
"Application identity").
|
|
Flag(nil, "g", &flagGroups,
|
|
"Groups inherited by all container processes").
|
|
Flag(&flagHomeDir, "d", command.StringFlag("os"),
|
|
"Container home directory").
|
|
Flag(&flagUserName, "u", command.StringFlag("chronos"),
|
|
"Passwd user name within sandbox").
|
|
Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false),
|
|
"Do not share XDG_RUNTIME_DIR between containers under the same identity").
|
|
Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false),
|
|
"Do not share TMPDIR between containers under the same identity").
|
|
Flag(&flagWayland, "wayland", command.BoolFlag(false),
|
|
"Enable connection to Wayland via security-context-v1").
|
|
Flag(&flagX11, "X", command.BoolFlag(false),
|
|
"Enable direct connection to X11").
|
|
Flag(&flagDBus, "dbus", command.BoolFlag(false),
|
|
"Enable proxied connection to D-Bus").
|
|
Flag(&flagPulse, "pulse", command.BoolFlag(false),
|
|
"Enable direct connection to PulseAudio")
|
|
}
|
|
|
|
{
|
|
var flagShort bool
|
|
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
|
switch len(args) {
|
|
case 0: // system
|
|
printShowSystem(os.Stdout, flagShort, flagJSON)
|
|
|
|
case 1: // instance
|
|
name := args[0]
|
|
config, entry := tryIdentifier(msg, name)
|
|
if config == nil {
|
|
config = tryPath(msg, name)
|
|
}
|
|
if !printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) {
|
|
os.Exit(1)
|
|
}
|
|
|
|
default:
|
|
log.Fatal("show requires 1 argument")
|
|
}
|
|
return errSuccess
|
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Omit filesystem information")
|
|
}
|
|
|
|
{
|
|
var flagShort bool
|
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
|
var sc hst.Paths
|
|
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
|
printPs(os.Stdout, time.Now().UTC(), store.NewMulti(msg, sc.RunDirPath), flagShort, flagJSON)
|
|
return errSuccess
|
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
|
}
|
|
|
|
c.Command("version", "Display version information", func(args []string) error { fmt.Println(internal.Version()); return errSuccess })
|
|
c.Command("license", "Show full license text", func(args []string) error { fmt.Println(license); return errSuccess })
|
|
c.Command("template", "Produce a config template", func(args []string) error { encodeJSON(log.Fatal, os.Stdout, false, hst.Template()); return errSuccess })
|
|
c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })
|
|
|
|
return c
|
|
}
|