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
- Variables
- func Profile(ctx context.Context, q access.Queryer, unconfined *Unconfined, ...) (*connectors.PluginProfile, error)
- func Profiles(ctx context.Context, q access.Queryer, unconfined *Unconfined) ([]connectors.Profile, error)
- func ValidDigest(digest string) bool
- type Call
- type Engine
- type Error
- type Executable
- type ExecutableFile
- type HTTP
- type Host
- func (h *Host) Call(ctx context.Context, digest string, call Call, result any) error
- func (h *Host) Carry(ctx context.Context, digest string, provider abi.Provider, req *http.Request, ...) (*http.Response, error)
- func (h *Host) Close(ctx context.Context)
- func (h *Host) Evict(digest string)
- func (h *Host) Manifest(ctx context.Context, digest string) (abi.Manifest, error)
- func (h *Host) Prepare(digest string)
- func (h *Host) PreparePinned(ctx context.Context) error
- func (h *Host) RefreshGrant(ctx context.Context, digest string, provider abi.Provider, ...) (abi.Grant, error)
- func (h *Host) RoutePredicate(ctx context.Context, digest string, input abi.RoutePredicate) (bool, error)
- func (h *Host) Sign(ctx context.Context, digest string, provider abi.Provider, ...) (abi.SignResult, error)
- type Installed
- type Limits
- type Management
- type Module
- type Runtime
- type Unconfined
Constants ¶
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 ¶
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 ¶
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.
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.
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.
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 ¶
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 ¶
Close releases the code of every plugin the Host holds, stopping unconfined plugins' processes.
func (*Host) Evict ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
Close releases the runtime and every module compiled in it, once loads under way end. Loading fails afterwards.
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.