aboutsummaryrefslogtreecommitdiffstats
path: root/src/simtest/policy.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:22 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:22 +0100
commite15e6779aa93bceee2156e3f368cafbaf07c15cf (patch)
treecc10d906f223ebd74680eb8b7a87b5a70d9f43e0 /src/simtest/policy.rs
parent395f063d9128b42f76015f0711a69b08eef26d5c (diff)
simtest: sealed values
A value sealed for its declared readers goes everywhere; one sealed for a stranger too, or plain, gets nothing past the others. Auth plan M8.
Diffstat (limited to 'src/simtest/policy.rs')
-rw-r--r--src/simtest/policy.rs82
1 files changed, 81 insertions, 1 deletions
diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs
index 60a9f6b..b6be095 100644
--- a/src/simtest/policy.rs
+++ b/src/simtest/policy.rs
@@ -3,7 +3,8 @@
//! node registers, a write a node lets through for a user the registry doesn't have
//! stays where it was made, and a remake carries the swarm over without refusing
//! anything. A strict one (`"values": "signed"`) takes the values its users sign, and
-//! nothing else there.
+//! nothing else there; one declaring values sealed, only those sealed for their
+//! readers.
use std::time::Duration;
@@ -315,3 +316,82 @@ async fn a_strict_swarm() {
assert_eq!(plain(node, "config").await, Some(json!({"mode": "slow"})));
}
}
+
+/// A swarm declaring values sealed (`access::sealed`), with the policy: a value sealed
+/// for its declared readers goes everywhere; one a node writes plain there, or sealed
+/// for a stranger too, gets nothing past the others.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn sealed_values() {
+ use crate::access::sealed::testing::sealed_for;
+ use crate::access::sshsig::PublicKey;
+ let alice_key = PublicKey([21; 32]);
+ let line = alice_key.line();
+ let sim = Sim::with_identity(74);
+ let a = sim.start_with("a", &[], Start::Genesis, move |config| {
+ config.first_users = [(
+ "alice".to_owned(),
+ [("laptop".to_owned(), line.clone())].into(),
+ )]
+ .into();
+ });
+ let b = sim.start("b", &["a"], Start::Join);
+ let c = sim.start("c", &["a"], Start::Join);
+ sim.wait_ready(&[&a, &b, &c], SOON).await;
+ let nodes = [a.clone(), b.clone(), c.clone()];
+ sim.wait_until("every node is registered", SOON, || {
+ let nodes = nodes.clone();
+ async move {
+ for node in &nodes {
+ if crate::swarm::members::members(&node.doc.read().await.doc).len() < 3 {
+ return false;
+ }
+ }
+ true
+ }
+ })
+ .await;
+ write_as(
+ &a,
+ "alice",
+ "_continuum/access/sealed",
+ json!([{"path": ["secrets", "*"], "readers": ["alice"]}]),
+ )
+ .await;
+ write_as(
+ &a,
+ "alice",
+ "_continuum/access/policy",
+ json!({"version": 1}),
+ )
+ .await;
+ sim.wait_converged(&[&a, &b, &c], SOON).await;
+
+ // Sealed for alice, written on b: everywhere.
+ let sealed = sealed_for(&[alice_key]);
+ write_as(&b, "alice", "secrets/db", json!(sealed.clone())).await;
+ for node in [&a, &b, &c] {
+ let sealed = sealed.clone();
+ sim.wait_until("the sealed value is everywhere", SOON, move || {
+ let sealed = sealed.clone();
+ async move { plain(node, "secrets/db").await == Some(json!(sealed)) }
+ })
+ .await;
+ assert_eq!(refused(node).await, 0, "an honest change was refused");
+ }
+
+ // c writes one sealed for a stranger too: refused by the others, and so is what it
+ // builds on it.
+ let stray = sealed_for(&[alice_key, PublicKey([22; 32])]);
+ write_as(&c, "alice", "secrets/web", json!(stray)).await;
+ write_as(&c, "alice", "secrets/api", json!("hunter2")).await;
+ for node in [&a, &b] {
+ sim.wait_until(
+ "the badly sealed writes are refused",
+ SOON,
+ move || async move { refused(node).await > 0 },
+ )
+ .await;
+ assert!(!holds(node, "secrets/web").await);
+ assert!(!holds(node, "secrets/api").await);
+ }
+}