plugins

package
v0.0.0-...-e5a87b8 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: AGPL-3.0 Imports: 45 Imported by: 0

Documentation

Overview

Package plugins installs provider plugins and runs them confined.

A plugin is a WebAssembly module that speaks the ABI in sdk/plugin/abi. OLP stores it by the SHA-256 digest of the module, and it can't be used until an owner approves the origins its manifest declares. Runtime runs plugin code on wazero within memory and time limits and grants it only a clock, randomness, redacted logging and, to calls that are granted it, HTTP to its approved origins. Where a deployment enables the unconfined tier, an owner may also permit an executable in its image as an unconfined plugin, which Unconfined runs as a subprocess speaking the ABI over stdio. Host runs installed plugins' code by digest.

Index

Constants

View Source
const (
	// CodeModuleInvalid: the module is not WebAssembly, is not a provider
	// plugin, or imports something OLP does not provide.
	CodeModuleInvalid = "plugin_module_invalid"
	// CodeABIUnsupported: the module was built for another ABI version.
	CodeABIUnsupported = "plugin_abi_unsupported"
	// CodeManifestInvalid: the module reported no valid manifest.
	CodeManifestInvalid = "plugin_manifest_invalid"
	// CodeDialectUnknown: a declared profile names a dialect plugin profiles
	// can't serve.
	CodeDialectUnknown = "plugin_dialect_unknown"
	// CodeTimedOut: a plugin call exceeded its time limit.
	CodeTimedOut = "plugin_timed_out"
	// CodeFailed: a plugin call trapped, exited or exhausted its memory limit.
	CodeFailed = "plugin_failed"
	// CodeNotInstalled: no plugin with the digest is installed.
	CodeNotInstalled = "plugin_not_installed"
	// CodeNotApproved: an owner has not approved the plugin's origins yet.
	CodeNotApproved = "plugin_not_approved"
	// CodeProfileUnknown: the plugin declares no profile with the ID.
	CodeProfileUnknown = "plugin_profile_unknown"
	// CodePinned: provider revisions pin the plugin, so it stays installed.
	CodePinned = "plugin_pinned"
	// CodeUnconfinedDisabled: the plugin is unconfined, and the deployment
	// does not enable unconfined plugins.
	CodeUnconfinedDisabled = "plugin_unconfined_disabled"
	// CodeExecutableUnknown: the unconfined plugin directory holds no
	// executable with the name.
	CodeExecutableUnknown = "plugin_executable_unknown"
	// CodeExecutableInvalid: the executable could not be started, or does
	// not speak the plugin ABI over standard input and output.
	CodeExecutableInvalid = "plugin_executable_invalid"
	// CodeExecutableChanged: the executable no longer has the digest it was
	// reviewed or permitted with.
	CodeExecutableChanged = "plugin_executable_changed"
)

Codes of the errors OLP reports about a plugin module or a plugin call.

Variables

View Source
var DefaultLimits = Limits{Memory: 64 << 20, Time: 10 * time.Second, Stack: 8 << 20, Instances: 4}

DefaultLimits are the limits OLP runs plugins within.

Functions

func Profile

func Profile(ctx context.Context, q access.Queryer, unconfined *Unconfined, digest, id string) (*connectors.PluginProfile, error)

Profile returns the profile id that a usable plugin declares, for a provider to pin with the plugin's digest.

func Profiles

func Profiles(ctx context.Context, q access.Queryer, unconfined *Unconfined) ([]connectors.Profile, error)

Profiles returns the profiles of every usable plugin, which providers may pin, newest build of each plugin first.

func ValidDigest

func ValidDigest(digest string) bool

ValidDigest reports whether digest is the lowercase hexadecimal SHA-256 digest that identifies a plugin.

Types

type Call

type Call struct {
	Method string
	Params any
	// Provider is the provider a call on behalf of one serves: the profile it
	// uses and its option values, which the plugin receives with the call.
	Provider *abi.Provider
	// Secrets are values the call hands the plugin. OLP redacts them from
	// everything the plugin logs and from failures it reports.
	Secrets []string
	// HTTP, when set, grants the call the http capability.
	HTTP *HTTP
	// contains filtered or unexported fields
}

