aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-07-02 20:59:17 +0900
committerOphestra <cat@gensokyo.uk>2025-07-02 20:59:17 +0900
commit255b77d91dc40736b7bbd7d96b22500076b098e5 (patch)
tree2886b18947b6631051ec9fc7f8aaca1269cef125 /cmd
parentf84ec5a3f8d648031bc8e7c4baf6e31d67d2d5a4 (diff)
cmd/hakurei: move command handlers
The hakurei command is a bit ugly since it's also used for validating the command package. This alleviates some of the ugliness. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hakurei/command.go258
-rw-r--r--cmd/hakurei/command_test.go (renamed from cmd/hakurei/main_test.go)0
-rw-r--r--cmd/hakurei/main.go250
3 files changed, 259 insertions, 249 deletions
diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go
new file mode 100644
index 00000000..5e9d5732
--- /dev/null
+++ b/cmd/hakurei/command.go
@@ -0,0 +1,258 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/signal"
+ "os/user"
+ "strconv"
+ "sync"
+ "syscall"
+ "time"
+
+ "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/app"
+ "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/app/instance"
+ "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/state"
+ "git.gensokyo.uk/security/hakurei/command"
+ "git.gensokyo.uk/security/hakurei/dbus"
+ "git.gensokyo.uk/security/hakurei/hst"
+ "git.gensokyo.uk/security/hakurei/internal"
+ "git.gensokyo.uk/security/hakurei/internal/hlog"
+ "git.gensokyo.uk/security/hakurei/system"
+)
+
+func buildCommand(out io.Writer) command.Command {
+ var (
+ flagVerbose bool
+ flagJSON bool
+ )
+ c := command.New(out, log.Printf, "hakurei", func([]string) error { internal.InstallOutput(flagVerbose); 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 { instance.ShimMain(); return errSuccess })
+
+ c.Command("app", "Load app from configuration file", func(args []string) error {
+ if len(args) < 1 {
+ log.Fatal("app requires at least 1 argument")
+ }
+
+ // config extraArgs...
+ config := tryPath(args[0])
+ config.Args = append(config.Args, args[1:]...)
+
+ runApp(config)
+ panic("unreachable")
+ })
+
+ {
+ var (
+ dbusConfigSession string
+ dbusConfigSystem string
+ mpris bool
+ dbusVerbose bool
+
+ fid string
+ aid int
+ groups command.RepeatableFlag
+ homeDir string
+ userName string
+
+ wayland, x11, dBus, pulse bool
+ )
+
+ c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
+ // initialise config from flags
+ config := &hst.Config{
+ ID: fid,
+ Args: args,
+ }
+
+ if aid < 0 || aid > 9999 {
+ log.Fatalf("aid %d out of range", aid)
+ }
+
+ // resolve home/username from os when flag is unset
+ var (
+ passwd *user.User
+ passwdOnce sync.Once
+ passwdFunc = func() {
+ var us string
+ if uid, err := std.Uid(aid); err != nil {
+ hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
+ os.Exit(1)
+ } else {
+ us = strconv.Itoa(uid)
+ }
+
+ if u, err := user.LookupId(us); err != nil {
+ hlog.Verbosef("cannot look up uid %s", us)
+ passwd = &user.User{
+ Uid: us,
+ Gid: us,
+ Username: "chronos",
+ Name: "Hakurei Permissive Default",
+ HomeDir: "/var/empty",
+ }
+ } else {
+ passwd = u
+ }
+ }
+ )
+
+ if homeDir == "os" {
+ passwdOnce.Do(passwdFunc)
+ homeDir = passwd.HomeDir
+ }
+
+ if userName == "chronos" {
+ passwdOnce.Do(passwdFunc)
+ userName = passwd.Username
+ }
+
+ config.Identity = aid
+ config.Groups = groups
+ config.Data = homeDir
+ config.Username = userName
+
+ if wayland {
+ config.Enablements |= system.EWayland
+ }
+ if x11 {
+ config.Enablements |= system.EX11
+ }
+ if dBus {
+ config.Enablements |= system.EDBus
+ }
+ if pulse {
+ config.Enablements |= system.EPulse
+ }
+
+ // parse D-Bus config file from flags if applicable
+ if dBus {
+ if dbusConfigSession == "builtin" {
+ config.SessionBus = dbus.NewConfig(fid, true, mpris)
+ } else {
+ if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
+ log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
+ } else {
+ config.SessionBus = conf
+ }
+ }
+
+ // system bus proxy is optional
+ if dbusConfigSystem != "nil" {
+ if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
+ log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
+ } else {
+ config.SystemBus = conf
+ }
+ }
+
+ // override log from configuration
+ if dbusVerbose {
+ config.SessionBus.Log = true
+ config.SystemBus.Log = true
+ }
+ }
+
+ // invoke app
+ runApp(config)
+ panic("unreachable")
+ }).
+ Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
+ "Path to session bus proxy config file, or \"builtin\" for defaults").
+ Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"),
+ "Path to system bus proxy config file, or \"nil\" to disable").
+ Flag(&mpris, "mpris", command.BoolFlag(false),
+ "Allow owning MPRIS D-Bus path, has no effect if custom config is available").
+ Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false),
+ "Force buffered logging in the D-Bus proxy").
+ Flag(&fid, "id", command.StringFlag(""),
+ "Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
+ Flag(&aid, "a", command.IntFlag(0),
+ "Application identity").
+ Flag(nil, "g", &groups,
+ "Groups inherited by all container processes").
+ Flag(&homeDir, "d", command.StringFlag("os"),
+ "Container home directory").
+ Flag(&userName, "u", command.StringFlag("chronos"),
+ "Passwd user name within sandbox").
+ Flag(&wayland, "wayland", command.BoolFlag(false),
+ "Enable connection to Wayland via security-context-v1").
+ Flag(&x11, "X", command.BoolFlag(false),
+ "Enable direct connection to X11").
+ Flag(&dBus, "dbus", command.BoolFlag(false),
+ "Enable proxied connection to D-Bus").
+ Flag(&pulse, "pulse", command.BoolFlag(false),
+ "Enable direct connection to PulseAudio")
+ }
+
+ var showFlagShort bool
+ c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
+ switch len(args) {
+ case 0: // system
+ printShowSystem(os.Stdout, showFlagShort, flagJSON)
+
+ case 1: // instance
+ name := args[0]
+ config, entry := tryShort(name)
+ if config == nil {
+ config = tryPath(name)
+ }
+ printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON)
+
+ default:
+ log.Fatal("show requires 1 argument")
+ }
+ return errSuccess
+ }).Flag(&showFlagShort, "short", command.BoolFlag(false), "Omit filesystem information")
+
+ var psFlagShort bool
+ c.NewCommand("ps", "List active instances", func(args []string) error {
+ printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath), psFlagShort, flagJSON)
+ return errSuccess
+ }).Flag(&psFlagShort, "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 {
+ printJSON(os.Stdout, false, hst.Template())
+ return errSuccess
+ })
+
+ c.Command("help", "Show this help message", func([]string) error {
+ c.PrintHelp()
+ return errSuccess
+ })
+
+ return c
+}
+
+func runApp(config *hst.Config) {
+ ctx, stop := signal.NotifyContext(context.Background(),
+ syscall.SIGINT, syscall.SIGTERM)
+ defer stop() // unreachable
+ a := instance.MustNew(instance.ISetuid, ctx, std)
+
+ rs := new(app.RunState)
+ if sa, err := a.Seal(config); err != nil {
+ hlog.PrintBaseError(err, "cannot seal app:")
+ internal.Exit(1)
+ } else {
+ internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)))
+ }
+
+ *(*int)(nil) = 0 // not reached
+}
diff --git a/cmd/hakurei/main_test.go b/cmd/hakurei/command_test.go
index 7e2b792e..7e2b792e 100644
--- a/cmd/hakurei/main_test.go
+++ b/cmd/hakurei/command_test.go
diff --git a/cmd/hakurei/main.go b/cmd/hakurei/main.go
index 143ce9c2..4cfe7ebd 100644
--- a/cmd/hakurei/main.go
+++ b/cmd/hakurei/main.go
@@ -4,31 +4,15 @@ package main
//go:generate cp ../../LICENSE .
import (
- "context"
_ "embed"
"errors"
- "fmt"
- "io"
"log"
"os"
- "os/signal"
- "os/user"
- "strconv"
- "sync"
- "syscall"
- "time"
- "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/app"
- "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/app/instance"
- "git.gensokyo.uk/security/hakurei/cmd/hakurei/internal/state"
- "git.gensokyo.uk/security/hakurei/command"
- "git.gensokyo.uk/security/hakurei/dbus"
- "git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal"
"git.gensokyo.uk/security/hakurei/internal/hlog"
"git.gensokyo.uk/security/hakurei/internal/sys"
"git.gensokyo.uk/security/hakurei/sandbox"
- "git.gensokyo.uk/security/hakurei/system"
)
var (
@@ -61,239 +45,7 @@ func main() {
hlog.BeforeExit()
os.Exit(0)
}
+ // this catches faulty command handlers that fail to return before this point
})
log.Fatal("unreachable")
}
-
-func buildCommand(out io.Writer) command.Command {
- var (
- flagVerbose bool
- flagJSON bool
- )
- c := command.New(out, log.Printf, "hakurei", func([]string) error { internal.InstallOutput(flagVerbose); 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 { instance.ShimMain(); return errSuccess })
-
- c.Command("app", "Load app from configuration file", func(args []string) error {
- if len(args) < 1 {
- log.Fatal("app requires at least 1 argument")
- }
-
- // config extraArgs...
- config := tryPath(args[0])
- config.Args = append(config.Args, args[1:]...)
-
- runApp(config)
- panic("unreachable")
- })
-
- {
- var (
- dbusConfigSession string
- dbusConfigSystem string
- mpris bool
- dbusVerbose bool
-
- fid string
- aid int
- groups command.RepeatableFlag
- homeDir string
- userName string
-
- wayland, x11, dBus, pulse bool
- )
-
- c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
- // initialise config from flags
- config := &hst.Config{
- ID: fid,
- Args: args,
- }
-
- if aid < 0 || aid > 9999 {
- log.Fatalf("aid %d out of range", aid)
- }
-
- // resolve home/username from os when flag is unset
- var (
- passwd *user.User
- passwdOnce sync.Once
- passwdFunc = func() {
- var us string
- if uid, err := std.Uid(aid); err != nil {
- hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
- os.Exit(1)
- } else {
- us = strconv.Itoa(uid)
- }
-
- if u, err := user.LookupId(us); err != nil {
- hlog.Verbosef("cannot look up uid %s", us)
- passwd = &user.User{
- Uid: us,
- Gid: us,
- Username: "chronos",
- Name: "Hakurei Permissive Default",
- HomeDir: "/var/empty",
- }
- } else {
- passwd = u
- }
- }
- )
-
- if homeDir == "os" {
- passwdOnce.Do(passwdFunc)
- homeDir = passwd.HomeDir
- }
-
- if userName == "chronos" {
- passwdOnce.Do(passwdFunc)
- userName = passwd.Username
- }
-
- config.Identity = aid
- config.Groups = groups
- config.Data = homeDir
- config.Username = userName
-
- if wayland {
- config.Enablements |= system.EWayland
- }
- if x11 {
- config.Enablements |= system.EX11
- }
- if dBus {
- config.Enablements |= system.EDBus
- }
- if pulse {
- config.Enablements |= system.EPulse
- }
-
- // parse D-Bus config file from flags if applicable
- if dBus {
- if dbusConfigSession == "builtin" {
- config.SessionBus = dbus.NewConfig(fid, true, mpris)
- } else {
- if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
- log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
- } else {
- config.SessionBus = conf
- }
- }
-
- // system bus proxy is optional
- if dbusConfigSystem != "nil" {
- if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
- log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
- } else {
- config.SystemBus = conf
- }
- }
-
- // override log from configuration
- if dbusVerbose {
- config.SessionBus.Log = true
- config.SystemBus.Log = true
- }
- }
-
- // invoke app
- runApp(config)
- panic("unreachable")
- }).
- Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
- "Path to session bus proxy config file, or \"builtin\" for defaults").
- Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"),
- "Path to system bus proxy config file, or \"nil\" to disable").
- Flag(&mpris, "mpris", command.BoolFlag(false),
- "Allow owning MPRIS D-Bus path, has no effect if custom config is available").
- Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false),
- "Force buffered logging in the D-Bus proxy").
- Flag(&fid, "id", command.StringFlag(""),
- "Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
- Flag(&aid, "a", command.IntFlag(0),
- "Application identity").
- Flag(nil, "g", &groups,
- "Groups inherited by all container processes").
- Flag(&homeDir, "d", command.StringFlag("os"),
- "Container home directory").
- Flag(&userName, "u", command.StringFlag("chronos"),
- "Passwd user name within sandbox").
- Flag(&wayland, "wayland", command.BoolFlag(false),
- "Enable connection to Wayland via security-context-v1").
- Flag(&x11, "X", command.BoolFlag(false),
- "Enable direct connection to X11").
- Flag(&dBus, "dbus", command.BoolFlag(false),
- "Enable proxied connection to D-Bus").
- Flag(&pulse, "pulse", command.BoolFlag(false),
- "Enable direct connection to PulseAudio")
- }
-
- var showFlagShort bool
- c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
- switch len(args) {
- case 0: // system
- printShowSystem(os.Stdout, showFlagShort, flagJSON)
-
- case 1: // instance
- name := args[0]
- config, entry := tryShort(name)
- if config == nil {
- config = tryPath(name)
- }
- printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON)
-
- default:
- log.Fatal("show requires 1 argument")
- }
- return errSuccess
- }).Flag(&showFlagShort, "short", command.BoolFlag(false), "Omit filesystem information")
-
- var psFlagShort bool
- c.NewCommand("ps", "List active instances", func(args []string) error {
- printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath), psFlagShort, flagJSON)
- return errSuccess
- }).Flag(&psFlagShort, "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 {
- printJSON(os.Stdout, false, hst.Template())
- return errSuccess
- })
-
- c.Command("help", "Show this help message", func([]string) error {
- c.PrintHelp()
- return errSuccess
- })
-
- return c
-}
-
-func runApp(config *hst.Config) {
- ctx, stop := signal.NotifyContext(context.Background(),
- syscall.SIGINT, syscall.SIGTERM)
- defer stop() // unreachable
- a := instance.MustNew(instance.ISetuid, ctx, std)
-
- rs := new(app.RunState)
- if sa, err := a.Seal(config); err != nil {
- hlog.PrintBaseError(err, "cannot seal app:")
- internal.Exit(1)
- } else {
- internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)))
- }
-
- *(*int)(nil) = 0 // not reached
-}