a3s-box-runtime 3.2.3

MicroVM runtime engine — VM lifecycle, OCI images, attestation, networking
Documentation
use super::*;
use a3s_acl::SchemaDiagnosticCode;
use a3s_box_core::platform::Platform;

const PLAN: &str = r#"
build "oci" {
  target = "release"
  schema = "a3s.box.build-plan.v1"
  platform = "linux/amd64"
  network = "none"
  file = "Dockerfile"
  context = "."
  cache = "content-addressed"
}
"#;

#[test]
fn canonical_acl_and_digest_are_stable() {
    let reordered = r#"
      # Input order and comments do not define build identity.
      build "oci" {
        cache = "content-addressed"
        context = "."
        file = "Dockerfile"
        network = "none"
        platform = "linux/amd64"
        schema = "a3s.box.build-plan.v1"
        target = "release"
      }
    "#;
    let expected = concat!(
        "build \"oci\" {\n",
        "  cache = \"content-addressed\"\n",
        "  context = \".\"\n",
        "  file = \"Dockerfile\"\n",
        "  network = \"none\"\n",
        "  platform = \"linux/amd64\"\n",
        "  schema = \"a3s.box.build-plan.v1\"\n",
        "  target = \"release\"\n",
        "}\n",
    );

    let first = BoxBuildPlan::parse_acl(PLAN).expect("valid build plan");
    let second = BoxBuildPlan::parse_acl(reordered).expect("valid reordered build plan");

    assert_eq!(first, second);
    assert_eq!(first.canonical_acl().unwrap(), expected);
    assert_eq!(
        first.canonical_digest().unwrap(),
        "sha256:f8f1fbacf18535adbf39217cacf7eec46d415c607ecceefeeae37f0e16b1d816"
    );
}

#[test]
fn parser_applies_a_bounded_document_limit() {
    let oversized = format!("{PLAN}\n# {}", "x".repeat(16 * 1024));

    assert!(matches!(
        BoxBuildPlan::parse_acl(&oversized),
        Err(BoxBuildPlanError::AclParse { .. })
    ));
}

#[test]
fn schema_is_closed_and_identity_values_are_exact() {
    let unknown = PLAN.replace(
        "  cache = \"content-addressed\"",
        "  cache = \"content-addressed\"\n  extra = \"rejected\"",
    );
    assert!(matches!(
        BoxBuildPlan::parse_acl(&unknown),
        Err(BoxBuildPlanError::Schema {
            code: SchemaDiagnosticCode::UnknownAttribute,
            ..
        })
    ));

    let wrong_label = PLAN.replace("build \"oci\"", "build \"container\"");
    assert!(matches!(
        BoxBuildPlan::parse_acl(&wrong_label),
        Err(BoxBuildPlanError::InvalidValue { field: "label", .. })
    ));

    let wrong_schema = PLAN.replace("a3s.box.build-plan.v1", "a3s.box.build-plan.experimental");
    assert!(matches!(
        BoxBuildPlan::parse_acl(&wrong_schema),
        Err(BoxBuildPlanError::InvalidValue {
            field: "schema",
            ..
        })
    ));
}

#[test]
fn cache_and_network_policies_are_closed() {
    let outbound = BoxBuildPlan::parse_acl(
        &PLAN
            .replace("network = \"none\"", "network = \"outbound\"")
            .replace("content-addressed", "disabled"),
    )
    .unwrap();
    assert_eq!(outbound.network(), BuildNetworkPolicy::Outbound);
    assert_eq!(outbound.cache(), BuildCachePolicy::Disabled);

    let bad_cache = PLAN.replace("content-addressed", "shared-directory");
    assert!(matches!(
        BoxBuildPlan::parse_acl(&bad_cache),
        Err(BoxBuildPlanError::InvalidValue { field: "cache", .. })
    ));

    let bad_network = PLAN.replace("network = \"none\"", "network = \"host\"");
    assert!(matches!(
        BoxBuildPlan::parse_acl(&bad_network),
        Err(BoxBuildPlanError::InvalidValue {
            field: "network",
            ..
        })
    ));
}

#[test]
fn repository_paths_and_target_are_bounded_posix_values() {
    for invalid in [
        PLAN.replace("context = \".\"", "context = \"../outside\""),
        PLAN.replace("file = \"Dockerfile\"", "file = \"/Dockerfile\""),
        PLAN.replace("file = \"Dockerfile\"", "file = \"build//Dockerfile\""),
        PLAN.replace("file = \"Dockerfile\"", "file = \"build\\\\Dockerfile\""),
        PLAN.replace("target = \"release\"", "target = \"release/escape\""),
    ] {
        assert!(
            matches!(
                BoxBuildPlan::parse_acl(&invalid),
                Err(BoxBuildPlanError::InvalidValue { .. })
            ),
            "unsafe plan was admitted:\n{invalid}"
        );
    }
}

#[test]
fn compiles_exactly_into_the_existing_build_engine() {
    let source = tempfile::TempDir::new().unwrap();
    let context = source.path().join("service");
    let recipe = source.path().join(".a3s");
    std::fs::create_dir_all(&context).unwrap();
    std::fs::create_dir_all(&recipe).unwrap();
    std::fs::write(recipe.join("Containerfile"), "FROM scratch\n").unwrap();

    let plan = BoxBuildPlan::parse_acl(
        r#"
build "oci" {
  schema = "a3s.box.build-plan.v1"
  context = "service"
  file = ".a3s/Containerfile"
  platform = "linux/arm64"
  target = "release"
  network = "none"
  cache = "disabled"
}
"#,
    )
    .unwrap();
    let options = BoxBuildOptions {
        tag: Some("registry.example/a3s/test:build".to_string()),
        quiet: true,
    };

    let config = plan.compile(source.path(), options).unwrap();

    assert_eq!(config.context_dir, context.canonicalize().unwrap());
    assert_eq!(
        config.dockerfile_path,
        recipe.join("Containerfile").canonicalize().unwrap()
    );
    assert_eq!(
        config.tag.as_deref(),
        Some("registry.example/a3s/test:build")
    );
    assert!(config.build_args.is_empty());
    assert!(config.quiet);
    assert_eq!(config.platforms, vec![Platform::linux_arm64()]);
    assert_eq!(config.target.as_deref(), Some("release"));
    assert!(config.no_cache);
    assert_eq!(config.network, BuildNetworkPolicy::None);
    assert!(config.metrics.is_none());
    assert!(config.run_pool.is_none());
}

#[test]
fn compilation_requires_an_absolute_existing_source_root() {
    let plan = BoxBuildPlan::parse_acl(PLAN).unwrap();

    assert!(matches!(
        plan.compile(
            std::path::Path::new("relative/source"),
            BoxBuildOptions::default()
        ),
        Err(BoxBuildPlanError::InvalidSourceRoot { .. })
    ));
}

#[cfg(unix)]
#[test]
fn compilation_rejects_context_and_file_symlink_escapes() {
    use std::os::unix::fs::symlink;

    let temporary = tempfile::TempDir::new().unwrap();
    let source = temporary.path().join("source");
    let outside = temporary.path().join("outside");
    std::fs::create_dir_all(&source).unwrap();
    std::fs::create_dir_all(&outside).unwrap();
    std::fs::write(source.join("Dockerfile"), "FROM scratch\n").unwrap();
    std::fs::write(outside.join("Containerfile"), "FROM scratch\n").unwrap();

    symlink(&outside, source.join("context-link")).unwrap();
    let context_plan =
        BoxBuildPlan::parse_acl(&PLAN.replace("context = \".\"", "context = \"context-link\""))
            .unwrap();
    assert!(matches!(
        context_plan.compile(&source, BoxBuildOptions::default()),
        Err(BoxBuildPlanError::UnsafePath {
            field: "context",
            ..
        })
    ));

    symlink(outside.join("Containerfile"), source.join("file-link")).unwrap();
    let file_plan =
        BoxBuildPlan::parse_acl(&PLAN.replace("file = \"Dockerfile\"", "file = \"file-link\""))
            .unwrap();
    assert!(matches!(
        file_plan.compile(&source, BoxBuildOptions::default()),
        Err(BoxBuildPlanError::UnsafePath { field: "file", .. })
    ));
}