Call is one invocation of plugin code.

type Engine

type Engine int

An Engine is how a runtime runs plugin code. For the reference plugin, a 5 MiB Go module, the interpreter is ready in about 0.4 s and signs a small request in about 15 ms; the compiler takes about 3 s and then signs in about 2 ms, counting every frame towards the call's stack limit. BenchmarkSign measures both.

const (
	// Interpreted interprets modules, which suits a module OLP calls once,
	// such as one being installed.
	Interpreted Engine = iota
	// Compiled compiles modules to machine code, where wazero can, which
	// suits modules OLP keeps and calls per request.
	Compiled
)

type Error

type Error struct {
	Code string
	// Field locates the offending manifest value, such as
	// manifest.profiles[0].dialect, when there is one.
	Field   string
	Message string
}

Error is why OLP refused a plugin module or a plugin call failed. Failures a plugin reports itself are *abi.Error values instead.

func (*Error) Error

func (e *Error) Error() string

type Executable

type Executable struct {
	// Digest is the lowercase hexadecimal SHA-256 digest of the executable.
	Digest string
	// contains filtered or unexported fields
}

Executable is an unconfined plugin, which OLP runs as a subprocess of its executable speaking the plugin ABI over standard input and output. One process serves all its calls, concurrently. OLP starts it for the first call, from a sealed copy of the executable whose digest it checked, and starts it again for the next call after it exits or is stopped. A caller that stops waiting for a call cancels it. A plugin that leaves a call unanswered past the time limit, cancelled or not, may be stuck, so OLP stops it with whatever it started, which fails the calls in flight on it. A call whose result streams instead lasts as long as its caller waits, and is given the time limit to answer once its caller stops waiting.

func (*Executable) Call

func (e *Executable) Call(ctx context.Context, call Call, result any) error

Call serves call on the plugin's process and decodes its result into result, as Module.Call does. A call the process leaves unanswered fails with an *Error: plugin_timed_out once the time limit passed, and plugin_failed when the process stopped first.

func (*Executable) Close

func (e *Executable) Close(context.Context) error

Close stops the plugin's process. A call still running fails.

type ExecutableFile

type ExecutableFile struct {
	// Name is the executable's file name in the directory.
	Name string
	// Digest is the lowercase hexadecimal SHA-256 digest of the executable,
	// which a permitted plugin is identified by and revisions pin.
	Digest string
	Size   int64
}

An ExecutableFile is an executable in the unconfined plugin directory.

type HTTP

type HTTP struct {
	// Origins are the plugin's approved origins, in canonical form.
	Origins []string
	Client  *http.Client
}

HTTP is the HTTP capability OLP may grant a call: requests only to the plugin's approved origins, sent by Client, which the caller gives the provider's network path under the egress policy. A redirect is returned to the plugin rather than followed, so every request it makes is checked.

type Host

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

Host runs the code of installed plugins, by digest, for the processes that serve providers: a plugin profile's signing hook runs once per upstream request, and an unconfined plugin may carry the request itself (Carry); a grant's refresh runs in a worker ahead of its access token's expiry. A confined plugin's module is loaded from the database when it is prepared or on its first call, and kept compiled, with its pool of instances, for the calls after it, so no request compiles anything; an unconfined plugin's process likewise keeps running between calls. A Host keeps the code of the plugins it called most recently, and runs only plugins that Usable admits. It is safe for concurrent use.

func NewHost

func NewHost(runtime *Runtime, unconfined *Unconfined, db access.Queryer) *Host

NewHost returns a Host that runs confined plugins' modules on runtime and, where unconfined is not nil, unconfined plugins' executables in that tier, reading which plugins are installed from db.

func (*Host) Call

func (h *Host) Call(ctx context.Context, digest string, call Call, result any) error

Call serves call, of any ABI method, on the code of the plugin with digest and decodes its result into result. The callers of hosted code, such as a gateway attempt, record only that it failed, so the Host logs why.

func (*Host) Carry

func (h *Host) Carry(ctx context.Context, digest string, provider abi.Provider, req *http.Request, secrets []string) (*http.Response, error)

