aboutsummaryrefslogtreecommitdiffhomepage
path: root/hst
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-06-25 03:59:52 +0900
committerOphestra <cat@gensokyo.uk>2025-06-25 04:57:41 +0900
commit87e008d56de974947ebb99c2cc40b25d3c2cf43e (patch)
tree31791911e5226d6ec04e3fac7d91b0bf53e63aa5 /hst
parent399207321265307bb15f37d867f9370cd51c82a8 (diff)
treewide: rename to hakurei
Fortify makes little sense for a container tool. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'hst')
-rw-r--r--hst/config.go83
-rw-r--r--hst/container.go59
-rw-r--r--hst/info.go5
-rw-r--r--hst/template.go91
-rw-r--r--hst/template_test.go140
5 files changed, 378 insertions, 0 deletions
diff --git a/hst/config.go b/hst/config.go
new file mode 100644
index 00000000..1c3390bf
--- /dev/null
+++ b/hst/config.go
@@ -0,0 +1,83 @@
+// Package hst exports shared types for invoking hakurei.
+package hst
+
+import (
+ "git.gensokyo.uk/security/hakurei/dbus"
+ "git.gensokyo.uk/security/hakurei/system"
+)
+
+const Tmp = "/.hakurei"
+
+// Config is used to seal an app implementation.
+type Config struct {
+ // reverse-DNS style arbitrary identifier string from config;
+ // passed to wayland security-context-v1 as application ID
+ // and used as part of defaults in dbus session proxy
+ ID string `json:"id"`
+
+ // absolute path to executable file
+ Path string `json:"path,omitempty"`
+ // final args passed to container init
+ Args []string `json:"args"`
+
+ // system services to make available in the container
+ Enablements system.Enablement `json:"enablements"`
+
+ // session D-Bus proxy configuration;
+ // nil makes session bus proxy assume built-in defaults
+ SessionBus *dbus.Config `json:"session_bus,omitempty"`
+ // system D-Bus proxy configuration;
+ // nil disables system bus proxy
+ SystemBus *dbus.Config `json:"system_bus,omitempty"`
+ // direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
+ // and the bare socket is mounted to the sandbox
+ DirectWayland bool `json:"direct_wayland,omitempty"`
+
+ // passwd username in container, defaults to passwd name of target uid or chronos
+ Username string `json:"username,omitempty"`
+ // absolute path to shell, empty for host shell
+ Shell string `json:"shell,omitempty"`
+ // absolute path to home directory in the init mount namespace
+ Data string `json:"data"`
+ // directory to enter and use as home in the container mount namespace, empty for Data
+ Dir string `json:"dir"`
+ // extra acl ops, dispatches before container init
+ ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
+
+ // numerical application id, used for init user namespace credentials
+ Identity int `json:"identity"`
+ // list of supplementary groups inherited by container processes
+ Groups []string `json:"groups"`
+
+ // abstract container configuration baseline
+ Container *ContainerConfig `json:"container"`
+}
+
+// ExtraPermConfig describes an acl update op.
+type ExtraPermConfig struct {
+ Ensure bool `json:"ensure,omitempty"`
+ Path string `json:"path"`
+ Read bool `json:"r,omitempty"`
+ Write bool `json:"w,omitempty"`
+ Execute bool `json:"x,omitempty"`
+}
+
+func (e *ExtraPermConfig) String() string {
+ buf := make([]byte, 0, 5+len(e.Path))
+ buf = append(buf, '-', '-', '-')
+ if e.Ensure {
+ buf = append(buf, '+')
+ }
+ buf = append(buf, ':')
+ buf = append(buf, []byte(e.Path)...)
+ if e.Read {
+ buf[0] = 'r'
+ }
+ if e.Write {
+ buf[1] = 'w'
+ }
+ if e.Execute {
+ buf[2] = 'x'
+ }
+ return string(buf)
+}
diff --git a/hst/container.go b/hst/container.go
new file mode 100644
index 00000000..796b82e8
--- /dev/null
+++ b/hst/container.go
@@ -0,0 +1,59 @@
+package hst
+
+import (
+ "git.gensokyo.uk/security/hakurei/sandbox/seccomp"
+)
+
+type (
+ // ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
+ ContainerConfig struct {
+ // container hostname
+ Hostname string `json:"hostname,omitempty"`
+
+ // extra seccomp flags
+ Seccomp seccomp.FilterOpts `json:"seccomp"`
+ // allow ptrace and friends
+ Devel bool `json:"devel,omitempty"`
+ // allow userns creation in container
+ Userns bool `json:"userns,omitempty"`
+ // share host net namespace
+ Net bool `json:"net,omitempty"`
+ // allow dangerous terminal I/O
+ 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 user namespace
+ MapRealUID bool `json:"map_real_uid"`
+
+ // pass through all devices
+ Device bool `json:"device,omitempty"`
+ // container host filesystem bind mounts
+ Filesystem []*FilesystemConfig `json:"filesystem"`
+ // create symlinks inside container filesystem
+ Link [][2]string `json:"symlink"`
+
+ // read-only /etc directory
+ Etc string `json:"etc,omitempty"`
+ // automatically set up /etc symlinks
+ AutoEtc bool `json:"auto_etc"`
+ // cover these paths or create them if they do not already exist
+ Cover []string `json:"cover"`
+ }
+
+ // FilesystemConfig is an abstract representation of a bind mount.
+ FilesystemConfig struct {
+ // mount point in container, same as src if empty
+ Dst string `json:"dst,omitempty"`
+ // host filesystem path to make available to the container
+ Src string `json:"src"`
+ // do not mount filesystem read-only
+ Write bool `json:"write,omitempty"`
+ // do not disable device files
+ Device bool `json:"dev,omitempty"`
+ // fail if the bind mount cannot be established for any reason
+ Must bool `json:"require,omitempty"`
+ }
+)
diff --git a/hst/info.go b/hst/info.go
new file mode 100644
index 00000000..d887cf2f
--- /dev/null
+++ b/hst/info.go
@@ -0,0 +1,5 @@
+package hst
+
+type Info struct {
+ User int `json:"user"`
+}
diff --git a/hst/template.go b/hst/template.go
new file mode 100644
index 00000000..4d054a49
--- /dev/null
+++ b/hst/template.go
@@ -0,0 +1,91 @@
+package hst
+
+import (
+ "git.gensokyo.uk/security/hakurei/dbus"
+ "git.gensokyo.uk/security/hakurei/sandbox/seccomp"
+ "git.gensokyo.uk/security/hakurei/system"
+)
+
+// Template returns a fully populated instance of Config.
+func Template() *Config {
+ return &Config{
+ ID: "org.chromium.Chromium",
+
+ Path: "/run/current-system/sw/bin/chromium",
+ Args: []string{
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland",
+ },
+
+ Enablements: system.EWayland | system.EDBus | system.EPulse,
+
+ SessionBus: &dbus.Config{
+ See: nil,
+ Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
+ "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
+ Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
+ "org.mpris.MediaPlayer2.chromium.*"},
+ Call: map[string]string{"org.freedesktop.portal.*": "*"},
+ Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
+ Log: false,
+ Filter: true,
+ },
+ SystemBus: &dbus.Config{
+ See: nil,
+ Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
+ Own: nil,
+ Call: nil,
+ Broadcast: nil,
+ Log: false,
+ Filter: true,
+ },
+ DirectWayland: false,
+
+ Username: "chronos",
+ Shell: "/run/current-system/sw/bin/zsh",
+ Data: "/var/lib/hakurei/u0/org.chromium.Chromium",
+ Dir: "/data/data/org.chromium.Chromium",
+ ExtraPerms: []*ExtraPermConfig{
+ {Path: "/var/lib/hakurei/u0", Ensure: true, Execute: true},
+ {Path: "/var/lib/hakurei/u0/org.chromium.Chromium", Read: true, Write: true, Execute: true},
+ },
+
+ Identity: 9,
+ Groups: []string{"video", "dialout", "plugdev"},
+
+ Container: &ContainerConfig{
+ Hostname: "localhost",
+ Devel: true,
+ Userns: true,
+ Net: true,
+ Device: true,
+ Seccomp: seccomp.FilterMultiarch,
+ Tty: true,
+ Multiarch: true,
+ MapRealUID: true,
+ // example API credentials pulled from Google Chrome
+ // DO NOT USE THESE IN A REAL BROWSER
+ Env: map[string]string{
+ "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
+ "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
+ "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
+ },
+ Filesystem: []*FilesystemConfig{
+ {Src: "/nix/store"},
+ {Src: "/run/current-system"},
+ {Src: "/run/opengl-driver"},
+ {Src: "/var/db/nix-channels"},
+ {Src: "/var/lib/hakurei/u0/org.chromium.Chromium",
+ Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
+ {Src: "/dev/dri", Device: true},
+ },
+ Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
+ Etc: "/etc",
+ AutoEtc: true,
+ Cover: []string{"/var/run/nscd"},
+ },
+ }
+}
diff --git a/hst/template_test.go b/hst/template_test.go
new file mode 100644
index 00000000..74d258de
--- /dev/null
+++ b/hst/template_test.go
@@ -0,0 +1,140 @@
+package hst_test
+
+import (
+ "encoding/json"
+ "testing"
+
+ "git.gensokyo.uk/security/hakurei/hst"
+)
+
+func TestTemplate(t *testing.T) {
+ const want = `{
+ "id": "org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
+ ],
+ "enablements": 13,
+ "session_bus": {
+ "see": null,
+ "talk": [
+ "org.freedesktop.Notifications",
+ "org.freedesktop.FileManager1",
+ "org.freedesktop.ScreenSaver",
+ "org.freedesktop.secrets",
+ "org.kde.kwalletd5",
+ "org.kde.kwalletd6",
+ "org.gnome.SessionManager"
+ ],
+ "own": [
+ "org.chromium.Chromium.*",
+ "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
+ "org.mpris.MediaPlayer2.chromium.*"
+ ],
+ "call": {
+ "org.freedesktop.portal.*": "*"
+ },
+ "broadcast": {
+ "org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
+ },
+ "filter": true
+ },
+ "system_bus": {
+ "see": null,
+ "talk": [
+ "org.bluez",
+ "org.freedesktop.Avahi",
+ "org.freedesktop.UPower"
+ ],
+ "own": null,
+ "call": null,
+ "broadcast": null,
+ "filter": true
+ },
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "data": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "dir": "/data/data/org.chromium.Chromium",
+ "extra_perms": [
+ {
+ "ensure": true,
+ "path": "/var/lib/hakurei/u0",
+ "x": true
+ },
+ {
+ "path": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "r": true,
+ "w": true,
+ "x": true
+ }
+ ],
+ "identity": 9,
+ "groups": [
+ "video",
+ "dialout",
+ "plugdev"
+ ],
+ "container": {
+ "hostname": "localhost",
+ "seccomp": 32,
+ "devel": true,
+ "userns": true,
+ "net": 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": [
+ {
+ "src": "/nix/store"
+ },
+ {
+ "src": "/run/current-system"
+ },
+ {
+ "src": "/run/opengl-driver"
+ },
+ {
+ "src": "/var/db/nix-channels"
+ },
+ {
+ "dst": "/data/data/org.chromium.Chromium",
+ "src": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "write": true,
+ "require": true
+ },
+ {
+ "src": "/dev/dri",
+ "dev": true
+ }
+ ],
+ "symlink": [
+ [
+ "/run/user/65534",
+ "/run/user/150"
+ ]
+ ],
+ "etc": "/etc",
+ "auto_etc": true,
+ "cover": [
+ "/var/run/nscd"
+ ]
+ }
+}`
+
+ 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)
+ }
+}