From 87e008d56de974947ebb99c2cc40b25d3c2cf43e Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 25 Jun 2025 03:59:52 +0900 Subject: treewide: rename to hakurei Fortify makes little sense for a container tool. Signed-off-by: Ophestra --- hst/config.go | 83 ++++++++++++++++++++++++++++++ hst/container.go | 59 ++++++++++++++++++++++ hst/info.go | 5 ++ hst/template.go | 91 +++++++++++++++++++++++++++++++++ hst/template_test.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 378 insertions(+) create mode 100644 hst/config.go create mode 100644 hst/container.go create mode 100644 hst/info.go create mode 100644 hst/template.go create mode 100644 hst/template_test.go (limited to 'hst') 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) + } +} -- cgit v1.3.1