Carry hands the unconfined plugin with digest one finished upstream request for provider, of a profile whose traffic the plugin carries, and returns the upstream's response as the plugin streams it back: its head once the plugin sends it, and its body as each part arrives, so a stream's events reach the caller as the upstream sends them. It implements connectors.Carrier.

A failure wraps connectors.ErrNotSent when the request never reached the upstream: OLP could not hand it to the plugin, or the plugin reported that it did not send it. After any other failure, including ctx ending before the plugin answered, the upstream's outcome is unknown. Ending ctx or closing the response's body cancels the call if the plugin still streams the response, and the plugin then answers it within the time limit. The Host keeps the plugin's code until the caller closes the body.

func (*Host) Close

func (h *Host) Close(ctx context.Context)

Close releases the code of every plugin the Host holds, stopping unconfined plugins' processes.

func (*Host) Evict

func (h *Host) Evict(digest string)

Evict drops the code the Host keeps for the plugin with digest, which its uninstall calls once committed, so this replica's cached copy stops serving and an unconfined plugin's process stops running. Other replicas' caches observe the removal when the usability of their copy is next rechecked.

func (*Host) Manifest

func (h *Host) Manifest(ctx context.Context, digest string) (abi.Manifest, error)

Manifest returns the manifest of the plugin with digest, which Usable must admit, such as for granting a call HTTP to the plugin's approved origins.

func (*Host) Prepare

func (h *Host) Prepare(digest string)

Prepare loads the code of the plugin with digest in the background, unless the Host holds it already, so the plugin's first call finds its module compiled, with an instance ready.

func (*Host) PreparePinned

func (h *Host) PreparePinned(ctx context.Context) error

PreparePinned prepares the confined plugins that providers' drafts or active revisions pin, one at a time and the most recently approved first, as many as the Host keeps. A process calls it as it starts, so no request waits for a module to compile.

func (*Host) RefreshGrant

func (h *Host) RefreshGrant(ctx context.Context, digest string, provider abi.Provider, refresh abi.GrantRefresh, client *http.Client, secrets []string) (abi.Grant, error)

RefreshGrant runs the grant refresh of the plugin with digest for a grant of provider, granting it HTTP to the plugin's approved origins through client, the provider's network path, and redacting secrets from what the plugin logs and reports. A failure wraps connectors.ErrNotSent only when OLP knows the refresh could not have spent its token. A confined plugin can reach the upstream only through its HTTP capability; an unconfined plugin can reach it itself, so absence of HTTP activity proves nothing there.

func (*Host) RoutePredicate

func (h *Host) RoutePredicate(ctx context.Context, digest string, input abi.RoutePredicate) (bool, error)

RoutePredicate runs the route_predicate hook of the confined plugin with digest. An unconfined plugin never judges a route, since its executable could see more than the call hands it.

func (*Host) Sign

func (h *Host) Sign(ctx context.Context, digest string, provider abi.Provider, request abi.SignRequest, secrets []string) (abi.SignResult, error)

Sign runs the signing hook of the plugin with digest over request for provider, redacting secrets from what the plugin logs and reports.

type Installed

type Installed struct {
	Manifest abi.Manifest
	// Module is a confined plugin's WebAssembly module.
	Module []byte
	// Executable names an unconfined plugin's executable in the unconfined
	// plugin directory.
	Executable string
}

An Installed plugin is one Usable admits, with its code.

func Usable

func Usable(ctx context.Context, q access.Queryer, unconfined *Unconfined, digest string) (Installed, error)

Usable returns an installed plugin that may be used: a confined plugin whose declared origins an owner approved or, where the deployment enables the unconfined tier, an unconfined plugin an owner permitted. unconfined is the tier, or nil. Everything that runs a plugin obtains it here, and everything that pins one obtains its profile from Profile.

type Limits

type Limits struct {
	// Memory bounds an instance's linear memory, in bytes.
	Memory uint32
	// Time bounds one call, including waiting for an instance and
	// instantiating the module for it.
	Time time.Duration
	// Stack bounds the stack of one call, in bytes: the parameters, locals
	// and operands of every frame its plugin code has entered and not left.
	Stack uint32
	// Instances bounds how many instances of one module exist at once, and so
	// how many of its calls run at once.
	Instances int
}

