From 31b7ddd122d54c36edb101d2c8bdf230651f27d4 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 13 Apr 2025 03:23:28 +0900 Subject: fst: improve config The config struct more or less "grew" to what it is today. This change moves things around to make more sense and fixes nonsensical comments describing obsolete behaviour. Signed-off-by: Ophestra --- fst/config.go | 49 ++++++------ fst/container.go | 59 +++++++++++++++ fst/sandbox.go | 63 ---------------- fst/template.go | 130 ++++++++++++++++---------------- fst/template_test.go | 206 +++++++++++++++++++++++++-------------------------- 5 files changed, 254 insertions(+), 253 deletions(-) create mode 100644 fst/container.go delete mode 100644 fst/sandbox.go (limited to 'fst') diff --git a/fst/config.go b/fst/config.go index a56927f0..62c85701 100644 --- a/fst/config.go +++ b/fst/config.go @@ -8,7 +8,7 @@ import ( const Tmp = "/.fortify" -// Config is used to seal an app +// 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 @@ -20,39 +20,40 @@ type Config struct { // final args passed to container init Args []string `json:"args"` - Confinement ConfinementConfig `json:"confinement"` -} + // 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"` -// ConfinementConfig defines fortified child's confinement -type ConfinementConfig struct { - // numerical application id, determines uid in the init namespace - AppID int `json:"app_id"` - // list of supplementary groups to inherit - Groups []string `json:"groups"` // passwd username in container, defaults to passwd name of target uid or chronos Username string `json:"username,omitempty"` - // home directory in container, empty for outer - Inner string `json:"home_inner"` - // home directory in init namespace - Outer string `json:"home"` // absolute path to shell, empty for host shell Shell string `json:"shell,omitempty"` - // abstract sandbox configuration - Sandbox *SandboxConfig `json:"sandbox"` - // extra acl ops, runs after everything else + // 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"` - // reference to a system D-Bus proxy configuration, - // nil value disables system bus proxy - SystemBus *dbus.Config `json:"system_bus,omitempty"` - // reference to a session D-Bus proxy configuration, - // nil value makes session bus proxy assume built-in defaults - SessionBus *dbus.Config `json:"session_bus,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"` - // system resources to expose to the container - Enablements system.Enablement `json:"enablements"` + // 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"` diff --git a/fst/container.go b/fst/container.go new file mode 100644 index 00000000..1190caa3 --- /dev/null +++ b/fst/container.go @@ -0,0 +1,59 @@ +package fst + +import ( + "git.gensokyo.uk/security/fortify/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/fst/sandbox.go b/fst/sandbox.go deleted file mode 100644 index c4936a2e..00000000 --- a/fst/sandbox.go +++ /dev/null @@ -1,63 +0,0 @@ -package fst - -import ( - "git.gensokyo.uk/security/fortify/sandbox/seccomp" -) - -// SandboxConfig describes resources made available to the sandbox. -type ( - SandboxConfig 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"` - // expose main process tty - 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"` - - // expose 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"` - - // 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"` - - // 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 a representation of [sandbox.BindMount]. - 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/fst/template.go b/fst/template.go index 0777c9d2..0ca928a1 100644 --- a/fst/template.go +++ b/fst/template.go @@ -9,7 +9,8 @@ import ( // Template returns a fully populated instance of Config. func Template() *Config { return &Config{ - ID: "org.chromium.Chromium", + ID: "org.chromium.Chromium", + Path: "/run/current-system/sw/bin/chromium", Args: []string{ "chromium", @@ -18,70 +19,73 @@ func Template() *Config { "--enable-features=UseOzonePlatform", "--ozone-platform=wayland", }, - Confinement: ConfinementConfig{ - AppID: 9, - Groups: []string{"video"}, - Username: "chronos", - Outer: "/var/lib/persist/home/org.chromium.Chromium", - Inner: "/var/lib/fortify", - Shell: "/run/current-system/sw/bin/zsh", - Sandbox: &SandboxConfig{ - Hostname: "localhost", - Devel: true, - Userns: true, - Net: true, - Device: true, - Seccomp: seccomp.FilterMultiarch, - Tty: true, - Multiarch: true, - MapRealUID: true, - DirectWayland: false, - // 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/fortify/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"}, - }, - ExtraPerms: []*ExtraPermConfig{ - {Path: "/var/lib/fortify/u0", Ensure: true, Execute: true}, - {Path: "/var/lib/fortify/u0/org.chromium.Chromium", Read: true, Write: true, Execute: 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, + + 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/fortify/u0/org.chromium.Chromium", + Dir: "/data/data/org.chromium.Chromium", + ExtraPerms: []*ExtraPermConfig{ + {Path: "/var/lib/fortify/u0", Ensure: true, Execute: true}, + {Path: "/var/lib/fortify/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", }, - 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, + Filesystem: []*FilesystemConfig{ + {Src: "/nix/store"}, + {Src: "/run/current-system"}, + {Src: "/run/opengl-driver"}, + {Src: "/var/db/nix-channels"}, + {Src: "/var/lib/fortify/u0/org.chromium.Chromium", + Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true}, + {Src: "/dev/dri", Device: true}, }, - Enablements: system.EWayland | system.EDBus | system.EPulse, + Link: [][2]string{{"/run/user/65534", "/run/user/150"}}, + Etc: "/etc", + AutoEtc: true, + Cover: []string{"/var/run/nscd"}, }, } } diff --git a/fst/template_test.go b/fst/template_test.go index 0ec2dae2..c7fd7804 100644 --- a/fst/template_test.go +++ b/fst/template_test.go @@ -18,116 +18,116 @@ func TestTemplate(t *testing.T) { "--enable-features=UseOzonePlatform", "--ozone-platform=wayland" ], - "confinement": { - "app_id": 9, - "groups": [ - "video" + "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" ], - "username": "chronos", - "home_inner": "/var/lib/fortify", - "home": "/var/lib/persist/home/org.chromium.Chromium", - "shell": "/run/current-system/sw/bin/zsh", - "sandbox": { - "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/fortify/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" - ] + "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/fortify/u0/org.chromium.Chromium", + "dir": "/data/data/org.chromium.Chromium", + "extra_perms": [ + { + "ensure": true, + "path": "/var/lib/fortify/u0", + "x": true }, - "extra_perms": [ + { + "path": "/var/lib/fortify/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": [ { - "ensure": true, - "path": "/var/lib/fortify/u0", - "x": true + "src": "/nix/store" }, { - "path": "/var/lib/fortify/u0/org.chromium.Chromium", - "r": true, - "w": true, - "x": true - } - ], - "system_bus": { - "see": null, - "talk": [ - "org.bluez", - "org.freedesktop.Avahi", - "org.freedesktop.UPower" - ], - "own": null, - "call": null, - "broadcast": null, - "filter": true - }, - "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.*": "*" + "src": "/run/current-system" }, - "broadcast": { - "org.freedesktop.portal.*": "@/org/freedesktop/portal/*" + { + "src": "/run/opengl-driver" }, - "filter": true - }, - "enablements": 13 + { + "src": "/var/db/nix-channels" + }, + { + "dst": "/data/data/org.chromium.Chromium", + "src": "/var/lib/fortify/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" + ] } }` -- cgit v1.3.1