feat(runtime): modularize host capabilities
- add an exact-version Capability Registry and structured descriptors - move Clock bindings, host state, and Linker installation into its own module - derive required capabilities from Component imports and link only requested interfaces - cover exact-version resolution and minimal per-component capability sets
This commit is contained in:
@@ -30,8 +30,9 @@ use wasmtime_wasi::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
RuntimeError, ServiceKey, ServiceManifest,
|
||||
bindings::{ServiceComponent, ServiceError, clock as clock_bindings},
|
||||
CapabilityDescriptor, RuntimeError, ServiceKey, ServiceManifest,
|
||||
bindings::{ServiceComponent, ServiceError},
|
||||
capability::{Capability, CapabilityRegistry, HostCapabilities},
|
||||
manifest::ResourceLimits,
|
||||
};
|
||||
|
||||
@@ -40,7 +41,6 @@ const MAX_EPOCH_TICK: Duration = Duration::from_millis(10);
|
||||
const DEFAULT_MAX_WASM_STACK: usize = 512 * 1024;
|
||||
const ACTOR_RESOURCE_COUNT_LIMIT: usize = 16;
|
||||
const ACTOR_TABLE_ELEMENT_LIMIT: usize = 16 * 1024;
|
||||
const MONOTONIC_CLOCK_IMPORT: &str = "wasmeld:clock/monotonic-clock@0.1.0";
|
||||
const ALLOWED_WASI_IMPORTS: &[&str] = &[
|
||||
"wasi:cli/environment@",
|
||||
"wasi:cli/exit@",
|
||||
@@ -162,12 +162,7 @@ impl Drop for EpochTicker {
|
||||
struct RegisteredService {
|
||||
manifest: ServiceManifest,
|
||||
component: Arc<Component>,
|
||||
capabilities: Vec<HostCapability>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum HostCapability {
|
||||
MonotonicClock,
|
||||
capabilities: Vec<Capability>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
@@ -327,6 +322,26 @@ impl Runtime {
|
||||
actor.invoke(input)
|
||||
}
|
||||
|
||||
/// Returns the exact versioned host capabilities imported by a service.
|
||||
pub fn capabilities(
|
||||
&self,
|
||||
key: &ServiceKey,
|
||||
) -> Result<Vec<CapabilityDescriptor>, RuntimeError> {
|
||||
let services = self
|
||||
.inner
|
||||
.services
|
||||
.lock()
|
||||
.map_err(|_| RuntimeError::LockPoisoned)?;
|
||||
let service = services
|
||||
.get(key)
|
||||
.ok_or_else(|| RuntimeError::ServiceNotRegistered(key.clone()))?;
|
||||
Ok(service
|
||||
.capabilities
|
||||
.iter()
|
||||
.map(|capability| capability.descriptor())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Stops one Actor and releases its Store and Component Instance.
|
||||
pub fn stop(&self, key: &ServiceKey) -> Result<(), RuntimeError> {
|
||||
let _lifecycle = self
|
||||
@@ -557,15 +572,15 @@ impl ActorStatus {
|
||||
}
|
||||
}
|
||||
|
||||
struct HostState {
|
||||
pub(crate) struct HostState {
|
||||
store_limits: StoreLimits,
|
||||
wasi: WasiCtx,
|
||||
wasi_table: ResourceTable,
|
||||
clock_origin: Instant,
|
||||
pub(crate) capabilities: HostCapabilities,
|
||||
}
|
||||
|
||||
impl HostState {
|
||||
fn new(limits: &ResourceLimits) -> Self {
|
||||
fn new(limits: &ResourceLimits, capabilities: &[Capability]) -> Self {
|
||||
let store_limits = StoreLimitsBuilder::new()
|
||||
.memory_size(limits.memory_bytes)
|
||||
.table_elements(ACTOR_TABLE_ELEMENT_LIMIT)
|
||||
@@ -585,7 +600,7 @@ impl HostState {
|
||||
store_limits,
|
||||
wasi: wasi.build(),
|
||||
wasi_table: ResourceTable::new(),
|
||||
clock_origin: Instant::now(),
|
||||
capabilities: HostCapabilities::new(capabilities),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,12 +609,6 @@ impl HasData for HostState {
|
||||
type Data<'a> = &'a mut HostState;
|
||||
}
|
||||
|
||||
impl clock_bindings::wasmeld::clock::monotonic_clock::Host for HostState {
|
||||
fn now(&mut self) -> u64 {
|
||||
u64::try_from(self.clock_origin.elapsed().as_nanos()).unwrap_or(u64::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiView for HostState {
|
||||
fn ctx(&mut self) -> WasiCtxView<'_> {
|
||||
WasiCtxView {
|
||||
@@ -639,12 +648,12 @@ impl ActorWorker {
|
||||
});
|
||||
}
|
||||
|
||||
let mut store = Store::new(&engine, HostState::new(&limits));
|
||||
let mut store = Store::new(&engine, HostState::new(&limits, &service.capabilities));
|
||||
store.limiter(|state| &mut state.store_limits);
|
||||
|
||||
let mut linker = Linker::new(&engine);
|
||||
add_restricted_wasi(&mut linker)?;
|
||||
add_host_capabilities(&mut linker, &service.capabilities)?;
|
||||
CapabilityRegistry::add_to_linker(&mut linker, &service.capabilities)?;
|
||||
|
||||
let epoch_ticks = ticks_for(limits.deadline(), epoch_tick);
|
||||
configure_call_budget(&mut store, &limits, epoch_ticks)?;
|
||||
@@ -758,33 +767,16 @@ fn link_wasi(result: wasmtime::Result<()>) -> Result<(), RuntimeError> {
|
||||
result.map_err(|error| RuntimeError::ActorInitialization(error.to_string()))
|
||||
}
|
||||
|
||||
fn add_host_capabilities(
|
||||
linker: &mut Linker<HostState>,
|
||||
capabilities: &[HostCapability],
|
||||
) -> Result<(), RuntimeError> {
|
||||
for capability in capabilities {
|
||||
match capability {
|
||||
HostCapability::MonotonicClock => link_wasi(
|
||||
clock_bindings::wasmeld::clock::monotonic_clock::add_to_linker::<
|
||||
HostState,
|
||||
HostState,
|
||||
>(linker, |state| state),
|
||||
)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_component_imports(
|
||||
component: &Component,
|
||||
engine: &Engine,
|
||||
) -> Result<Vec<HostCapability>, RuntimeError> {
|
||||
) -> Result<Vec<Capability>, RuntimeError> {
|
||||
// Import validation is deny-by-default. A host function is linked only
|
||||
// after its exact WIT identity or WASI family has passed this allowlist.
|
||||
let mut capabilities = Vec::new();
|
||||
for (name, _) in component.component_type().imports(engine) {
|
||||
if name == MONOTONIC_CLOCK_IMPORT {
|
||||
capabilities.push(HostCapability::MonotonicClock);
|
||||
if let Some(capability) = CapabilityRegistry::resolve(name) {
|
||||
capabilities.push(capability);
|
||||
} else if !ALLOWED_WASI_IMPORTS
|
||||
.iter()
|
||||
.any(|allowed| name.starts_with(allowed))
|
||||
@@ -793,6 +785,8 @@ fn validate_component_imports(
|
||||
}
|
||||
}
|
||||
|
||||
capabilities.sort_unstable();
|
||||
capabilities.dedup();
|
||||
Ok(capabilities)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user