a3s-box-runtime 3.1.0

MicroVM runtime engine — VM lifecycle, OCI images, attestation, networking
Documentation
use std::collections::BTreeMap;
use std::time::Duration;

use a3s_box_core::ExecutionManager;
use a3s_runtime::contract::{
    ArtifactRef, IsolationLevel, MountKind, NetworkMode, ResourceControl, ResourceLimits,
    RestartPolicy, RuntimeFeature, RuntimeMount, RuntimeMountSource, RuntimeNetworkSpec,
    RuntimeProcessSpec, RuntimeUnitClass, RuntimeUnitSpec,
};
use a3s_runtime::RuntimeDriver;

use super::mapping::{creation_request, operation};
use super::metadata::validate_record_for_spec;
use super::*;

fn spec(class: RuntimeUnitClass) -> RuntimeUnitSpec {
    RuntimeUnitSpec {
        schema: RuntimeUnitSpec::SCHEMA.into(),
        unit_id: "box-runtime-test".into(),
        generation: 7,
        class,
        artifact: ArtifactRef {
            uri: format!(
                "oci://registry.example/a3s/runtime@sha256:{}",
                "a".repeat(64)
            ),
            digest: format!("sha256:{}", "a".repeat(64)),
            media_type: OCI_IMAGE_MANIFEST.into(),
        },
        process: RuntimeProcessSpec {
            command: vec!["/bin/sh".into(), "-c".into()],
            args: vec!["echo ready".into()],
            working_directory: Some("/work".into()),
            environment: BTreeMap::from([("LANG".into(), "C.UTF-8".into())]),
        },
        mounts: Vec::new(),
        secrets: Vec::new(),
        network: RuntimeNetworkSpec {
            mode: NetworkMode::None,
            ports: Vec::new(),
        },
        resources: ResourceLimits {
            cpu_millis: 1_501,
            memory_bytes: 65 * 1024 * 1024 + 17,
            pids: 37,
            ephemeral_storage_bytes: None,
            execution_timeout_ms: (class == RuntimeUnitClass::Task).then_some(2_500),
        },
        isolation: IsolationLevel::Sandbox,
        health: None,
        restart: if class == RuntimeUnitClass::Task {
            RestartPolicy::Never
        } else {
            RestartPolicy::Always
        },
        outputs: Vec::new(),
        semantics_profile_digest: None,
    }
}

fn driver(directory: &tempfile::TempDir) -> BoxRuntimeDriver {
    BoxRuntimeDriver::new(BoxRuntimeDriverConfig {
        home_dir: directory.path().join("home"),
        control_timeout: Duration::from_secs(2),
        task_poll_interval: Duration::from_millis(5),
    })
    .unwrap()
}

fn mutate_record(
    driver: &BoxRuntimeDriver,
    execution_id: &str,
    mutation: impl FnOnce(&mut crate::BoxRecord),
) {
    crate::BoxStateStore::modify(driver.manager.state_path(), move |store| {
        let record = store.find_by_id_mut(execution_id).ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("missing managed execution {execution_id}"),
            )
        })?;
        mutation(record);
        Ok(())
    })
    .unwrap();
}

#[tokio::test]
async fn capabilities_claim_only_the_mapped_box_surface() {
    let directory = tempfile::tempdir().unwrap();
    let driver = driver(&directory);
    driver
        .provider_build
        .set("a3s-box/test crun/test sha256:0123456789abcdef".into())
        .unwrap();

    let capabilities = driver.capabilities().await.unwrap();
    assert_eq!(capabilities.provider_id.as_str(), "a3s-box");
    assert_eq!(
        capabilities.unit_classes,
        vec![RuntimeUnitClass::Task, RuntimeUnitClass::Service]
    );
    assert_eq!(capabilities.isolation_levels, vec![IsolationLevel::Sandbox]);
    assert_eq!(capabilities.network_modes, vec![NetworkMode::None]);
    assert_eq!(capabilities.mount_kinds, vec![MountKind::Tmpfs]);
    assert!(capabilities.health_check_kinds.is_empty());
    assert_eq!(
        capabilities.resource_controls,
        vec![
            ResourceControl::Cpu,
            ResourceControl::Memory,
            ResourceControl::Pids,
            ResourceControl::ExecutionTimeout,
        ]
    );
    assert_eq!(
        capabilities.features,
        vec![
            RuntimeFeature::DurableIdentity,
            RuntimeFeature::Stop,
            RuntimeFeature::Remove,
            RuntimeFeature::Logs,
            RuntimeFeature::Exec,
        ]
    );
}

