From c66788cba37bcbd779c4b18d3ae6ae105fddbf12 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 20 Apr 2026 07:15:12 +0200 Subject: [PATCH] feat: end-to-end pipeline attestation example + wasm_optimize loom fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit examples/pipeline_attestation/ exercises the full attestation surface on a single tiny Rust component: rust_wasm_component_bindgen -> wasm_sign -> wasm_attest | wasm_show_chain (JSON) | wasm_verify_chain | | all verified green Verified locally: `bazel build //examples/pipeline_attestation:all_pipeline_outputs` emits a wsc "Transformation chain is valid" marker and a JSON artifact with one attestation entry recording input hash, output hash, tool name/version and timestamp. The example deliberately uses wasm_attest (not meld_fuse or wasm_optimize) because meld_fuse needs a multi-component setup and wasm_optimize has a pending WASI path-resolution issue — loom reads `bazel-out/...` paths via WASI and `--dir=.` does not follow the bazel-out symlinks. Documented in a TODO in wasm/private/wasm_optimize.bzl. While touching that file also dropped the stale `--` subcommand separator that loom 0.3.0 rejects. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/pipeline_attestation/BUILD.bazel | 94 +++++++++++++++++++++ examples/pipeline_attestation/README.md | 53 ++++++++++++ examples/pipeline_attestation/src/lib.rs | 14 +++ examples/pipeline_attestation/wit/greet.wit | 11 +++ wasm/private/wasm_optimize.bzl | 8 +- 5 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 examples/pipeline_attestation/BUILD.bazel create mode 100644 examples/pipeline_attestation/README.md create mode 100644 examples/pipeline_attestation/src/lib.rs create mode 100644 examples/pipeline_attestation/wit/greet.wit diff --git a/examples/pipeline_attestation/BUILD.bazel b/examples/pipeline_attestation/BUILD.bazel new file mode 100644 index 00000000..7813eaac --- /dev/null +++ b/examples/pipeline_attestation/BUILD.bazel @@ -0,0 +1,94 @@ +"""End-to-end PulseEngine attestation pipeline example. + +Demonstrates the full sign + attest + verify_chain + show_chain flow against a +minimal Rust component. This is the minimum viable pipeline that produces a +non-empty transformation chain today — meld_fuse requires multi-component +setups and wasm_optimize (loom) has a pending WASI path-resolution issue +(TODO in //wasm/private:wasm_optimize.bzl), so they are not wired in here. +""" + +load("@rules_wasm_component//rust:defs.bzl", "rust_wasm_component_bindgen") +load( + "@rules_wasm_component//wasm:defs.bzl", + "wasm_attest", + "wasm_keygen", + "wasm_show_chain", + "wasm_sign", + "wasm_verify_chain", +) +load("@rules_wasm_component//wit:defs.bzl", "wit_library") + +package(default_visibility = ["//visibility:public"]) + +# 1. WIT interface for a trivial greeter component. +wit_library( + name = "greet_interfaces", + package_name = "example:greet", + srcs = ["wit/greet.wit"], + world = "greet", +) + +# 2. Build the component from Rust. +rust_wasm_component_bindgen( + name = "greeter_component", + srcs = ["src/lib.rs"], + profiles = ["release"], + validate_wit = False, + wit = ":greet_interfaces", +) + +# 3. Generate signing keys (compact format for wasm_sign). +wasm_keygen( + name = "example_keys", + public_key_name = "example.public", + secret_key_name = "example.secret", +) + +# 4. Sign the component. Signing mutates the module (adds a signature section) +# but does not record a transformation attestation — that's what step 5 +# exists for. +wasm_sign( + name = "greeter_signed", + component = ":greeter_component", + keys = ":example_keys", +) + +# 5. Record a transformation attestation covering the signing step. This +# writes a `wsc-attestation` custom section into the output describing the +# (greeter_component -> greeter_signed) transformation. +wasm_attest( + name = "greeter_attested", + input_component = ":greeter_component", + output_component = ":greeter_signed", + tool_name = "wasmsign2", + tool_version = "0.2.6", + transformation_type = "custom", +) + +# 6. Emit the attestation chain as JSON — useful as a CI artifact for +# compliance or debugging. +wasm_show_chain( + name = "greeter_chain", + as_json = True, + component = ":greeter_attested", +) + +# 7. Verify the chain. Because the module now carries a transformation +# attestation, wsc verify-chain runs the full policy pipeline (empty +# policy here => default rules only) and emits a success marker. +wasm_verify_chain( + name = "greeter_chain_verified", + component = ":greeter_attested", +) + +# Convenience aggregate for `bazel build //examples/pipeline_attestation:all_pipeline_outputs`. +filegroup( + name = "all_pipeline_outputs", + srcs = [ + ":greeter_attested", + ":greeter_chain", + ":greeter_chain_verified", + ":greeter_component", + ":greeter_signed", + ], +) diff --git a/examples/pipeline_attestation/README.md b/examples/pipeline_attestation/README.md new file mode 100644 index 00000000..888f630d --- /dev/null +++ b/examples/pipeline_attestation/README.md @@ -0,0 +1,53 @@ +# Pipeline attestation example + +End-to-end demonstration of the PulseEngine attestation surface: + +``` +rust_wasm_component_bindgen -> wasm_sign -> wasm_attest + | + wasm_show_chain <-----+ + wasm_verify_chain <----+ +``` + +## Targets + +| Target | Rule | Output | +|--------|------|--------| +| `:greeter_component` | `rust_wasm_component_bindgen` | Signed-free WASM component | +| `:example_keys` | `wasm_keygen` | Ed25519 key pair (compact format) | +| `:greeter_signed` | `wasm_sign` | Component with embedded signature | +| `:greeter_attested` | `wasm_attest` | Signed component + transformation attestation | +| `:greeter_chain` | `wasm_show_chain` | Chain as JSON (CI-friendly artifact) | +| `:greeter_chain_verified` | `wasm_verify_chain` | Success marker, fails the build on policy/chain error | +| `:all_pipeline_outputs` | `filegroup` | Convenience aggregate | + +## Build it + +```sh +bazel build //examples/pipeline_attestation:all_pipeline_outputs +``` + +Expected output includes a success message from `wasm_verify_chain`: + +``` +✓ Transformation chain is valid +Transformation stages: 1 +Tools used: wasmsign2 +``` + +And a JSON chain (`greeter_chain.json`) with one attestation entry recording +input hash, output hash, tool name/version, and timestamp. + +## Why `wasm_attest` here rather than `meld_fuse` or `wasm_optimize` + +- **`meld_fuse`** produces meaningful chains but requires two or more + components with compatible component-model interfaces — an end-to-end + fusion demo is more than this example wants to set up. +- **`wasm_optimize` (loom)** has a pending WASI path-resolution issue (see + the `TODO` in `wasm/private/wasm_optimize.bzl`) so it is not wired into + this example yet. + +`wasm_attest` is the attestation escape hatch for arbitrary transformations +and is sufficient to demonstrate the full `sign -> attest -> verify -> show` +surface against a single component. Once `wasm_optimize` and `meld_fuse` +self-attest reliably end-to-end, follow-up examples can chain them. diff --git a/examples/pipeline_attestation/src/lib.rs b/examples/pipeline_attestation/src/lib.rs new file mode 100644 index 00000000..696972d7 --- /dev/null +++ b/examples/pipeline_attestation/src/lib.rs @@ -0,0 +1,14 @@ +// Tiny greeter component used to demonstrate the PulseEngine attestation +// pipeline: build -> sign -> attest -> verify_chain -> show_chain. + +use greeter_component_bindings::exports::example::greet::greeter::Guest; + +struct Component; + +impl Guest for Component { + fn greet(name: String) -> String { + format!("Hello, {name}!") + } +} + +greeter_component_bindings::export!(Component with_types_in greeter_component_bindings); diff --git a/examples/pipeline_attestation/wit/greet.wit b/examples/pipeline_attestation/wit/greet.wit new file mode 100644 index 00000000..aa6d1425 --- /dev/null +++ b/examples/pipeline_attestation/wit/greet.wit @@ -0,0 +1,11 @@ +/// Minimal interface for the pipeline attestation example. +package example:greet@0.1.0; + +interface greeter { + /// Return a short greeting for the given name. + greet: func(name: string) -> string; +} + +world greet { + export greeter; +} diff --git a/wasm/private/wasm_optimize.bzl b/wasm/private/wasm_optimize.bzl index fbdf224d..56df8354 100644 --- a/wasm/private/wasm_optimize.bzl +++ b/wasm/private/wasm_optimize.bzl @@ -19,12 +19,16 @@ def _wasm_optimize_impl(ctx): # Get LOOM WASM component loom_wasm = ctx.file._loom_wasm - # Build command arguments + # Build command arguments. + # Note: loom v0.3.0 does NOT accept the `--` separator between the wasm + # module path and the subcommand when run under `wasmtime run`. + # TODO: loom reads input paths via WASI and `--dir=.` does not resolve + # the bazel-out symlinks; a small wrapper (mirroring wasmsign2_wrapper) + # is needed to compute real paths and register them as WASI mounts. args = ctx.actions.args() args.add("run") args.add("--dir=.") args.add(loom_wasm) - args.add("--") args.add("optimize") args.add(input_wasm) args.add("-o", output_wasm)