diff --git a/scripts/factory-edge-demo.md b/scripts/factory-edge-demo.md new file mode 100644 index 0000000000..e78256988c --- /dev/null +++ b/scripts/factory-edge-demo.md @@ -0,0 +1,68 @@ +# The factory-edge demo + +Three small programs run side by side, each allowed to reach only its own +destination. Two do inspection work. The third, the maintenance program, asks +for a destination it was not given. The host refuses and records that, and the +other two carry on. + +This is the demo described in "Proposed factory-edge presentation" in +`specs/contracts/host-interface-distributed-execution.md`. Everything is +synthetic traffic. + +## Run it + +```sh +scripts/factory-edge-demo.sh +``` + +About 15 seconds. It starts the three programs and prints: + +1. All three are running. +2. What each is allowed to reach. +3. What the host refused for the maintenance program: `DNS lookup of example.com`, + not in its allow-list. +4. That the two inspection programs have nothing refused. + +Every `mvmctl explain` ends with `audit chain verifies clean`, which is the +evidence line. `scripts/factory-edge-demo.sh --down` removes the programs again. + +## The same thing from the library + +The Studio UI does these same four things through the `mvm-client` library, not +the command line: + +| Demo step | Command line | Library (`mvm-client`) | +| --- | --- | --- | +| Start a program with one allowed destination | `mvmctl run -d --name N --allow-host H:443 -- CMD` | `LaunchRequest::builder(LifecycleMode::Persistent, image).name(N).command(CMD).allow_egress(H, 443).build()?`, then `LocalBackend::new().launch(request)` | +| List them | `mvmctl machine ls` | `inventory::list_local_inventory` | +| What a program is allowed | `mvmctl machine inspect N` | not available: `inspect_machine` returns status, backend, memory and ports but not the allowed destinations ([#5521](https://github.com/tinylabscom/mvm/issues/5521)); the UI can show what it asked for at launch | +| What the host refused, and the audit result | `mvmctl explain N` | `explain::explain_local_run("local", N)`, which returns the events, `egress_denials` and `chain_verified` | + +This table comes from reading the library's code and has not been run, so check +it against the library before building on it. Use a new name for each run, +because `explain` by name is ambiguous when a name has been used before. + +## Set up a new Mac, once + +From a checkout of this repository: + +```sh +MVM_VERSION=v0.28.0-rc.2 MVM_SKIP_BOOTSTRAP=1 sh install.sh +export MVM_HOME="$HOME/.mvm-demo" +mvmctl image pull curlimages/curl:8.21.0 +mvmctl run --image curlimages/curl:8.21.0 -- true +``` + +The last command builds the runtime the first time, which takes a few minutes. +It needs an Apple Silicon Mac on macOS 26 or later, and working internet and +DNS, because each allowed destination is looked up when its program starts. + +## Known limits + +- The three programs are started one after another. Starting them together can + fail with `another local volume lifecycle operation is already in progress`. +- A launch takes about 4 seconds on this release, so the demo takes about 15. + The cause and the fix are written up in the factory-edge section of the + contract document. +- Not shown: Studio's own view, the real maintenance and inspection tasks, and + stopping and replacing a program. diff --git a/scripts/factory-edge-demo.sh b/scripts/factory-edge-demo.sh new file mode 100755 index 0000000000..ce613eb27c --- /dev/null +++ b/scripts/factory-edge-demo.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# The factory-edge demo, raw. +# +# Three small workloads run side by side. Two do inspection work and each may +# reach only its own destination. The third, the maintenance workload, asks for a +# destination it was not given. The host refuses that and records it, `mvmctl +# explain` shows it, and the other two carry on. +# +# scripts/factory-edge-demo.sh start the three workloads and show the result +# scripts/factory-edge-demo.sh --down remove them again +# +# It assumes an installed mvmctl and a prepared home (see factory-edge-demo.md). +# MVMCTL, MVM_HOME and IMAGE can be overridden. +set -euo pipefail + +mvm="${MVMCTL:-mvmctl}" +image="${IMAGE:-curlimages/curl:8.21.0}" +export MVM_HOME="${MVM_HOME:-$HOME/.mvm-demo}" MVM_RESIDENCY=cold + +if [ "${1:-}" = --down ]; then + for m in $($mvm machine ls | awk '$1 ~ /^(line-a|line-b|maintenance)-/ { print $1 }'); do + $mvm machine stop "$m" --yes >/dev/null 2>&1 || true + $mvm machine rm "$m" --yes >/dev/null 2>&1 || true + done + echo "removed"; exit 0 +fi + +id="$(date +%H%M%S)" # names are unique per run, so `explain ` is never ambiguous +a="line-a-$id"; b="line-b-$id"; m="maintenance-$id" + +# One workload: a name, the one destination it is allowed, and what it does. +launch() { $mvm run -d --image "$image" --name "$1" --allow-host "$2" -- sh -c "$3" >/dev/null; } +work() { echo "while true; do curl -sS -m 10 -o /dev/null https://$1/; sleep 2; done"; } + +echo "Starting three workloads, each allowed one destination" +launch "$a" pypi.org:443 "$(work pypi.org)" +launch "$b" files.pythonhosted.org:443 "$(work files.pythonhosted.org)" +launch "$m" api.github.com:443 "while true; do curl -sS -m 10 https://example.com/ >/dev/null 2>&1; sleep 3; done" + +echo; echo "== 1. All three are running" +$mvm machine ls | grep -E "^NAME|-$id " + +echo; echo "== 2. What each is allowed to reach" +for n in "$a" "$b" "$m"; do $mvm machine inspect "$n" | grep -E "^(name|allow-host):"; done + +echo; echo "== 3. The maintenance workload asks for example.com, which it was not given" +for _ in $(seq 1 30); do + $mvm explain "$m" 2>/dev/null | grep -q "egress denied" && break + sleep 1 +done +$mvm explain "$m" | sed -n '/^outcome:/,$p' + +echo; echo "== 4. The inspection workloads are unaffected" +for n in "$a" "$b"; do + echo "-- $n"; $mvm explain "$n" | sed -n '/^outcome:/,$p' +done