Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions crates/cli/tests/coverage/plugins_lifecycle_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,103 @@ symbol = "nemo_relay_fixture_native_plugin"
manifest_path
}

fn materialize_native_example_manifest(dir: &Path) -> (PathBuf, PathBuf) {
let artifact_name = format!(
"{}nemo_relay_rust_native_plugin_example{}",
std::env::consts::DLL_PREFIX,
std::env::consts::DLL_SUFFIX
);
let artifact_relative = Path::new("target").join("debug").join(&artifact_name);
let artifact_path = dir.join(&artifact_relative);
std::fs::create_dir_all(artifact_path.parent().unwrap()).unwrap();
let artifact_body = b"native plugin example fixture";
std::fs::write(&artifact_path, artifact_body).unwrap();
let digest = Sha256::digest(artifact_body)
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>();

let repository_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let template = std::fs::read_to_string(
repository_root.join("examples/rust-native-plugin/relay-plugin.toml"),
)
.unwrap();
let manifest = template
.replace("<platform-library-file>", &artifact_name)
.replace("<artifact-sha256>", &digest);
let manifest_path = dir.join("relay-plugin.toml");
std::fs::write(&manifest_path, manifest).unwrap();
(manifest_path, artifact_path)
}

#[test]
fn tracked_native_plugin_example_satisfies_default_trust_policy() {
let temp = tempfile::tempdir().unwrap();
let _env = EnvScope::hermetic(&temp);
let _cwd = CurrentDirGuard::enter(temp.path());
let plugin_dir = temp.path().join("plugins").join("native-example");
std::fs::create_dir_all(&plugin_dir).unwrap();
materialize_native_example_manifest(&plugin_dir);

add(
PluginsAddCommand {
scope: PluginsScopeArgs {
project: true,
..PluginsScopeArgs::default()
},
path: plugin_dir,
},
&ServerArgs::default(),
)
.unwrap();

let resolved = resolve_plugins_config(None).unwrap();
assert_eq!(resolved.dynamic_plugins.len(), 1);
assert_eq!(
resolved.dynamic_plugins[0].plugin_id,
"examples.rust_native_policy"
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[test]
fn tracked_native_plugin_example_rejects_tampered_artifact() {
let temp = tempfile::tempdir().unwrap();
let _env = EnvScope::hermetic(&temp);
let _cwd = CurrentDirGuard::enter(temp.path());
let plugin_dir = temp.path().join("plugins").join("native-example");
std::fs::create_dir_all(&plugin_dir).unwrap();
let (_, artifact_path) = materialize_native_example_manifest(&plugin_dir);
std::fs::write(artifact_path, b"tampered native plugin example fixture").unwrap();

let error = add(
PluginsAddCommand {
scope: PluginsScopeArgs {
project: true,
..PluginsScopeArgs::default()
},
path: plugin_dir,
},
&ServerArgs::default(),
)
.unwrap_err();

match error {
CliError::PluginLifecycle {
kind: PluginLifecycleFailureKind::Refused,
code: Some("integrity_failed"),
message,
..
} => assert!(message.contains("failed integrity verification")),
other => panic!("unexpected integrity add error: {other}"),
}
assert!(
resolve_plugins_config(None)
.unwrap()
.dynamic_plugins
.is_empty()
);
}

#[test]
fn add_registers_dynamic_plugin_in_project_plugins_toml() {
let temp = tempfile::tempdir().unwrap();
Expand Down
28 changes: 21 additions & 7 deletions examples/rust-native-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,44 @@ Run this command from the example directory:
cargo build
```

Before you register the plugin, replace `<platform-library-file>` in
`relay-plugin.toml` with the file name that `cargo build` creates for your
platform:
Before you register the plugin, copy `relay-plugin.toml` to a local manifest and
replace both occurrences of `<platform-library-file>` with the file name that
`cargo build` creates for your platform:

| Platform | Library path |
|---|---|
| macOS | `target/debug/libnemo_relay_rust_native_plugin_example.dylib` |
| Linux | `target/debug/libnemo_relay_rust_native_plugin_example.so` |
| Windows | `target/debug/nemo_relay_rust_native_plugin_example.dll` |

The copied manifest must use the same relative path for `source.artifact` and
`load.library`. Calculate the library's SHA-256 digest and replace
`<artifact-sha256>` with the lowercase hexadecimal value:

| Platform | Digest command |
|---|---|
| macOS | `shasum -a 256 <library-path>` |
| Linux | `sha256sum <library-path>` |
| Windows PowerShell | `(Get-FileHash <library-path> -Algorithm SHA256).Hash.ToLower()` |

Keep the `sha256:` prefix in the manifest. For example, a digest of `abc123`
is written as `sha256:abc123`.

## Register With Relay

After updating `load.library`, run these commands from the repository root:
After materializing the library path and digest, run these commands from the
repository root using the copied manifest path:

```bash
nemo-relay plugins add ./examples/rust-native-plugin/relay-plugin.toml
nemo-relay plugins add ./examples/rust-native-plugin/relay-plugin.local.toml
nemo-relay plugins enable examples.rust_native_policy
```

You can also reference the manifest manually from `plugins.toml`:

```toml
[[plugins.dynamic]]
manifest = "./examples/rust-native-plugin/relay-plugin.toml"
manifest = "./examples/rust-native-plugin/relay-plugin.local.toml"

[plugins.dynamic.config]
tag = "demo"
Expand All @@ -57,7 +71,7 @@ emit_isolated_scope = true
Start the gateway normally after the dynamic record is enabled:

```bash
nemo-relay gateway
nemo-relay --bind 127.0.0.1:4040
```

## What the Example Registers
Expand Down
8 changes: 8 additions & 0 deletions examples/rust-native-plugin/relay-plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ enabled = false
[capabilities]
items = ["plugin_native"]

[source]
# Keep this path aligned with `load.library` when materializing the manifest.
artifact = "target/debug/<platform-library-file>"

[integrity]
# Replace this placeholder with the SHA-256 digest of `source.artifact`.
sha256 = "sha256:<artifact-sha256>"

[load]
# Replace `<platform-library-file>` with the file built by `cargo build` for
# your platform. Refer to README.md for the expected debug artifact names.
Expand Down
Loading