diff --git a/Makefile b/Makefile index 40bfdb723..214e2782d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ EMBASSY_BINS := backend/target/aarch64-unknown-linux-gnu/release/embassyd backend/target/aarch64-unknown-linux-gnu/release/embassy-init backend/target/aarch64-unknown-linux-gnu/release/embassy-cli backend/target/aarch64-unknown-linux-gnu/release/embassy-sdk EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnostic-ui EMBASSY_SRC := raspios.img product_key.txt $(EMBASSY_BINS) backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(shell find build) +EMBASSY_V8_SNAPSHOTS := backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin COMPAT_SRC := $(shell find system-images/compat/src) UTILS_SRC := $(shell find system-images/utils/Dockerfile) BACKEND_SRC := $(shell find backend/src) $(shell find patch-db/*/src) $(shell find rpc-toolkit/*/src) backend/Cargo.toml backend/Cargo.lock @@ -28,7 +29,7 @@ clean: rm -rf patch-db/client/node_modules rm -rf patch-db/client/dist -eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar +eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar $(EMBASSY_V8_SNAPSHOTS) ! test -f eos.img || rm eos.img if [ "$(NO_KEY)" = "1" ]; then NO_KEY=1 ./build/make-image.sh; else ./build/make-image.sh; fi @@ -50,6 +51,9 @@ product_key.txt: if [ "$(KEY)" != "" ]; then $(shell which echo) -n "$(KEY)" > product_key.txt; fi echo >> product_key.txt +$(EMBASSY_V8_SNAPSHOTS): $(BACKEND_SRC) $(EMBASSY_BINS) + cd backend && ./build-arm-v8-snapshot.sh + $(EMBASSY_BINS): $(BACKEND_SRC) cd backend && ./build-prod.sh diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c089321ae..fabdfa725 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -823,9 +823,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.134.0" +version = "0.135.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339890ba27bf35d5185400bfce383cbdcba49c2a38f0c819a144d6d710a1e34c" +checksum = "32cd837520179a6f8063fe542b98dacec14b44ce647990be476b6eca8e6125e5" dependencies = [ "anyhow", "deno_ops", @@ -846,9 +846,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb254e9a65955eddbaaa23298cb6ec23902984cadc550e75c16245a9ce16cc2" +checksum = "f6ab6a5a7c3d5b9fbd43064996bbe61799db5e0bfb0f46672b2f85c0192d15a9" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.36", @@ -3247,10 +3247,11 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898109a3146fe6ef36047584e13fa32630d01ddd090977a39f2fe19de7b293fa" +checksum = "f7797d56c9575ced9175e22366e5bd4cc8f3d571cd8c75be510f410ab97a54f6" dependencies = [ + "bytes 1.1.0", "derive_more", "serde", "v8", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b292bbb3e..e0b37082b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -24,6 +24,10 @@ path = "src/lib.rs" name = "embassyd" path = "src/bin/embassyd.rs" +[[bin]] +name = "create-js-snapshots" +path = "src/bin/build-js-snapshots.rs" + [[bin]] name = "embassy-init" path = "src/bin/embassy-init.rs" @@ -61,8 +65,8 @@ color-eyre = "0.5" cookie_store = "0.15.0" digest = "0.9.0" divrem = "1.0.0" -deno_core = "0.134.0" -deno_ast = {version="0.14.0", features = ["transpiling"]} +deno_core = "0.135.0" +deno_ast = {version="0.14.1", features = ["transpiling"]} ed25519-dalek = { version = "1.0.1", features = ["serde"] } emver = { version = "0.1.6", features = ["serde"] } fd-lock-rs = "0.1.3" diff --git a/backend/build-arm-v8-snapshot.sh b/backend/build-arm-v8-snapshot.sh new file mode 100755 index 000000000..caf5f8598 --- /dev/null +++ b/backend/build-arm-v8-snapshot.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Reason for this being is that we need to create a snapshot for the deno runtime. It wants to pull 3 files from build, and during the creation it gets embedded, but for some +# reason during the actual runtime it is looking for them. So this will create a docker in arm that creates the snaphot needed for the arm +set -e + +if [ "$0" != "./build-arm-v8-snapshot.sh" ]; then + >&2 echo "Must be run from backend directory" + exit 1 +fi + +echo "Creating Arm v8 Snapshot" +docker run --platform linux/arm64/v8 --mount type=bind,src=$(pwd),dst=/mnt arm64v8/ubuntu:20.04 /bin/sh -c "cd /mnt && /mnt/target/aarch64-unknown-linux-gnu/release/create-js-snapshots" +mv JS_SNAPSHOT.bin src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin \ No newline at end of file diff --git a/backend/src/bin/build-js-snapshots.rs b/backend/src/bin/build-js-snapshots.rs new file mode 100644 index 000000000..971fad000 --- /dev/null +++ b/backend/src/bin/build-js-snapshots.rs @@ -0,0 +1,12 @@ +fn main() { + let mut runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { + will_snapshot: true, + ..Default::default() + }); + let snapshot = runtime.snapshot(); + + let snapshot_slice: &[u8] = &*snapshot; + println!("Snapshot size: {}", snapshot_slice.len()); + + std::fs::write("JS_SNAPSHOT.bin", snapshot_slice).unwrap(); +} diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 14f55c464..53cffbcef 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -187,7 +187,6 @@ impl ConfigGetReceipts { locks: &mut Vec, id: &PackageId, ) -> impl FnOnce(&Verifier) -> Result { - let manifest_version = crate::db::DatabaseModel::new() .package_data() .idx_model(id) @@ -680,6 +679,7 @@ pub fn configure_rec<'a, Db: DbHandle>( dependent, &manifest.version, &manifest.volumes, + dependent, &config, ) .await? diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index 56ec1231e..f5dae92ad 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -14,7 +14,6 @@ use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::config::action::{ConfigActions, ConfigRes}; use crate::config::spec::PackagePointerSpec; use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec}; use crate::context::RpcContext; @@ -27,6 +26,10 @@ use crate::util::serde::display_serializable; use crate::util::{display_none, Version}; use crate::volume::Volumes; use crate::Error; +use crate::{ + config::action::{ConfigActions, ConfigRes}, + procedure::ProcedureName, +}; #[command(subcommands(configure))] pub fn dependency() -> Result<(), Error> { @@ -257,6 +260,7 @@ impl DependencyError { id, &dependent_manifest.version, &dependent_manifest.volumes, + dependency, &dependency_config, ) .await? @@ -496,6 +500,7 @@ impl DependencyConfig { dependent_id: &PackageId, dependent_version: &Version, dependent_volumes: &Volumes, + dependency_id: &PackageId, dependency_config: &Config, ) -> Result, Error> { Ok(self @@ -507,6 +512,7 @@ impl DependencyConfig { dependent_volumes, Some(dependency_config), None, + ProcedureName::Check(dependency_id.clone()), ) .await? .map_err(|(_, e)| e)) @@ -527,6 +533,7 @@ impl DependencyConfig { dependent_volumes, Some(old), None, + ProcedureName::AutoConfig(dependent_id.clone()), ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure)) @@ -730,6 +737,7 @@ pub async fn configure_logic( &pkg_volumes, Some(&old_config), None, + ProcedureName::AutoConfig(dependency_id.clone()), ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?; diff --git a/backend/src/procedure/build.rs b/backend/src/procedure/build.rs new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/procedure/js_scripts.rs b/backend/src/procedure/js_scripts.rs index d6ba0b796..99e5f43ca 100644 --- a/backend/src/procedure/js_scripts.rs +++ b/backend/src/procedure/js_scripts.rs @@ -30,7 +30,7 @@ pub enum JsError { pub struct JsProcedure {} impl JsProcedure { - pub fn validate(&self, volumes: &Volumes) -> Result<(), color_eyre::eyre::Report> { + pub fn validate(&self, _volumes: &Volumes) -> Result<(), color_eyre::eyre::Report> { Ok(()) } @@ -53,7 +53,6 @@ impl JsProcedure { volumes.clone(), ) .await? - .with_effects() .run_action(name, input); let output: O = match timeout { Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action) @@ -76,6 +75,7 @@ impl JsProcedure { volumes: &Volumes, input: Option, timeout: Option, + name: ProcedureName, ) -> Result, Error> { Ok(async move { let running_action = JsExecutionEnvironment::load_from_package( @@ -86,7 +86,7 @@ impl JsProcedure { ) .await? .read_only_effects() - .run_action(ProcedureName::GetConfig, input); + .run_action(name, input); let output: O = match timeout { Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action) .await @@ -111,6 +111,7 @@ mod js_runtime { use deno_core::ModuleSpecifier; use deno_core::ModuleType; use deno_core::RuntimeOptions; + use deno_core::Snapshot; use deno_core::{Extension, OpDecl}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -120,11 +121,16 @@ mod js_runtime { use crate::s9pk::manifest::PackageId; use crate::util::Version; - use crate::volume::{script_dir, Volumes}; + use crate::volume::script_dir; + use crate::volume::Volumes; use super::super::ProcedureName; use super::{JsCode, JsError}; + #[cfg(target_arch = "x86_64")] + const SNAPSHOT_BYTES: &[u8] = include_bytes!("./js_scripts/JS_SNAPSHOT.bin"); + #[cfg(target_arch = "aarch64")] + const SNAPSHOT_BYTES: &[u8] = include_bytes!("./js_scripts/ARM_JS_SNAPSHOT.bin"); #[derive(Clone, Deserialize, Serialize)] struct JsContext { sandboxed: bool, @@ -212,7 +218,6 @@ mod js_runtime { module_loader: ModsLoader, package_id: PackageId, version: Version, - operations: Vec, volumes: Arc, } @@ -230,6 +235,7 @@ mod js_runtime { let mut file = match tokio::fs::File::open(file_path.clone()).await { Ok(x) => x, Err(e) => { + tracing::debug!("path: {:?}", file_path); tracing::debug!("{:?}", e); return Err(( JsError::FileSystem, @@ -250,7 +256,6 @@ mod js_runtime { Ok(Self { base_directory: base_directory.to_owned(), module_loader: ModsLoader { code: js_code }, - operations: Default::default(), package_id: package_id.clone(), version: version.clone(), volumes: Arc::new(volumes), @@ -259,29 +264,9 @@ mod js_runtime { } pub fn read_only_effects(mut self) -> Self { self.sandboxed = true; - self.with_effects() - } - - pub fn with_effects(mut self) -> Self { - self.operations = vec![ - fns::read_file::decl(), - fns::write_file::decl(), - fns::create_dir::decl(), - fns::remove_dir::decl(), - fns::get_context::decl(), - fns::log_trace::decl(), - fns::log_warn::decl(), - fns::log_error::decl(), - fns::log_debug::decl(), - fns::log_info::decl(), - fns::current_function::decl(), - fns::set_value::decl(), - fns::remove_file::decl(), - fns::is_sandboxed::decl(), - fns::get_input::decl(), - ]; self } + pub async fn run_action Deserialize<'de>>( self, procedure_name: ProcedureName, @@ -318,6 +303,25 @@ mod js_runtime { } } } + fn declarations() -> Vec { + vec![ + fns::read_file::decl(), + fns::write_file::decl(), + fns::remove_file::decl(), + fns::create_dir::decl(), + fns::remove_dir::decl(), + fns::current_function::decl(), + fns::log_trace::decl(), + fns::log_warn::decl(), + fns::log_error::decl(), + fns::log_debug::decl(), + fns::log_info::decl(), + fns::get_context::decl(), + fns::get_input::decl(), + fns::set_value::decl(), + fns::is_sandboxed::decl(), + ] + } fn execute( &self, @@ -337,7 +341,7 @@ mod js_runtime { input, }; let ext = Extension::builder() - .ops(self.operations.clone()) + .ops(Self::declarations()) .state(move |state| { state.put(ext_answer_state.clone()); state.put(js_ctx.clone()); @@ -346,11 +350,13 @@ mod js_runtime { .build(); let loader = std::rc::Rc::new(self.module_loader.clone()); - let mut runtime = JsRuntime::new(RuntimeOptions { + let runtime_options = RuntimeOptions { module_loader: Some(loader), extensions: vec![ext], + startup_snapshot: Some(Snapshot::Static(SNAPSHOT_BYTES)), ..Default::default() - }); + }; + let mut runtime = JsRuntime::new(runtime_options); let future = async move { let mod_id = runtime @@ -358,8 +364,8 @@ mod js_runtime { .await?; let evaluated = runtime.mod_evaluate(mod_id); let res = runtime.run_event_loop(false).await; - evaluated.await??; res?; + evaluated.await??; Ok::<_, AnyError>(()) }; @@ -367,7 +373,7 @@ mod js_runtime { .block_on(future) .map_err(|e| { tracing::debug!("{:?}", e); - (JsError::Javascript, format!("Execution error: {}", e)) + (JsError::Javascript, format!("{}", e)) })?; let answer = answer_state.0.lock().clone(); @@ -377,10 +383,17 @@ mod js_runtime { /// Note: Make sure that we have the assumption that all these methods are callable at any time, and all call restrictions should be in rust mod fns { - use deno_core::{anyhow::bail, error::AnyError, *}; + use deno_core::{ + anyhow::{anyhow, bail}, + error::AnyError, + *, + }; use serde_json::Value; - use std::{convert::TryFrom, path::PathBuf}; + use std::{ + convert::TryFrom, + path::{Path, PathBuf}, + }; use crate::volume::VolumeId; @@ -402,8 +415,12 @@ mod js_runtime { volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id); //get_path_for in volume.rs let new_file = volume_path.join(path_in); - if !new_file.starts_with(volume_path) { - bail!("Path has broken away from parent"); + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); } let answer = tokio::fs::read_to_string(new_file).await?; Ok(answer) @@ -429,10 +446,18 @@ mod js_runtime { } let volume_path = volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id); + let new_file = volume_path.join(path_in); + let parent_new_file = new_file + .parent() + .ok_or_else(|| anyhow!("Expecting that file is not root"))?; // With the volume check - if !new_file.starts_with(volume_path) { - bail!("Path has broken away from parent"); + if !is_subset(&volume_path, &parent_new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); } tokio::fs::write(new_file, write).await?; Ok(()) @@ -457,10 +482,14 @@ mod js_runtime { } let volume_path = volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id); - let new_file = volume_path.clone().join(path_in); + let new_file = volume_path.join(path_in); // With the volume check - if !new_file.starts_with(volume_path) { - bail!("Path has broken away from parent"); + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); } tokio::fs::remove_file(new_file).await?; Ok(()) @@ -485,10 +514,14 @@ mod js_runtime { } let volume_path = volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id); - let new_file = volume_path.clone().join(path_in); + let new_file = volume_path.join(path_in); // With the volume check - if !new_file.starts_with(volume_path) { - bail!("Path has broken away from parent"); + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); } tokio::fs::remove_dir_all(new_file).await?; Ok(()) @@ -513,10 +546,17 @@ mod js_runtime { } let volume_path = volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id); - let new_file = volume_path.clone().join(path_in); + let new_file = volume_path.join(path_in); + let parent_new_file = new_file + .parent() + .ok_or_else(|| anyhow!("Expecting that file is not root"))?; // With the volume check - if !new_file.starts_with(volume_path) { - bail!("Path has broken away from parent"); + if !is_subset(&volume_path, &parent_new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); } tokio::fs::create_dir_all(new_file).await?; Ok(()) @@ -605,12 +645,24 @@ mod js_runtime { let ctx = state.borrow::(); Ok(ctx.sandboxed) } + + /// We need to make sure that during the file accessing, we don't reach beyond our scope of control + async fn is_subset(parent: impl AsRef, child: impl AsRef) -> Result { + let child = tokio::fs::canonicalize(child).await?; + let parent = tokio::fs::canonicalize(parent).await?; + Ok(child.starts_with(parent)) + } } } + #[tokio::test] async fn js_action_execute() { let js_action = JsProcedure {}; - let path: PathBuf = "test/js_action_execute/".parse().unwrap(); + let path: PathBuf = "test/js_action_execute/" + .parse::() + .unwrap() + .canonicalize() + .unwrap(); let package_id = "test-package".parse().unwrap(); let package_version: Version = "0.3.0.3".parse().unwrap(); let name = ProcedureName::GetConfig; @@ -631,7 +683,7 @@ async fn js_action_execute() { })) .unwrap(); let input: Option = Some(serde_json::json!({"test":123})); - let timeout = None; + let timeout = Some(Duration::from_secs(10)); let _output: crate::config::action::ConfigRes = js_action .execute( &path, diff --git a/backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin b/backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin new file mode 100644 index 000000000..a927ac093 Binary files /dev/null and b/backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin differ diff --git a/backend/src/procedure/js_scripts/JS_SNAPSHOT.bin b/backend/src/procedure/js_scripts/JS_SNAPSHOT.bin new file mode 100644 index 000000000..65249a414 Binary files /dev/null and b/backend/src/procedure/js_scripts/JS_SNAPSHOT.bin differ diff --git a/backend/src/procedure/js_scripts/loadModule.js b/backend/src/procedure/js_scripts/loadModule.js index 3f9e9bc35..c9a030e26 100644 --- a/backend/src/procedure/js_scripts/loadModule.js +++ b/backend/src/procedure/js_scripts/loadModule.js @@ -1,6 +1,6 @@ //@ts-check // @ts-ignore -import Deno from "/deno_global.js"; +import Deno from "/deno_global.js"; // @ts-ignore import * as mainModule from "/embassy.js"; /** @@ -23,36 +23,28 @@ function jsonPointerValue(obj, pointer) { // @ts-ignore const context = Deno.core.opSync("get_context"); // @ts-ignore -const writeFile = ({ path, volumeId, toWrite }) => - Deno.core.opAsync("write_file", context, volumeId, path, toWrite); +const writeFile = ({ path, volumeId, toWrite }) => Deno.core.opAsync("write_file", context, volumeId, path, toWrite); // @ts-ignore -const readFile = ({ volumeId, path }) => - Deno.core.opAsync("read_file", context, volumeId, path); +const readFile = ({ volumeId, path }) => Deno.core.opAsync("read_file", context, volumeId, path); // @ts-ignore -const removeFile = ({ volumeId, path }) => - Deno.core.opAsync("remove_file", context, volumeId, path); +const removeFile = ({ volumeId, path }) => Deno.core.opAsync("remove_file", context, volumeId, path); // @ts-ignore const isSandboxed = () => Deno.core.opSync("is_sandboxed"); // @ts-ignore const writeJsonFile = ({ volumeId, path, toWrite }) => - Deno.core.opAsync( - "write_file", - context, + writeFile({ volumeId, path, - JSON.stringify(toWrite) - ); + toWrite: JSON.stringify(toWrite), + }); // @ts-ignore -const readJsonFile = ({ volumeId, path }) => - JSON.parse(Deno.core.opAsync("read_file", context, volumeId, path)); +const readJsonFile = async ({ volumeId, path }) => JSON.parse(await readFile({ volumeId, path })); // @ts-ignore -const createDir = ({ volumeId, path }) => - Deno.core.opAsync("create_dir", context, volumeId, path); +const createDir = ({ volumeId, path }) => Deno.core.opAsync("create_dir", context, volumeId, path); // @ts-ignore -const removeDir = ({ volumeId, path }) => - Deno.core.opAsync("remove_dir", context, volumeId, path); +const removeDir = ({ volumeId, path }) => Deno.core.opAsync("remove_dir", context, volumeId, path); // @ts-ignore const trace = (x) => Deno.core.opSync("log_trace", x); // @ts-ignore @@ -86,14 +78,11 @@ const effects = { removeDir, }; -const runFunction = jsonPointerValue( - mainModule, - currentFunction -); +const runFunction = jsonPointerValue(mainModule, currentFunction); (async () => { if (typeof runFunction !== "function") { - error(`Expecting ${{ currentFunction }} to be a function`); - throw new Error(`Expecting ${{ currentFunction }} to be a function`); + error(`Expecting ${ currentFunction } to be a function`); + throw new Error(`Expecting ${ currentFunction } to be a function`); } const answer = await runFunction(effects, input); setState(answer); diff --git a/backend/src/procedure/mod.rs b/backend/src/procedure/mod.rs index cafc992e5..966b32fe0 100644 --- a/backend/src/procedure/mod.rs +++ b/backend/src/procedure/mod.rs @@ -28,6 +28,8 @@ pub enum ProcedureName { SetConfig, Migration, Properties, + Check(PackageId), + AutoConfig(PackageId), Health(HealthCheckId), Action(ActionId), } @@ -44,11 +46,13 @@ impl ProcedureName { ProcedureName::Properties => Some(format!("Properties-{}", rand::random::())), ProcedureName::Health(id) => Some(format!("{}Health", id)), ProcedureName::Action(id) => Some(format!("{}Action", id)), + ProcedureName::Check(_) => None, + ProcedureName::AutoConfig(_) => None, } } fn js_function_name(&self) -> String { match self { - ProcedureName::Main => todo!(), + ProcedureName::Main => "/main".to_string(), ProcedureName::CreateBackup => "/createBackup".to_string(), ProcedureName::RestoreBackup => "/restoreBackup".to_string(), ProcedureName::GetConfig => "/getConfig".to_string(), @@ -57,6 +61,8 @@ impl ProcedureName { ProcedureName::Properties => "/properties".to_string(), ProcedureName::Health(id) => format!("/health/{}", id), ProcedureName::Action(id) => format!("/action/{}", id), + ProcedureName::Check(id) => format!("/dependencies/{}/check", id), + ProcedureName::AutoConfig(id) => format!("/dependencies/{}/autoConfigure", id), } } } @@ -136,6 +142,7 @@ impl PackageProcedure { volumes: &Volumes, input: Option, timeout: Option, + name: ProcedureName, ) -> Result, Error> { match self { PackageProcedure::Docker(procedure) => { @@ -145,7 +152,7 @@ impl PackageProcedure { } PackageProcedure::Script(procedure) => { procedure - .sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout) + .sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout, name) .await } } diff --git a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js index db8532089..11c4091c3 100644 --- a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js +++ b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js @@ -10,19 +10,53 @@ export function properties() { * @returns {Promise} */ export async function getConfig(effects) { + try{ await effects.writeFile({ - path: "./test.log", + path: "../test.log", toWrite: "This is a test", volumeId: 'main', }); + throw new Error("Expecting that the ../test.log should not be a valid path since we are breaking out of the parent") + } catch(e) {} + try{ + await effects.writeFile({ + path: "./hack_back/broken.log", + toWrite: "This is a test", + volumeId: 'main', + }); + throw new Error("Expecting that using a symlink to break out of parent still fails for writing") + } catch(e) {} + try{ + await effects.createDir({ + path: "./hack_back/broken_dir", + volumeId: 'main', + }); + throw new Error("Expecting that using a symlink to break out of parent still fails for writing dir") + } catch(e) {} + try{ + await effects.readFile({ + path: "./hack_back/data/bad_file.txt", + volumeId: 'main', + }); + throw new Error("Expecting that using a symlink to break out of parent still fails for reading") + } catch(e) {} + + // Testing dir, create + delete await effects.createDir({ path: "./testing", volumeId: 'main',}); - await effects.writeFile({ + await effects.writeJsonFile({ path: "./testing/test2.log", - toWrite: "This is a test", + toWrite: {value: "This is a test"}, volumeId: 'main', }); + + (await effects.readJsonFile({ + path: "./testing/test2.log", + volumeId: 'main', + // @ts-ignore + })).value; + await effects.removeFile({ path: "./testing/test2.log", volumeId: 'main', @@ -30,10 +64,20 @@ export async function getConfig(effects) { await effects.removeDir({ path: "./testing", volumeId: 'main',}); + + + // Testing reading + writing + await effects.writeFile({ + path: "./test.log", + toWrite: "This is a test", + volumeId: 'main', + }); + effects.debug(`Read results are ${await effects.readFile({ path: "./test.log", volumeId: 'main', })}`) + // Testing loging effects.trace('trace') effects.debug('debug') effects.warn('warn') @@ -587,6 +631,7 @@ export async function getConfig(effects) { export async function setConfig(effects, input) { return { + signal: "SIGTERM", "depends-on": {} } } diff --git a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/types.d.ts b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/types.d.ts index 1c7b5c8b3..e14d2781e 100644 --- a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/types.d.ts +++ b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/types.d.ts @@ -1,191 +1,266 @@ export type Effects = { - writeFile(input: {path: string, volumeId: string, toWrite: string}): Promise, - readFile(input: {volumeId: string,path: string}): Promise, - createDir(input: {volumeId: string,path: string}): Promise, - removeDir(input: {volumeId: string,path: string}): Promise, - removeFile(input: {volumeId: string,path: string}): Promise, - writeJsonFile(input: {volumeId: string,path: string, toWrite: object}): void, - readJsonFile(input:{volumeId: string,path: string}): object, - trace(whatToPrin: string), - warn(whatToPrin: string), - error(whatToPrin: string), - debug(whatToPrin: string), - info(whatToPrin: string), - is_sandboxed(): boolean, -} + writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise; + readFile(input: { volumeId: string; path: string }): Promise; + createDir(input: { volumeId: string; path: string }): Promise; + removeDir(input: { volumeId: string; path: string }): Promise; + removeFile(input: { volumeId: string; path: string }): Promise; + writeJsonFile(input: { volumeId: string; path: string; toWrite: object }): Promise; + readJsonFile(input: { volumeId: string; path: string }): Promise; + trace(whatToPrin: string): void; + warn(whatToPrin: string): void; + error(whatToPrin: string): void; + debug(whatToPrin: string): void; + info(whatToPrin: string): void; + is_sandboxed(): boolean; +}; export type ActionResult = { - version: "0", - message: string, - value?: string, - copyable: boolean, - qr: boolean, -} + version: "0"; + message: string; + value?: string; + copyable: boolean; + qr: boolean; +}; export type ConfigRes = { - config?: Config, - spec: ConfigSpec, - } + config?: Config; + spec: ConfigSpec; +}; export type Config = { - [value: string]: any - } + [value: string]: any; +}; -export type ConfigSpec = { - [value: string]: ValueSpecAny - } - export type WithDefault = T & { - default?: Default - } +export type ConfigSpec = { + [value: string]: ValueSpecAny; +}; +export type WithDefault = T & { + default?: Default; +}; export type WithDescription = T & { - description?: String, - name: string, - warning?: string, -} + description?: String; + name: string; + warning?: string; +}; export type ListSpec = { - spec: T, - range: string -} + spec: T; + range: string; +}; export type Tag = V & { - type: T -} + type: T; +}; export type Subtype = V & { - subtype: T -} + subtype: T; +}; export type Target = V & { - "target": T -} + target: T; +}; export type UniqueBy = -|{ - any: UniqueBy[], -} -| { - all: UniqueBy[] -} -| string -| null + | { + any: UniqueBy[]; + } + | { + all: UniqueBy[]; + } + | string + | null; export type WithNullable = T & { - nullable: boolean -} -export type DefaultString = String | { - charset?: string, - len: number -} + nullable: boolean; +}; +export type DefaultString = + | String + | { + charset?: string; + len: number; + }; -export type ValueSpecString= ({} | { - pattern: string, - 'pattern-description': string -}) & { - copyable?: boolean, - masked?: boolean, - placeholder?: string - -} +export type ValueSpecString = ( + | {} + | { + pattern: string; + "pattern-description": string; + } +) & { + copyable?: boolean; + masked?: boolean; + placeholder?: string; +}; export type ValueSpecNumber = { - range?: string, - integral?: boolean, - units?: string, - placeholder?: number, -} -export type ValueSpecBoolean = {} -export type ValueSpecAny = -| Tag<'boolean', WithDescription>> -| Tag<'string', WithDescription, DefaultString>>> -| Tag<'number', WithDescription, number>>> -| Tag<'enum', WithDescription>> -| Tag<'list', ValueSpecList> -| Tag<'object', WithDescription>> -| Tag<'union', WithDescription>> -| Tag<'pointer', WithDescription< - | Subtype<'package', - | Target<'tor-key', { - 'package-id': string - interface: string - }> - | Target<'tor-address', { - 'package-id': string, - interface: string - } > - | Target<'lan-address',{ - 'package-id': string, - interface: string - } > - | Target<'config', { - 'package-id': string, - selector: string, - multi: boolean - }> + range?: string; + integral?: boolean; + units?: string; + placeholder?: number; +}; +export type ValueSpecBoolean = {}; +export type ValueSpecAny = + | Tag<"boolean", WithDescription>> + | Tag<"string", WithDescription, DefaultString>>> + | Tag<"number", WithDescription, number>>> + | Tag< + "enum", + WithDescription< + WithDefault< + { + values: string[]; + "value-names": { + [key: string]: string; + }; + }, + string + > + > > - | Subtype<'system', {}> ->> + | Tag<"list", ValueSpecList> + | Tag<"object", WithDescription>> + | Tag<"union", WithDescription>> + | Tag< + "pointer", + WithDescription< + | Subtype< + "package", + | Target< + "tor-key", + { + "package-id": string; + interface: string; + } + > + | Target< + "tor-address", + { + "package-id": string; + interface: string; + } + > + | Target< + "lan-address", + { + "package-id": string; + interface: string; + } + > + | Target< + "config", + { + "package-id": string; + selector: string; + multi: boolean; + } + > + > + | Subtype<"system", {}> + > + >; export type ValueSpecUnion = { - tag: { - id: string, - name: string, - description?: string, - "variant-names": { - [key: string]: string, - } - }, - variants: { - [key: string]: ConfigSpec - }, - "display-as"?: string, - "unique-by"?: UniqueBy - -} + tag: { + id: string; + name: string; + description?: string; + "variant-names": { + [key: string]: string; + }; + }; + variants: { + [key: string]: ConfigSpec; + }; + "display-as"?: string; + "unique-by"?: UniqueBy; +}; export type ValueSpecObject = { - spec : ConfigSpec, - 'display-as'?: string, - "unique-by"?: UniqueBy - -} -export type ValueSpecList = -| Subtype<'boolean', WithDescription, boolean>>> -| Subtype<'string', WithDescription, string>>> -| Subtype<'number', WithDescription, number>>> -| Subtype<'enum', WithDescription>> + spec: ConfigSpec; + "display-as"?: string; + "unique-by"?: UniqueBy; +}; +export type ValueSpecList = + | Subtype<"boolean", WithDescription, boolean>>> + | Subtype<"string", WithDescription, string>>> + | Subtype<"number", WithDescription, number>>> + | Subtype< + "enum", + WithDescription< + WithDefault< + { + values: string[]; + "value-names": { + [key: string]: string; + }; + }, + string + > + > + >; export type SetResult = { - signal?: string, - 'depends-on': { - [packageId: string]: string[] - } -} + signal: + | "SIGTERM" + | "SIGHUP" + | "SIGINT" + | "SIGQUIT" + | "SIGILL" + | "SIGTRAP" + | "SIGABRT" + | "SIGBUS" + | "SIGFPE" + | "SIGKILL" + | "SIGUSR1" + | "SIGSEGV" + | "SIGUSR2" + | "SIGPIPE" + | "SIGALRM" + | "SIGSTKFLT" + | "SIGCHLD" + | "SIGCONT" + | "SIGSTOP" + | "SIGTSTP" + | "SIGTTIN" + | "SIGTTOU" + | "SIGURG" + | "SIGXCPU" + | "SIGXFSZ" + | "SIGVTALRM" + | "SIGPROF" + | "SIGWINCH" + | "SIGIO" + | "SIGPWR" + | "SIGSYS" + | "SIGEMT" + | "SIGINFO"; + "depends-on": { + [packageId: string]: string[]; + }; +}; export type PackagePropertiesV2 = { - [name: string]: PackagePropertyObject | PackagePropertyString -} + [name: string]: PackagePropertyObject | PackagePropertyString; +}; export type PackagePropertyString = { - type: 'string', - description?: string, - value: string, - copyable?: boolean, - qr?: boolean, - masked?: boolean, -} + type: "string"; + description?: string; + value: string; + copyable?: boolean; + qr?: boolean; + masked?: boolean; +}; export type PackagePropertyObject = { - value: PackagePropertiesV2; - type: "object"; - description: string; -} + value: PackagePropertiesV2; + type: "object"; + description: string; +}; export type Properties = { - version: 2, - data: PackagePropertiesV2 -} + version: 2; + data: PackagePropertiesV2; +}; + + +export type Dependencies = { + [id: string]: { + check(effects: Effects, input: Config): Promise, + autoConfigure(effects: Effects, input: Config): Promise, + } +} \ No newline at end of file diff --git a/backend/test/js_action_execute/package-data/volumes/test-package/data/bad_file.txt b/backend/test/js_action_execute/package-data/volumes/test-package/data/bad_file.txt new file mode 100644 index 000000000..7bd6da0b6 --- /dev/null +++ b/backend/test/js_action_execute/package-data/volumes/test-package/data/bad_file.txt @@ -0,0 +1 @@ +Out of volumes diff --git a/backend/test/js_action_execute/package-data/volumes/test-package/data/main/hack_back b/backend/test/js_action_execute/package-data/volumes/test-package/data/main/hack_back new file mode 120000 index 000000000..6581736d6 --- /dev/null +++ b/backend/test/js_action_execute/package-data/volumes/test-package/data/main/hack_back @@ -0,0 +1 @@ +../../ \ No newline at end of file diff --git a/deno_core.tar.gz b/deno_core.tar.gz new file mode 100644 index 000000000..ca9297cb4 Binary files /dev/null and b/deno_core.tar.gz differ