#[test]
fn mapping_preserves_digest_resources_timeout_and_hardening() {
    let spec = spec(RuntimeUnitClass::Task);
    let request = creation_request(&spec).unwrap();
    assert_eq!(
        request.config.image,
        format!("registry.example/a3s/runtime@sha256:{}", "a".repeat(64))
    );
    assert_eq!(request.config.resources.vcpus, 2);
    assert_eq!(request.config.resources.memory_mb, 66);
    assert_eq!(request.config.resources.timeout, 3);
    assert_eq!(request.config.resource_limits.cpu_period, Some(100_000));
    assert_eq!(request.config.resource_limits.cpu_quota, Some(150_100));
    assert_eq!(request.config.resource_limits.pids_limit, Some(37));
    assert_eq!(
        request.config.resource_limits.sandbox_memory_limit_bytes,
        Some(spec.resources.memory_bytes)
    );
    assert_eq!(
        request.config.resource_limits.memory_swap,
        Some(spec.resources.memory_bytes as i64)
    );
    assert!(request.config.persistent);
    assert_eq!(request.config.cap_drop, vec!["ALL"]);
    assert_eq!(request.config.security_opt, vec!["no-new-privileges"]);
}

#[test]
fn mapping_rejects_path_like_unit_identity_before_mutation() {
    for unit_id in [
        "../provider-escape",
        "/absolute-provider-id",
        "tenant/../provider-escape",
        "tenant//provider-id",
    ] {
        let mut spec = spec(RuntimeUnitClass::Service);
        spec.unit_id = unit_id.into();
        assert!(matches!(
            creation_request(&spec),
            Err(RuntimeError::InvalidRequest(message))
                if message.contains("path traversal")
        ));
    }

    let mut namespaced = spec(RuntimeUnitClass::Service);
    namespaced.unit_id = "tenant/provider-id".into();
    assert!(creation_request(&namespaced).is_ok());
}

#[test]
fn mapping_compiles_bounded_tmpfs_mounts_and_read_only_intent() {
    let mut spec = spec(RuntimeUnitClass::Service);
    spec.mounts = vec![
        RuntimeMount {
            name: "scratch".into(),
            source: RuntimeMountSource::Tmpfs {
                size_bytes: 8 * 1024 * 1024,
            },
            target: "/runtime/scratch".into(),
            read_only: false,
        },
        RuntimeMount {
            name: "sealed".into(),
            source: RuntimeMountSource::Tmpfs {
                size_bytes: 4 * 1024 * 1024,
            },
            target: "/runtime/sealed".into(),
            read_only: true,
        },
    ];

    let request = creation_request(&spec).unwrap();

    assert_eq!(
        request.config.tmpfs,
        vec![
            "/runtime/scratch:size=8388608,rw",
            "/runtime/sealed:size=4194304,ro",
        ]
    );
    assert!(request.config.volumes.is_empty());
    assert!(request.policy.volume_names.is_empty());
}

#[test]
fn mapping_rejects_unadvertised_mount_kinds_before_mutation() {
    let mut spec = spec(RuntimeUnitClass::Service);
    spec.mounts.push(RuntimeMount {
        name: "data".into(),
        source: RuntimeMountSource::Volume {
            volume_id: "runtime-data".into(),
        },
        target: "/data".into(),
        read_only: false,
    });

    assert!(matches!(
        creation_request(&spec),
        Err(RuntimeError::UnsupportedCapabilities(missing))
            if missing == vec!["mount_kind:Volume"]
    ));
}

#[test]
fn mapping_rejects_protected_or_unencodable_tmpfs_targets() {
    for target in ["/proc/runtime", "/dev/shm/nested", "/run/a3s-box/data"] {
        let mut spec = spec(RuntimeUnitClass::Service);
        spec.mounts.push(RuntimeMount {
            name: "scratch".into(),
            source: RuntimeMountSource::Tmpfs { size_bytes: 4096 },
            target: target.into(),
            read_only: false,
        });
        assert!(matches!(
            creation_request(&spec),
            Err(RuntimeError::InvalidRequest(message)) if message.contains("protected")
        ));
    }

    for target in [
        "/runtime/ambiguous:target",
        "/runtime/./scratch",
        "/runtime//scratch",
        "/runtime/scratch/",
    ] {
        let mut spec = spec(RuntimeUnitClass::Service);
        spec.mounts.push(RuntimeMount {
            name: "scratch".into(),
            source: RuntimeMountSource::Tmpfs { size_bytes: 4096 },
            target: target.into(),
            read_only: false,
        });
        assert!(matches!(
            creation_request(&spec),
            Err(RuntimeError::InvalidRequest(message)) if message.contains("encodable")
        ));
    }
}

