daemon

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package daemon implements openbloxd: the policy broker that owns the Docker connection so its callers need no socket access.

Index

Constants

View Source
const (
	TransportUnix = "unix"
	TransportTLS  = "tls"
)

Transport names the way a request arrived.

Variables

This section is empty.

Functions

func Listen

func Listen(socketPath, group string) (net.Listener, error)

Listen creates the Unix socket the daemon serves on.

It refuses to start rather than serve on a socket it could not lock down: a permissive socket is indistinguishable from a working one until something uses it, and by then the caller is trusting a boundary that is not there.

A leftover socket from an unclean shutdown is removed first. Only a socket — removing anything else would let a misconfigured path delete a real file.

func ListenTLS added in v0.7.0

func ListenTLS(cfg ListenConfig) (net.Listener, error)

ListenTLS creates the network listener the daemon serves on.

Two gates, both during the handshake. The client certificate must chain to the configured CA, and its Common Name must be on the allowlist.

The second gate is not belt-and-braces. With verification alone the CA is the whole access control list, so a CA shared with anything else silently grants sandbox creation to whatever that other thing issued. The allowlist is what makes a CA mis-issuance survivable, and what makes the set of permitted callers something an operator can read in one place.

Rejecting during the handshake rather than in a middleware means a caller that fails either gate never reaches the request router at all.

func WithCaller added in v0.7.0

func WithCaller(next http.Handler) http.Handler

WithCaller records the caller on the request context, over every transport.

Name is empty for a Unix caller because SO_PEERCRED is unimplemented; that is the local transport's identity seam and is unrelated to this one.

Types

type Backend

type Backend interface {
	sandbox.Backend
	DialPort(ctx context.Context, name string, port int) (net.Conn, error)
	Reap(ctx context.Context) ([]string, error)
}

Backend is what the daemon needs from a provisioner: the library contract plus the two methods outside it that the broker exposes.

type Caller added in v0.7.0

type Caller struct {
	Transport string
	Name      string
}

Caller is who made a request.

Nothing consumes this yet. It exists anyway because a transport that discards the caller's identity has to be reopened to add per-caller quotas or an audit trail, and the place to record identity is where it is still available.

Transport is carried explicitly rather than inferred from an empty Name: a log line for a security boundary should say whether a request arrived locally or over a network, not leave it to be deduced.

func CallerFrom added in v0.7.0

func CallerFrom(ctx context.Context) (Caller, bool)

CallerFrom returns the caller recorded by WithCaller.

type Config

type Config struct {
	Socket       string             `yaml:"socket"`
	SocketGroup  string             `yaml:"socket_group"`
	Listen       *ListenConfig      `yaml:"listen"`
	ReapInterval time.Duration      `yaml:"reap_interval"`
	Profiles     map[string]Profile `yaml:"profiles"`
}

Config is the daemon's whole configuration.

func Load

func Load(path string) (*Config, error)

Load reads and validates a config file.

Decoding is strict. A misspelled key is a refusal to start rather than a silently unapplied bound, because an unapplied bound looks identical to a working one until something escapes.

type ListenConfig added in v0.7.0

type ListenConfig struct {
	Address string    `yaml:"address"`
	TLS     TLSConfig `yaml:"tls"`
}

ListenConfig is the network listener. Absent means the daemon serves only its Unix socket, which stays the default and the recommended arrangement wherever caller and daemon share a host.

func (ListenConfig) IsWildcardHost added in v0.7.0

func (l ListenConfig) IsWildcardHost() bool

IsWildcardHost reports whether the address binds every interface. That is a legitimate choice for a daemon whose network namespace is already the boundary, and a serious mistake otherwise — so it is warned about at boot rather than refused.

type Profile

type Profile struct {
	Image          string        `yaml:"image"`
	Runtime        string        `yaml:"runtime"`
	Egress         string        `yaml:"egress"`
	User           string        `yaml:"user"`
	CPUs           float64       `yaml:"cpus"`
	MemoryMB       int64         `yaml:"memory_mb"`
	DiskMB         int64         `yaml:"disk_mb"`
	MaxProcesses   int           `yaml:"max_processes"`
	MaxSandboxes   int           `yaml:"max_sandboxes"`
	IdleTimeout    time.Duration `yaml:"idle_timeout"`
	MaxAge         time.Duration `yaml:"max_age"`
	DefaultTimeout time.Duration `yaml:"default_timeout"`
	MaxTimeout     time.Duration `yaml:"max_timeout"`
	RegistryAuth   *RegistryAuth `yaml:"registry_auth"`
}

Profile is one named isolation policy. Every field here is deliberately unreachable from a request: this struct is the reason WithRuntime and WithEgress cannot be asked for over the wire.

func (Profile) DigestPinned

func (p Profile) DigestPinned() bool

DigestPinned reports whether the profile's image is pinned by digest. The daemon warns rather than refuses: refusing would make a local tag-built image unusable in development, where there is no registry to pin against.

func (Profile) Options

func (p Profile) Options() []sandbox.CreateOption

Options renders the profile as library options.

This function is the only place in openbloxd that produces WithRuntime or WithEgress, and its sole input is the config file. Nothing derived from a request reaches it. Keep it that way.

type RegistryAuth

type RegistryAuth struct {
	Username string `yaml:"username"`
	Password string `yaml:"password"`
}

RegistryAuth authenticates image pulls. It lives here, and only here: the credentials never leave the daemon and are never a request field.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server routes broker requests onto a Backend under a fixed policy.

func New

func New(backend Backend, cfg *Config) *Server

New returns a Server. It does not listen; see Listen and Handler.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the route table.

No path carries a version prefix: the daemon and its client ship together and speak over a local socket, so there is no independently versioned consumer to protect. A header can version this later without a guess baked into a path.

func (*Server) RunReaper

func (s *Server) RunReaper(ctx context.Context)

RunReaper sweeps expired sandboxes until ctx is cancelled.

The daemon owns lifetime now, so it owns the sweep. Nothing ran this before: the library wrote idle and max-age labels that no process acted on, so a sandbox whose caller vanished lived until someone noticed.

type TLSConfig added in v0.7.0

type TLSConfig struct {
	CertFile         string   `yaml:"cert_file"`
	KeyFile          string   `yaml:"key_file"`
	ClientCAFile     string   `yaml:"client_ca_file"`
	AllowedClientCNs []string `yaml:"allowed_client_cns"`
}

TLSConfig is the daemon's half of the mTLS credential, plus the allowlist that stops the CA from being the whole access control list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL