//! Explicit signed-release acceptance with no host language trees in the final root. use super::*; use std::os::unix::fs::{MetadataExt, chown}; const ECOSYSTEMS: [&str; 3] = ["pip", "npm", "go"]; const STANDALONE_UID: u32 = 65534; fn nonroot_standalone(source: &Path, expected_program: &Value) -> Value { let deployment = TemporaryDirectory::new_in(Path::new("/tmp")); let program = deployment.path().join("program"); let config = deployment.path().join("config"); let settings = config.join("setting.json"); fs::create_dir(&config).unwrap(); fs::copy(source.join("program"), &program).unwrap(); fs::copy(source.join("config/setting.json"), &settings).unwrap(); let identity = json!({ "bytes": fs::metadata(&program).unwrap().len(), "sha256": sha256_file(&program).unwrap(), }); assert_eq!(&identity, expected_program); for (path, mode) in [ (program.as_path(), 0o700), (config.as_path(), 0o700), (settings.as_path(), 0o600), (deployment.path(), 0o700), ] { fs::set_permissions(path, fs::Permissions::from_mode(mode)).unwrap(); chown(path, Some(STANDALONE_UID), Some(STANDALONE_UID)).unwrap(); } // Only the standalone executable and private config cross this boundary. // The actual non-root process must extract its own embedded Worker bundle. assert!(!deployment.path().join("data").exists()); let output = command( Command::new("/usr/bin/setpriv") .arg(format!("--reuid={STANDALONE_UID}")) .arg(format!("--regid={STANDALONE_UID}")) .args([ "--clear-groups", "--inh-caps=-all", "--ambient-caps=-all", "--", ]) .arg(&program) .args(["sample.worker.probe", "{}"]) .current_dir(deployment.path()) .stdin(Stdio::null()), ); eprintln!( "standalone UID {STANDALONE_UID} {}: {}\n{}", source.display(), String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert_response(output); assert_eq!( fs::metadata(deployment.path().join("data/cache/lib")) .unwrap() .uid(), STANDALONE_UID ); json!({"passed": true, "uid": STANDALONE_UID, "program": identity}) } fn dependency_fixture(workspace: &Path, ecosystem: &str) -> PathBuf { workspace.join(match ecosystem { "pip" => "target/source-build", "npm" => "target/npm-build", _ => unreachable!(), }) } /// The fixtures were built against these exact pack bytes. The test author /// signs those inputs, rather than rewriting receipts for a recompressed pack. fn sign_build_runtimes( release: &Path, workspace: &Path, key: &Ed25519KeyPair, ) -> dever_cli::toolchain::ReleaseManifest { use dever_cli::libs::{LockFile, RegistryRuntime, RuntimePack}; let mut manifest: dever_cli::toolchain::ReleaseManifest = serde_json::from_slice(&fs::read(release.join("manifest.json")).unwrap()).unwrap(); for ecosystem in ["pip", "npm"] { let fixture = dependency_fixture(workspace, ecosystem); let settings: Value = serde_json::from_slice(&fs::read(fixture.join("config/setting.json")).unwrap()) .unwrap(); let runtime: RegistryRuntime = serde_json::from_value(settings["runtime"].clone()).unwrap(); assert_eq!(runtime.target, "linux-x86_64"); let pack = fixture.join(settings["runtime_file"].as_str().unwrap()); assert_eq!(sha256_file(&pack).unwrap(), runtime.pack.sha256); let lock = LockFile::decode(&fs::read(fixture.join("dependency/dever.lock")).unwrap()).unwrap(); assert!( !lock.builds.is_empty(), "acceptance requires actual source build receipts" ); for receipt in &lock.builds { assert_eq!(receipt.runtime, runtime.pack); if let Some(python) = &receipt.auxiliary_runtime { let python_config: Value = serde_json::from_slice( &fs::read(dependency_fixture(workspace, "pip").join("config/setting.json")) .unwrap(), ) .unwrap(); assert_eq!( *python, serde_json::from_value::(python_config["runtime"]["pack"].clone()) .unwrap() ); } } let prefix = format!("runtime/{ecosystem}/linux-x86_64"); fs::copy(pack, release.join(&prefix).join("runtime.pack")).unwrap(); fs::write( release.join(&prefix).join("manifest.json"), serde_json::to_vec(&json!({"format":"dever-registry-runtime-v1","runtime":runtime})) .unwrap(), ) .unwrap(); for suffix in ["runtime.pack", "manifest.json"] { let name = format!("{prefix}/{suffix}"); let artifact = manifest .artifacts .iter_mut() .find(|artifact| artifact.path == name) .unwrap(); artifact.bytes = fs::metadata(release.join(&name)).unwrap().len(); artifact.sha256 = sha256_file(&release.join(&name)).unwrap(); } } let encoded = serde_json::to_vec(&manifest).unwrap(); fs::write(release.join("manifest.json"), &encoded).unwrap(); fs::write( release.join("manifest.sig"), key.sign(&encoded) .as_ref() .iter() .map(|byte| format!("{byte:02x}")) .collect::(), ) .unwrap(); manifest } fn import_built_dependencies(project: &Path, fixture: &Path, layout: &Layout) { use dever_cli::libs::{LockFile, LockedWorker, build}; let mut lock = LockFile::decode(&fs::read(fixture.join("dependency/dever.lock")).unwrap()).unwrap(); for artifact in lock .libs .iter() .flat_map(|lib| &lib.artifacts) .chain(build::npm_outputs(&lock).map(|receipt| &receipt.output)) { let bytes = fs::read(fixture.join("dependency/artifacts").join(&artifact.sha256)).unwrap(); let stored = dever_cli::toolchain::artifact_put(layout, &bytes).unwrap(); assert_eq!(stored.sha256, artifact.sha256); assert_eq!(stored.bytes, artifact.bytes); } let sources = dever_core::source::SourceMap::load(&project.join("module")).unwrap(); let program = dever_core::check(&sources).unwrap_or_else(|errors| panic!("{errors:?}")); let contract = program.external_worker_contracts().remove(0); let runtime = lock.libs[0].runtime.clone(); assert!(lock.libs.iter().all(|lib| lib.runtime == runtime)); assert!(lock.workers.is_empty()); lock.workers.push(LockedWorker { port: contract.port, adapter: contract.adapter, ecosystem: contract.ecosystem, runtime: Some(runtime), entry: contract.entry, schema: contract.schema, capabilities: contract.capabilities, operations: contract.operations, libs: contract .libs .iter() .map(|spec| spec.parse().unwrap()) .collect(), }); dever_cli::libs::doctor(&lock).unwrap(); lock.write_atomic(project).unwrap(); } fn ecosystem_inputs(author: &Path, workspace: &Path) { let prepared = workspace.join("target/ecosystem-release-inputs/prepared"); let fragment: Value = serde_json::from_slice(&fs::read(prepared.join("config/setting.json")).unwrap()).unwrap(); for ecosystem in ECOSYSTEMS { for entry in fragment["runtimes"][ecosystem]["files"].as_array().unwrap() { let relative = entry["source"].as_str().unwrap(); let output = author.join(relative); fs::create_dir_all(output.parent().unwrap()).unwrap(); // Author staging only; the maker materializes its own immutable pack. fs::hard_link(prepared.join(relative), output).unwrap(); } } let config = author.join("config/setting.json"); let mut settings: Value = serde_json::from_slice(&fs::read(&config).unwrap()).unwrap(); settings["runtimes"] = fragment["runtimes"].clone(); fs::write(config, serde_json::to_vec(&settings).unwrap()).unwrap(); } fn application(root: &Path, ecosystem: &str) { let (extension, source) = match ecosystem { "pip" => ("py", include_str!("../../ecosystem-release/probe.py")), "npm" => ("mjs", include_str!("../../ecosystem-release/probe.mjs")), "go" => ("go", include_str!("../../ecosystem-release/probe.go")), _ => unreachable!(), }; write(root, "config/setting.json", "{}\n"); write( root, "module/sample/worker/port.dever", "probe() (okay: Bool) fails app.ProbeResult\n", ); write( root, "module/sample/worker/app.dever", "type ProbeResult { error Unavailable(message: Text) }\nprobe() (okay: Bool) { okay = port.probe() }\n", ); write( root, "module/sample/worker/api.dever", "cmd probe = app.probe\n", ); write( root, "module/sample/worker/adapter.dever", format!( "external {ecosystem} \"worker/probe.{extension}\" {{\n{}\n}}\n", match ecosystem { "pip" => "lib \"simplejson@3.20.1\"", "npm" => "lib \"bufferutil@4.0.9\"", "go" => "", _ => unreachable!(), } ), ); let sources = dever_core::source::SourceMap::load(&root.join("module")).unwrap(); let program = dever_core::check(&sources).unwrap_or_else(|errors| panic!("{errors:?}")); let entry = &program.external_worker_contracts()[0].entry; write(root, entry, source); } pub(super) fn minimal_os(root: &Path) { // This root contains only the named Linux OS ABI libraries. No host language // executable, standard library, PATH, package manager or source tree is visible. for name in [ "libc.so.6", "libdl.so.2", "libm.so.6", "libpthread.so.0", "librt.so.1", "libutil.so.1", "libresolv.so.2", "ld-linux-x86-64.so.2", ] { let source = Path::new("/lib/x86_64-linux-gnu").join(name); let output = root.join("lib/x86_64-linux-gnu").join(name); fs::create_dir_all(output.parent().unwrap()).unwrap(); fs::copy(source, output).unwrap(); } fs::create_dir(root.join("lib64")).unwrap(); fs::copy( "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2", root.join("lib64/ld-linux-x86-64.so.2"), ) .unwrap(); fs::create_dir(root.join("tmp")).unwrap(); fs::create_dir(root.join("proc")).unwrap(); fs::create_dir(root.join("dev")).unwrap(); // The outer namespace supplies only its own proc and synthetic device tree. assert!(!root.join("usr").exists()); } #[test] #[ignore = "requires prepared pinned ecosystem inputs and root for owned namespace; no existing services"] fn signed_ecosystems_build_and_run_without_system_language_installations() { assert_eq!( rustix::process::geteuid().as_raw(), 0, "explicit no-host acceptance requires the owned root namespace fixture" ); let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); let projects = TemporaryDirectory::new(); for ecosystem in ECOSYSTEMS { application(&projects.path().join(ecosystem), ecosystem); } let author = TemporaryDirectory::new(); let key = author_inputs(author.path(), &workspace); ecosystem_inputs(author.path(), &workspace); let machine = machine_temporary_directory(&workspace); let layout = Layout::new(machine.path()); layout.initialize().unwrap(); let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); let release = layout.downloads().join(version.as_str()); command_with_timeout( Command::new(workspace.join("target/debug/examples/native-release")) .arg(author.path()) .arg("--output") .arg(&release), // Debug gzip compresses complete language distributions; this is author // preparation, independent of the service's bounded compile deadline. Duration::from_secs(900), ); let signed = sign_build_runtimes(&release, &workspace, &key); let mut runtimes = serde_json::Map::new(); for ecosystem in ECOSYSTEMS { let path = format!("runtime/{ecosystem}/linux-x86_64/runtime.pack"); let artifact = signed .artifacts .iter() .find(|artifact| artifact.path == path) .unwrap(); runtimes.insert( ecosystem.into(), json!({"bytes":artifact.bytes,"sha256":artifact.sha256}), ); } install_release(&layout, &version, &key); fs::remove_dir_all(&release).unwrap(); drop(author); let core = layout.versions().join(version.as_str()).join("dever-core"); let mut daemon = daemon::TestDaemon::start(&layout); let isolated = TemporaryDirectory::new(); minimal_os(isolated.path()); let mut programs = serde_json::Map::new(); for ecosystem in ECOSYSTEMS { let project = projects.path().join(ecosystem); if ecosystem == "go" { command(Command::new(&core).args(["lib", "add"]).arg(&project)); } else { import_built_dependencies( &project, &dependency_fixture(&workspace, ecosystem), &layout, ); } let lock = dever_cli::libs::LockFile::decode(&fs::read(project.join("dever.lock")).unwrap()) .unwrap(); assert_eq!(lock.workers.len(), 1); assert_eq!(lock.libs.is_empty(), ecosystem == "go"); assert_eq!(lock.builds.is_empty(), ecosystem == "go"); assert_eq!( lock.workers[0].runtime.as_ref().unwrap().sha256, runtimes[ecosystem]["sha256"].as_str().unwrap() ); command(Command::new(&core).arg("check").arg(&project)); assert_response(command( Command::new(&core) .arg("run") .arg(&project) .args(["--", "sample.worker.probe", "{}"]), )); let destination = isolated.path().join(ecosystem); fs::create_dir(&destination).unwrap(); write(&destination, "config/setting.json", "{}\n"); let executable = destination.join("program"); command( Command::new(&core) .arg("build") .arg(&project) .arg("--output") .arg(&executable), ); programs.insert(ecosystem.into(), json!({"bytes":fs::metadata(&executable).unwrap().len(),"sha256":sha256_file(&executable).unwrap()})); } assert_eq!(cache_status(&layout).unwrap().entries, 3); daemon.stop(); drop(machine); drop(projects); for ecosystem in ECOSYSTEMS { assert_response(command( isolated_os_command(isolated.path()) .arg(format!("/{ecosystem}/program")) .args(["sample.worker.probe", "{}"]), )); } let mut standalone_nonroot = serde_json::Map::new(); for ecosystem in ECOSYSTEMS { standalone_nonroot.insert( ecosystem.into(), nonroot_standalone(&isolated.path().join(ecosystem), &programs[ecosystem]), ); } write(&workspace, "target/closure-sandbox-ecosystem-release-acceptance.json", serde_json::to_vec_pretty(&json!({ "format":"dever-ecosystem-release-acceptance-v1", "platform":"linux-x86_64", "runtimes":runtimes,"programs":programs,"standalone_nonroot":standalone_nonroot, "checks":{"signed_install":true,"locked_runtimes":3,"managed_check_run_build":3,"standalone_isolated_root":3,"standalone_nonroot":3,"shared_compile_entries":3,"python_stdlib_paths_in_bundle":true,"os_sandbox":true,"offline_build_receipt_replay":true}, "os_baseline":"explicit Linux GNU OS libraries and named device nodes inside an owned root", "runtime_pack_provenance":"test author signed the exact pip/npm packs bound to the completed source builds; maker determinism is verified separately", "third_party_packages":{"pip":"simplejson@3.20.1 _speedups C extension","npm":"bufferutil@4.0.9 build/Release addon"}, "other_platforms":"not_run", "public_release":"not_published" })).unwrap()); }