#[test]
fn mapping_rejects_unpinned_mismatched_and_unsupported_artifacts() {
    let mut value = spec(RuntimeUnitClass::Service);
    value.artifact.uri = "oci://registry.example/a3s/runtime:latest".into();
    assert!(creation_request(&value).is_err());

    let mut value = spec(RuntimeUnitClass::Service);
    value.artifact.uri = format!(
        "oci://registry.example/a3s/runtime@sha256:{}",
        "b".repeat(64)
    );
    assert!(creation_request(&value).is_err());

    let mut value = spec(RuntimeUnitClass::Service);
    value.artifact.media_type = "application/vnd.oci.image.index.v1+json".into();
    assert!(matches!(
        creation_request(&value),
        Err(RuntimeError::UnsupportedCapabilities(_))
    ));

    let mut value = spec(RuntimeUnitClass::Service);
    value.artifact.uri = format!(
        "oci://user:secret@registry.example/a3s/runtime@sha256:{}",
        "a".repeat(64)
    );
    assert!(creation_request(&value).is_err());
}

#[test]
fn mapping_rejects_numeric_overflow_before_mutation() {
    let mut value = spec(RuntimeUnitClass::Service);
    value.resources.memory_bytes = i64::MAX as u64 + 1;
    assert!(matches!(
        creation_request(&value),
        Err(RuntimeError::InvalidRequest(message)) if message.contains("memory")
    ));

    let mut value = spec(RuntimeUnitClass::Service);
    value.resources.cpu_millis = u64::MAX;
    assert!(matches!(
        creation_request(&value),
        Err(RuntimeError::InvalidRequest(message)) if message.contains("CPU")
    ));
}

#[tokio::test]
async fn metadata_tamper_is_rejected_fail_closed() {
    let directory = tempfile::tempdir().unwrap();
    let driver = driver(&directory);
    let spec = spec(RuntimeUnitClass::Service);
    let operation_id = operation(&spec).unwrap();
    let reservation = driver
        .manager
        .create(creation_request(&spec).unwrap(), &operation_id)
        .await
        .unwrap();
    let mut record = driver
        .manager
        .managed_record(&reservation.execution_id)
        .await
        .unwrap()
        .unwrap();
    validate_record_for_spec(&record, &spec).unwrap();

    record
        .labels
        .insert(super::metadata::GENERATION_LABEL.into(), "8".into());
    assert!(matches!(
        validate_record_for_spec(&record, &spec),
        Err(RuntimeError::Protocol(message)) if message.contains("identity") || message.contains("intent")
    ));
}

#[tokio::test]
async fn discovery_rejects_a_runtime_record_hidden_by_unit_label_tamper() {
    let directory = tempfile::tempdir().unwrap();
    let driver = driver(&directory);
    let spec = spec(RuntimeUnitClass::Service);
    let reservation = driver
        .manager
        .create(creation_request(&spec).unwrap(), &operation(&spec).unwrap())
        .await
        .unwrap();

    mutate_record(&driver, reservation.execution_id.as_str(), |record| {
        record
            .labels
            .insert(super::metadata::UNIT_LABEL.into(), "hidden-unit".into());
    });

    assert!(matches!(
        driver.find_generation(&spec).await,
        Err(RuntimeError::Protocol(message)) if message.contains("ownership")
    ));
}

#[tokio::test]
async fn discovery_rejects_a_runtime_operation_that_lost_all_labels() {
    let directory = tempfile::tempdir().unwrap();
    let driver = driver(&directory);
    let spec = spec(RuntimeUnitClass::Service);
    let reservation = driver
        .manager
        .create(creation_request(&spec).unwrap(), &operation(&spec).unwrap())
        .await
        .unwrap();

    mutate_record(&driver, reservation.execution_id.as_str(), |record| {
        record.labels.clear();
        record
            .managed_execution
            .as_mut()
            .unwrap()
            .request
            .labels
            .clear();
    });

    assert!(matches!(
        driver.find_generation(&spec).await,
        Err(RuntimeError::Protocol(message)) if message.contains("no unit identity")
    ));
}