Limits bound every instance of a plugin module.

type Management

type Management struct {
	Access  *access.Server
	Runtime *Runtime
	// Host runs installed plugins' code for this process. Approving a plugin
	// prepares its code there, as providers are about to use it.
	Host *Host
	// Unconfined is the deployment's unconfined tier, or nil where it does
	// not enable one. No API path lists or permits unconfined plugins then.
	Unconfined *Unconfined
	// Index is the signed index of reviewed plugins this release ships,
	// verified at start-up.
	Index *pluginindex.Signed
}

Management serves provider plugin administration on the management API. Only an owner with a user session installs, approves, permits and uninstalls plugins; any role that may read management state lists them.

func (*Management) Register

func (s *Management) Register(mux *http.ServeMux)

Register mounts the plugin operations on the management surface.

type Module

type Module struct {
	// Digest is the lowercase hexadecimal SHA-256 digest of the module.
	Digest string
	// contains filtered or unexported fields
}

Module is a compiled plugin module built for the ABI this runtime serves. Its calls share a pool of instances, which Limits.Instances bounds: a call takes an idle instance, or instantiates the module if none is idle, and returns the instance for later calls unless the call failed.

func (*Module) Call

func (m *Module) Call(ctx context.Context, call Call, result any) error

Call serves call on an instance of the module and decodes its result into result. A call that exceeds its limits, traps or exits fails with an *Error, and its instance is discarded, so it leaves nothing behind; a failure the plugin reports is an *abi.Error.

func (*Module) Close

func (m *Module) Close(ctx context.Context) error

Close releases the compiled module and its instances. A call still running finishes, and its instance is closed afterwards.

type Runtime

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

Runtime runs confined plugin modules. It is safe for concurrent use.

func NewRuntime

func NewRuntime(ctx context.Context, engine Engine, limits Limits, log *slog.Logger) (*Runtime, error)

NewRuntime starts a runtime whose plugin calls run on engine, stay within limits and log to log.

func (*Runtime) Close

func (r *Runtime) Close(ctx context.Context) error

Close releases the runtime and every module compiled in it, once loads under way end. Loading fails afterwards.

func (*Runtime) Inspect

func (r *Runtime) Inspect(ctx context.Context, module []byte) (abi.Manifest, error)

Inspect loads a module and returns the manifest it declares. It refuses a module that is not a plugin, was built for another ABI version, declares an invalid manifest or names a dialect OLP doesn't have.

func (*Runtime) Load

func (r *Runtime) Load(ctx context.Context, module []byte) (*Module, error)

Load compiles a module and checks that it is a provider plugin built for this ABI version.

type Unconfined

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

Unconfined is the experimental unconfined tier of a deployment that enables it: the directory of its image that holds the executables of unconfined plugins. OLP runs an unconfined plugin as a subprocess with the operating system's privileges, speaking the plugin ABI over standard input and output. Only a deployment setting names the directory, so nothing the management API does enables the tier; an owner then permits each plugin.

func NewUnconfined

func NewUnconfined(dir string, limits Limits, log *slog.Logger) *Unconfined

NewUnconfined returns the unconfined tier whose executables live in dir, whose calls stay within limits' time and log to log.

func (*Unconfined) Executables

func (u *Unconfined) Executables() ([]ExecutableFile, error)

Executables lists the executables in the unconfined plugin directory, by name: regular files with an execute permission, whose names are 1–128 letters, digits, dots, underscores and hyphens, not starting with a dot, underscore or hyphen. Listing runs none of them.

func (*Unconfined) Inspect

func (u *Unconfined) Inspect(ctx context.Context, name string) (ExecutableFile, abi.Manifest, error)

Inspect runs the executable with name and returns it with the manifest it declares. It refuses an executable that doesn't speak the plugin ABI over standard input and output, was built for another ABI version, declares an invalid manifest or names a dialect OLP doesn't have.

func (*Unconfined) Load

func (u *Unconfined) Load(digest, name string) *Executable

Load returns the unconfined plugin whose executable has name and digest. It starts nothing until the first call.

Jump to

Keyboard shortcuts

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