hst: improve doc comments
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m26s
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m26s
These now read a lot better both in source and on pkgsite. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
122
hst/config.go
122
hst/config.go
@@ -8,74 +8,97 @@ import (
|
|||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config configures an application container, implemented in internal/app.
|
// Config configures an application container.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Reverse-DNS style configured arbitrary identifier string.
|
// Reverse-DNS style configured arbitrary identifier string.
|
||||||
// Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy.
|
//
|
||||||
|
// This value is passed as is to Wayland security-context-v1 and used as
|
||||||
|
// part of defaults in D-Bus session proxy. The zero value causes a default
|
||||||
|
// value to be derived from the container instance.
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
|
|
||||||
// System services to make available in the container.
|
// System services to make available in the container.
|
||||||
Enablements *Enablements `json:"enablements,omitempty"`
|
Enablements *Enablements `json:"enablements,omitempty"`
|
||||||
|
|
||||||
// Session D-Bus proxy configuration.
|
// Session D-Bus proxy configuration.
|
||||||
// If set to nil, session bus proxy assume built-in defaults.
|
//
|
||||||
|
// Has no effect if [EDBus] but is not set in Enablements. The zero value
|
||||||
|
// assumes built-in defaults derived from ID.
|
||||||
SessionBus *BusConfig `json:"session_bus,omitempty"`
|
SessionBus *BusConfig `json:"session_bus,omitempty"`
|
||||||
// System D-Bus proxy configuration.
|
// System D-Bus proxy configuration.
|
||||||
// If set to nil, system bus proxy is disabled.
|
//
|
||||||
|
// Has no effect if [EDBus] but is not set in Enablements. The zero value
|
||||||
|
// disables system bus proxy.
|
||||||
SystemBus *BusConfig `json:"system_bus,omitempty"`
|
SystemBus *BusConfig `json:"system_bus,omitempty"`
|
||||||
|
|
||||||
// Direct access to wayland socket, no attempt is made to attach security-context-v1
|
// Direct access to Wayland socket, no attempt is made to attach
|
||||||
// and the bare socket is made available to the container.
|
// security-context-v1 and the bare socket is made available to the
|
||||||
|
// container.
|
||||||
//
|
//
|
||||||
// This option is unsupported and most likely enables full control over the Wayland
|
// This option is unsupported and will most likely enable full control over
|
||||||
// session. Do not set this to true unless you are sure you know what you are doing.
|
// the Wayland session from within the container. Do not set this to true
|
||||||
|
// unless you are sure you know what you are doing.
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
// Direct access to the PipeWire socket established via SecurityContext::Create, no
|
|
||||||
// attempt is made to start the pipewire-pulse server.
|
// Direct access to the PipeWire socket established via SecurityContext::Create,
|
||||||
|
// no attempt is made to start the pipewire-pulse server.
|
||||||
//
|
//
|
||||||
// The SecurityContext machinery is fatally flawed, it blindly sets read and execute
|
// The SecurityContext machinery is fatally flawed, it unconditionally sets
|
||||||
// bits on all objects for clients with the lowest achievable privilege level (by
|
// read and execute bits on all objects for clients with the lowest achievable
|
||||||
// setting PW_KEY_ACCESS to "restricted"). This enables them to call any method
|
// privilege level (by setting PW_KEY_ACCESS to "restricted" or by satisfying
|
||||||
// targeting any object, and since Registry::Destroy checks for the read and execute bit,
|
// all conditions of [the /.flatpak-info hack]). This enables them to call
|
||||||
// allows the destruction of any object other than PW_ID_CORE as well. This behaviour
|
// any method targeting any object, and since Registry::Destroy checks for
|
||||||
// is implemented separately in media-session and wireplumber, with the wireplumber
|
// the read and execute bit, allows the destruction of any object other than
|
||||||
// implementation in Lua via an embedded Lua vm. In all known setups, wireplumber is
|
// PW_ID_CORE as well.
|
||||||
// in use, and there is no known way to change its behaviour and set permissions
|
|
||||||
// differently without replacing the Lua script. Also, since PipeWire relies on these
|
|
||||||
// permissions to work, reducing them is not possible.
|
|
||||||
//
|
//
|
||||||
// Currently, the only other sandboxed use case is flatpak, which is not aware of
|
// This behaviour is implemented separately in media-session and wireplumber,
|
||||||
// PipeWire and blindly exposes the bare PulseAudio socket to the container (behaves
|
// with the wireplumber implementation in Lua via an embedded Lua vm. In all
|
||||||
// like DirectPulse). This socket is backed by the pipewire-pulse compatibility daemon,
|
// known setups, wireplumber is in use, and in that case, no option for
|
||||||
// which obtains client pid via the SO_PEERCRED option. The PipeWire daemon, pipewire-pulse
|
// configuring this behaviour exists, without replacing the Lua script.
|
||||||
// daemon and the session manager daemon then separately performs the /.flatpak-info hack
|
// Also, since PipeWire relies on these permissions to work, reducing them
|
||||||
// described in https://git.gensokyo.uk/security/hakurei/issues/21. Under such use case,
|
// was never possible in the first place.
|
||||||
// since the client has no direct access to PipeWire, insecure parts of the protocol are
|
|
||||||
// obscured by pipewire-pulse simply not implementing them, and thus hiding the flaws
|
|
||||||
// described above.
|
|
||||||
//
|
//
|
||||||
// Hakurei does not rely on the /.flatpak-info hack. Instead, a socket is sets up via
|
// Currently, the only other sandboxed use case is flatpak, which is not
|
||||||
// SecurityContext. A pipewire-pulse server connected through it achieves the same
|
// aware of PipeWire and blindly exposes the bare PulseAudio socket to the
|
||||||
// permissions as flatpak does via the /.flatpak-info hack and is maintained for the
|
// container (behaves like DirectPulse). This socket is backed by the
|
||||||
// life of the container.
|
// pipewire-pulse compatibility daemon, which obtains client pid via the
|
||||||
|
// SO_PEERCRED option. The PipeWire daemon, pipewire-pulse daemon and the
|
||||||
|
// session manager daemon then separately performs [the /.flatpak-info hack].
|
||||||
|
// Under such use case, since the client has no direct access to PipeWire,
|
||||||
|
// insecure parts of the protocol are obscured by the absence of an
|
||||||
|
// equivalent API in PulseAudio, or pipewire-pulse simply not implementing
|
||||||
|
// them.
|
||||||
|
//
|
||||||
|
// Hakurei does not rely on [the /.flatpak-info hack]. Instead, a socket is
|
||||||
|
// sets up via SecurityContext. A pipewire-pulse server connected through it
|
||||||
|
// achieves the same permissions as flatpak does via [the /.flatpak-info hack]
|
||||||
|
// and is maintained for the life of the container.
|
||||||
|
//
|
||||||
|
// This option is unsupported and enables a denial-of-service attack as the
|
||||||
|
// sandboxed client is able to destroy any client object and thus
|
||||||
|
// disconnecting them from PipeWire, or destroy the SecurityContext object,
|
||||||
|
// preventing any further container creation.
|
||||||
//
|
//
|
||||||
// This option is unsupported and enables a denial-of-service attack as the sandboxed
|
|
||||||
// client is able to destroy any client object and thus disconnecting them from PipeWire,
|
|
||||||
// or destroy the SecurityContext object preventing any further container creation.
|
|
||||||
// Do not set this to true, it is insecure under any configuration.
|
// Do not set this to true, it is insecure under any configuration.
|
||||||
DirectPipeWire bool `json:"direct_pipewire,omitempty"`
|
|
||||||
// Direct access to PulseAudio socket, no attempt is made to establish pipewire-pulse
|
|
||||||
// server via a PipeWire socket with a SecurityContext attached and the bare socket
|
|
||||||
// is made available to the container.
|
|
||||||
//
|
//
|
||||||
// This option is unsupported and enables arbitrary code execution as the PulseAudio
|
// [the /.flatpak-info hack]: https://git.gensokyo.uk/security/hakurei/issues/21
|
||||||
// server. Do not set this to true, it is insecure under any configuration.
|
DirectPipeWire bool `json:"direct_pipewire,omitempty"`
|
||||||
|
|
||||||
|
// Direct access to PulseAudio socket, no attempt is made to establish
|
||||||
|
// pipewire-pulse server via a PipeWire socket with a SecurityContext
|
||||||
|
// attached, and the bare socket is made available to the container.
|
||||||
|
//
|
||||||
|
// This option is unsupported and enables arbitrary code execution as the
|
||||||
|
// PulseAudio server.
|
||||||
|
//
|
||||||
|
// Do not set this to true, it is insecure under any configuration.
|
||||||
DirectPulse bool `json:"direct_pulse,omitempty"`
|
DirectPulse bool `json:"direct_pulse,omitempty"`
|
||||||
|
|
||||||
// Extra acl updates to perform before setuid.
|
// Extra acl updates to perform before setuid.
|
||||||
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
|
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||||
|
|
||||||
// Numerical application id, passed to hsu, used to derive init user namespace credentials.
|
// Numerical application id, passed to hsu, used to derive init user
|
||||||
|
// namespace credentials.
|
||||||
Identity int `json:"identity"`
|
Identity int `json:"identity"`
|
||||||
// Init user namespace supplementary groups inherited by all container processes.
|
// Init user namespace supplementary groups inherited by all container processes.
|
||||||
Groups []string `json:"groups"`
|
Groups []string `json:"groups"`
|
||||||
@@ -85,17 +108,20 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any
|
// ErrConfigNull is returned by [Config.Validate] for an invalid configuration
|
||||||
// field that must not be null.
|
// that contains a null value for any field that must not be null.
|
||||||
ErrConfigNull = errors.New("unexpected null in config")
|
ErrConfigNull = errors.New("unexpected null in config")
|
||||||
|
|
||||||
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value.
|
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds
|
||||||
|
// [Config.Identity] value.
|
||||||
ErrIdentityBounds = errors.New("identity out of bounds")
|
ErrIdentityBounds = errors.New("identity out of bounds")
|
||||||
|
|
||||||
// ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL.
|
// ErrEnviron is returned by [Config.Validate] if an environment variable
|
||||||
|
// name contains '=' or NUL.
|
||||||
ErrEnviron = errors.New("invalid environment variable name")
|
ErrEnviron = errors.New("invalid environment variable name")
|
||||||
|
|
||||||
// ErrInsecure is returned by [Config.Validate] if the configuration is considered insecure.
|
// ErrInsecure is returned by [Config.Validate] if the configuration is
|
||||||
|
// considered insecure.
|
||||||
ErrInsecure = errors.New("configuration is insecure")
|
ErrInsecure = errors.New("configuration is insecure")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,18 +16,20 @@ const PrivateTmp = "/.hakurei"
|
|||||||
var AbsPrivateTmp = check.MustAbs(PrivateTmp)
|
var AbsPrivateTmp = check.MustAbs(PrivateTmp)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// WaitDelayDefault is used when WaitDelay has its zero value.
|
// WaitDelayDefault is used when WaitDelay has the zero value.
|
||||||
WaitDelayDefault = 5 * time.Second
|
WaitDelayDefault = 5 * time.Second
|
||||||
// WaitDelayMax is used if WaitDelay exceeds its value.
|
// WaitDelayMax is used when WaitDelay exceeds its value.
|
||||||
WaitDelayMax = 30 * time.Second
|
WaitDelayMax = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ExitFailure is returned if the container fails to start.
|
// ExitFailure is returned if the container fails to start.
|
||||||
ExitFailure = iota + 1
|
ExitFailure = iota + 1
|
||||||
// ExitCancel is returned if the container is terminated by a shim-directed signal which cancels its context.
|
// ExitCancel is returned if the container is terminated by a shim-directed
|
||||||
|
// signal which cancels its context.
|
||||||
ExitCancel
|
ExitCancel
|
||||||
// ExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
// ExitOrphan is returned when the shim is orphaned before priv side process
|
||||||
|
// delivers a signal.
|
||||||
ExitOrphan
|
ExitOrphan
|
||||||
|
|
||||||
// ExitRequest is returned when the priv side process requests shim exit.
|
// ExitRequest is returned when the priv side process requests shim exit.
|
||||||
@@ -38,10 +40,12 @@ const (
|
|||||||
type Flags uintptr
|
type Flags uintptr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
|
// FMultiarch unblocks system calls required for multiarch to work on
|
||||||
|
// multiarch-enabled targets (amd64, arm64).
|
||||||
FMultiarch Flags = 1 << iota
|
FMultiarch Flags = 1 << iota
|
||||||
|
|
||||||
// FSeccompCompat changes emitted seccomp filter programs to be identical to that of Flatpak.
|
// FSeccompCompat changes emitted seccomp filter programs to be identical to
|
||||||
|
// that of Flatpak in enabled rulesets.
|
||||||
FSeccompCompat
|
FSeccompCompat
|
||||||
// FDevel unblocks ptrace and friends.
|
// FDevel unblocks ptrace and friends.
|
||||||
FDevel
|
FDevel
|
||||||
@@ -54,12 +58,15 @@ const (
|
|||||||
// FTty unblocks dangerous terminal I/O (faking input).
|
// FTty unblocks dangerous terminal I/O (faking input).
|
||||||
FTty
|
FTty
|
||||||
|
|
||||||
// FMapRealUID maps the target user uid to the privileged user uid in the container user namespace.
|
// FMapRealUID maps the target user uid to the privileged user uid in the
|
||||||
// Some programs fail to connect to dbus session running as a different uid,
|
// container user namespace.
|
||||||
// this option works around it by mapping priv-side caller uid in container.
|
//
|
||||||
|
// Some programs fail to connect to dbus session running as a different uid,
|
||||||
|
// this option works around it by mapping priv-side caller uid in container.
|
||||||
FMapRealUID
|
FMapRealUID
|
||||||
|
|
||||||
// FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
// FDevice mount /dev/ from the init mount namespace as is in the container
|
||||||
|
// mount namespace.
|
||||||
FDevice
|
FDevice
|
||||||
|
|
||||||
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
||||||
@@ -112,30 +119,37 @@ func (flags Flags) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerConfig describes the container configuration to be applied to an underlying [container].
|
// ContainerConfig describes the container configuration to be applied to an
|
||||||
|
// underlying [container]. It is validated by [Config.Validate].
|
||||||
type ContainerConfig struct {
|
type ContainerConfig struct {
|
||||||
// Container UTS namespace hostname.
|
// Container UTS namespace hostname.
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
|
||||||
// Duration in nanoseconds to wait for after interrupting the initial process.
|
// Duration in nanoseconds to wait for after interrupting the initial process.
|
||||||
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax].
|
//
|
||||||
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
|
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than
|
||||||
|
// [WaitDelayMax]. Values lesser than zero is equivalent to zero, bypassing
|
||||||
|
// [WaitDelayDefault].
|
||||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||||
|
|
||||||
// Initial process environment variables.
|
// Initial process environment variables.
|
||||||
Env map[string]string `json:"env"`
|
Env map[string]string `json:"env"`
|
||||||
|
|
||||||
/* Container mount points.
|
// Container mount points.
|
||||||
|
//
|
||||||
If the first element targets /, it is inserted early and excluded from path hiding. */
|
// If the first element targets /, it is inserted early and excluded from
|
||||||
|
// path hiding. Otherwise, an anonymous instance of tmpfs is set up on /.
|
||||||
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
||||||
|
|
||||||
// String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
|
// String used as the username of the emulated user, validated against the
|
||||||
|
// default NAME_REGEX from adduser.
|
||||||
|
//
|
||||||
// Defaults to passwd name of target uid or chronos.
|
// Defaults to passwd name of target uid or chronos.
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
// Pathname of shell in the container filesystem to use for the emulated user.
|
// Pathname of shell in the container filesystem to use for the emulated user.
|
||||||
Shell *check.Absolute `json:"shell"`
|
Shell *check.Absolute `json:"shell"`
|
||||||
// Directory in the container filesystem to enter and use as the home directory of the emulated user.
|
// Directory in the container filesystem to enter and use as the home
|
||||||
|
// directory of the emulated user.
|
||||||
Home *check.Absolute `json:"home"`
|
Home *check.Absolute `json:"home"`
|
||||||
|
|
||||||
// Pathname to executable file in the container filesystem.
|
// Pathname to executable file in the container filesystem.
|
||||||
@@ -148,6 +162,7 @@ type ContainerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||||
|
//
|
||||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||||
type ContainerConfigF ContainerConfig
|
type ContainerConfigF ContainerConfig
|
||||||
|
|
||||||
|
|||||||
46
hst/dbus.go
46
hst/dbus.go
@@ -5,8 +5,26 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BadInterfaceError is returned when Interface fails an undocumented check in xdg-dbus-proxy,
|
// BadInterfaceError is returned when Interface fails an undocumented check in
|
||||||
// which would have cause a silent failure.
|
// xdg-dbus-proxy, which would have cause a silent failure.
|
||||||
|
//
|
||||||
|
// xdg-dbus-proxy fails without output when this condition is not met:
|
||||||
|
//
|
||||||
|
// char *dot = strrchr (filter->interface, '.');
|
||||||
|
// if (dot != NULL)
|
||||||
|
// {
|
||||||
|
// *dot = 0;
|
||||||
|
// if (strcmp (dot + 1, "*") != 0)
|
||||||
|
// filter->member = g_strdup (dot + 1);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// trim ".*" since they are removed before searching for '.':
|
||||||
|
//
|
||||||
|
// if (g_str_has_suffix (name, ".*"))
|
||||||
|
// {
|
||||||
|
// name[strlen (name) - 2] = 0;
|
||||||
|
// wildcard = TRUE;
|
||||||
|
// }
|
||||||
type BadInterfaceError struct {
|
type BadInterfaceError struct {
|
||||||
// Interface is the offending interface string.
|
// Interface is the offending interface string.
|
||||||
Interface string
|
Interface string
|
||||||
@@ -19,7 +37,8 @@ func (e *BadInterfaceError) Error() string {
|
|||||||
if e == nil {
|
if e == nil {
|
||||||
return "<nil>"
|
return "<nil>"
|
||||||
}
|
}
|
||||||
return "bad interface string " + strconv.Quote(e.Interface) + " in " + e.Segment + " bus configuration"
|
return "bad interface string " + strconv.Quote(e.Interface) +
|
||||||
|
" in " + e.Segment + " bus configuration"
|
||||||
}
|
}
|
||||||
|
|
||||||
// BusConfig configures the xdg-dbus-proxy process.
|
// BusConfig configures the xdg-dbus-proxy process.
|
||||||
@@ -76,31 +95,14 @@ func (c *BusConfig) Interfaces(yield func(string) bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckInterfaces checks for invalid interface strings based on an undocumented check in xdg-dbus-error,
|
// CheckInterfaces checks for invalid interface strings based on an undocumented
|
||||||
// returning [BadInterfaceError] if one is encountered.
|
// check in xdg-dbus-error, returning [BadInterfaceError] if one is encountered.
|
||||||
func (c *BusConfig) CheckInterfaces(segment string) error {
|
func (c *BusConfig) CheckInterfaces(segment string) error {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for iface := range c.Interfaces {
|
for iface := range c.Interfaces {
|
||||||
/*
|
|
||||||
xdg-dbus-proxy fails without output when this condition is not met:
|
|
||||||
char *dot = strrchr (filter->interface, '.');
|
|
||||||
if (dot != NULL)
|
|
||||||
{
|
|
||||||
*dot = 0;
|
|
||||||
if (strcmp (dot + 1, "*") != 0)
|
|
||||||
filter->member = g_strdup (dot + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
trim ".*" since they are removed before searching for '.':
|
|
||||||
if (g_str_has_suffix (name, ".*"))
|
|
||||||
{
|
|
||||||
name[strlen (name) - 2] = 0;
|
|
||||||
wildcard = TRUE;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 {
|
if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 {
|
||||||
return &BadInterfaceError{iface, segment}
|
return &BadInterfaceError{iface, segment}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ import (
|
|||||||
type Enablement byte
|
type Enablement byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EWayland exposes a wayland pathname socket via security-context-v1.
|
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
||||||
EWayland Enablement = 1 << iota
|
EWayland Enablement = 1 << iota
|
||||||
// EX11 adds the target user via X11 ChangeHosts and exposes the X11 pathname socket.
|
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
||||||
|
// pathname socket.
|
||||||
EX11
|
EX11
|
||||||
// EDBus enables the per-container xdg-dbus-proxy daemon.
|
// EDBus enables the per-container xdg-dbus-proxy daemon.
|
||||||
EDBus
|
EDBus
|
||||||
// EPipeWire exposes a pipewire pathname socket via SecurityContext.
|
// EPipeWire exposes a pipewire pathname socket via SecurityContext.
|
||||||
EPipeWire
|
EPipeWire
|
||||||
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket.
|
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the
|
||||||
|
// PulseAudio socket.
|
||||||
EPulse
|
EPulse
|
||||||
|
|
||||||
// EM is a noop.
|
// EM is a noop.
|
||||||
|
|||||||
15
hst/fs.go
15
hst/fs.go
@@ -24,7 +24,8 @@ type FilesystemConfig interface {
|
|||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Ops interface enables [FilesystemConfig] to queue container ops without depending on the container package.
|
// The Ops interface enables [FilesystemConfig] to queue container ops without
|
||||||
|
// depending on the container package.
|
||||||
type Ops interface {
|
type Ops interface {
|
||||||
// Tmpfs appends an op that mounts tmpfs on a container path.
|
// Tmpfs appends an op that mounts tmpfs on a container path.
|
||||||
Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops
|
Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops
|
||||||
@@ -41,12 +42,15 @@ type Ops interface {
|
|||||||
// Link appends an op that creates a symlink in the container filesystem.
|
// Link appends an op that creates a symlink in the container filesystem.
|
||||||
Link(target *check.Absolute, linkName string, dereference bool) Ops
|
Link(target *check.Absolute, linkName string, dereference bool) Ops
|
||||||
|
|
||||||
// Root appends an op that expands a directory into a toplevel bind mount mirror on container root.
|
// Root appends an op that expands a directory into a toplevel bind mount
|
||||||
|
// mirror on container root.
|
||||||
Root(host *check.Absolute, flags int) Ops
|
Root(host *check.Absolute, flags int) Ops
|
||||||
// Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
// Etc appends an op that expands host /etc into a toplevel symlink mirror
|
||||||
|
// with /etc semantics.
|
||||||
Etc(host *check.Absolute, prefix string) Ops
|
Etc(host *check.Absolute, prefix string) Ops
|
||||||
|
|
||||||
// Daemon appends an op that starts a daemon in the container and blocks until target appears.
|
// Daemon appends an op that starts a daemon in the container and blocks
|
||||||
|
// until target appears.
|
||||||
Daemon(target, path *check.Absolute, args ...string) Ops
|
Daemon(target, path *check.Absolute, args ...string) Ops
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +65,8 @@ type ApplyState struct {
|
|||||||
// ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value.
|
// ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value.
|
||||||
var ErrFSNull = errors.New("unexpected null in mount point")
|
var ErrFSNull = errors.New("unexpected null in mount point")
|
||||||
|
|
||||||
// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry with invalid type.
|
// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry
|
||||||
|
// with invalid type.
|
||||||
type FSTypeError string
|
type FSTypeError string
|
||||||
|
|
||||||
func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) }
|
func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) }
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ type FSLink struct {
|
|||||||
Target *check.Absolute `json:"dst"`
|
Target *check.Absolute `json:"dst"`
|
||||||
// Arbitrary linkname value store in the symlink.
|
// Arbitrary linkname value store in the symlink.
|
||||||
Linkname string `json:"linkname"`
|
Linkname string `json:"linkname"`
|
||||||
// Whether to treat Linkname as an absolute pathname and dereference before creating the link.
|
|
||||||
|
// Whether to treat Linkname as an absolute pathname and dereference before
|
||||||
|
// creating the link.
|
||||||
Dereference bool `json:"dereference,omitempty"`
|
Dereference bool `json:"dereference,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ type FSOverlay struct {
|
|||||||
|
|
||||||
// Any filesystem, does not need to be on a writable filesystem, must not be nil.
|
// Any filesystem, does not need to be on a writable filesystem, must not be nil.
|
||||||
Lower []*check.Absolute `json:"lower"`
|
Lower []*check.Absolute `json:"lower"`
|
||||||
// The upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly.
|
// The upperdir is normally on a writable filesystem, leave as nil to mount
|
||||||
|
// Lower readonly.
|
||||||
Upper *check.Absolute `json:"upper,omitempty"`
|
Upper *check.Absolute `json:"upper,omitempty"`
|
||||||
// The workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated.
|
// The workdir needs to be an empty directory on the same filesystem as
|
||||||
|
// Upper, must not be nil if Upper is populated.
|
||||||
Work *check.Absolute `json:"work,omitempty"`
|
Work *check.Absolute `json:"work,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
hst/hst.go
60
hst/hst.go
@@ -44,11 +44,13 @@ func (e *AppError) Message() string {
|
|||||||
type Paths struct {
|
type Paths struct {
|
||||||
// Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp].
|
// Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp].
|
||||||
TempDir *check.Absolute `json:"temp_dir"`
|
TempDir *check.Absolute `json:"temp_dir"`
|
||||||
// Shared directory specific to the hsu userid, usually (`/tmp/hakurei.%d`, [Info.User]).
|
// Shared directory specific to the hsu userid, usually
|
||||||
|
// (`/tmp/hakurei.%d`, [Info.User]).
|
||||||
SharePath *check.Absolute `json:"share_path"`
|
SharePath *check.Absolute `json:"share_path"`
|
||||||
// Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid).
|
// Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid).
|
||||||
RuntimePath *check.Absolute `json:"runtime_path"`
|
RuntimePath *check.Absolute `json:"runtime_path"`
|
||||||
// Shared directory specific to the hsu userid located in RuntimePath, usually (`/run/user/%d/hakurei`, uid).
|
// Shared directory specific to the hsu userid located in RuntimePath,
|
||||||
|
// usually (`/run/user/%d/hakurei`, uid).
|
||||||
RunDirPath *check.Absolute `json:"run_dir_path"`
|
RunDirPath *check.Absolute `json:"run_dir_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,10 +76,23 @@ func Template() *Config {
|
|||||||
|
|
||||||
SessionBus: &BusConfig{
|
SessionBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
Talk: []string{
|
||||||
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
"org.freedesktop.Notifications",
|
||||||
Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
"org.freedesktop.FileManager1",
|
||||||
"org.mpris.MediaPlayer2.chromium.*"},
|
"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.*": "*"},
|
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||||
Log: false,
|
Log: false,
|
||||||
@@ -112,7 +127,12 @@ func Template() *Config {
|
|||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
},
|
},
|
||||||
Filesystem: []FilesystemConfigJSON{
|
Filesystem: []FilesystemConfigJSON{
|
||||||
{&FSBind{Target: fhs.AbsRoot, Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"), Write: true, Special: true}},
|
{&FSBind{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"),
|
||||||
|
Write: true,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
{&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
|
{&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
|
||||||
{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
|
{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
|
||||||
{&FSOverlay{
|
{&FSOverlay{
|
||||||
@@ -121,11 +141,27 @@ func Template() *Config {
|
|||||||
Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
|
Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
|
||||||
Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
|
Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
|
||||||
}},
|
}},
|
||||||
{&FSLink{Target: fhs.AbsRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}},
|
{&FSLink{
|
||||||
{&FSLink{Target: fhs.AbsRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}},
|
Target: fhs.AbsRun.Append("current-system"),
|
||||||
{&FSBind{Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
Linkname: "/run/current-system",
|
||||||
Target: check.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}},
|
Dereference: true,
|
||||||
{&FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
|
}},
|
||||||
|
{&FSLink{
|
||||||
|
Target: fhs.AbsRun.Append("opengl-driver"),
|
||||||
|
Linkname: "/run/opengl-driver",
|
||||||
|
Dereference: true,
|
||||||
|
}},
|
||||||
|
{&FSBind{
|
||||||
|
Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
||||||
|
Target: check.MustAbs("/data/data/org.chromium.Chromium"),
|
||||||
|
Write: true,
|
||||||
|
Ensure: true,
|
||||||
|
}},
|
||||||
|
{&FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("dri"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
|
|
||||||
Username: "chronos",
|
Username: "chronos",
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import (
|
|||||||
// An ID is a unique identifier held by a running hakurei container.
|
// An ID is a unique identifier held by a running hakurei container.
|
||||||
type ID [16]byte
|
type ID [16]byte
|
||||||
|
|
||||||
// ErrIdentifierLength is returned when encountering a [hex] representation of [ID] with unexpected length.
|
// ErrIdentifierLength is returned when encountering a [hex] representation of
|
||||||
|
// [ID] with unexpected length.
|
||||||
var ErrIdentifierLength = errors.New("identifier string has unexpected length")
|
var ErrIdentifierLength = errors.New("identifier string has unexpected length")
|
||||||
|
|
||||||
// IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant error descriptions.
|
// IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant
|
||||||
|
// error descriptions.
|
||||||
type IdentifierDecodeError struct{ Err error }
|
type IdentifierDecodeError struct{ Err error }
|
||||||
|
|
||||||
func (e IdentifierDecodeError) Unwrap() error { return e.Err }
|
func (e IdentifierDecodeError) Unwrap() error { return e.Err }
|
||||||
@@ -23,7 +25,10 @@ func (e IdentifierDecodeError) Error() string {
|
|||||||
var invalidByteError hex.InvalidByteError
|
var invalidByteError hex.InvalidByteError
|
||||||
switch {
|
switch {
|
||||||
case errors.As(e.Err, &invalidByteError):
|
case errors.As(e.Err, &invalidByteError):
|
||||||
return fmt.Sprintf("got invalid byte %#U in identifier", rune(invalidByteError))
|
return fmt.Sprintf(
|
||||||
|
"got invalid byte %#U in identifier",
|
||||||
|
rune(invalidByteError),
|
||||||
|
)
|
||||||
case errors.Is(e.Err, hex.ErrLength):
|
case errors.Is(e.Err, hex.ErrLength):
|
||||||
return "odd length identifier hex string"
|
return "odd length identifier hex string"
|
||||||
|
|
||||||
@@ -41,7 +46,9 @@ func (a *ID) CreationTime() time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewInstanceID creates a new unique [ID].
|
// NewInstanceID creates a new unique [ID].
|
||||||
func NewInstanceID(id *ID) error { return newInstanceID(id, uint64(time.Now().UnixNano())) }
|
func NewInstanceID(id *ID) error {
|
||||||
|
return newInstanceID(id, uint64(time.Now().UnixNano()))
|
||||||
|
}
|
||||||
|
|
||||||
// newInstanceID creates a new unique [ID] with the specified timestamp.
|
// newInstanceID creates a new unique [ID] with the specified timestamp.
|
||||||
func newInstanceID(id *ID, p uint64) error {
|
func newInstanceID(id *ID, p uint64) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user