diff options
Diffstat (limited to 'src/simtest')
| -rw-r--r-- | src/simtest/mod.rs | 2 | ||||
| -rw-r--r-- | src/simtest/policy.rs | 113 |
2 files changed, 115 insertions, 0 deletions
diff --git a/src/simtest/mod.rs b/src/simtest/mod.rs index 0ebb0ae..67ddaff 100644 --- a/src/simtest/mod.rs +++ b/src/simtest/mod.rs @@ -222,6 +222,8 @@ impl Sim { session_lifetime: Duration::from_secs(86400), challenge_lifetime: Duration::from_secs(300), key_logins_per_user: 100, + strict_keys: Vec::new(), + follow_strict_remakes: false, cors_origins: Vec::new(), files: Arc::new(Files::default()), state_file: None, diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs index 7f32695..0d590d7 100644 --- a/src/simtest/policy.rs +++ b/src/simtest/policy.rs @@ -899,3 +899,116 @@ async fn a_write_every_node_would_refuse() { assert!(!holds(&a, "secrets/x").await); assert_eq!(refused(&a).await, 0); } + +/// A swarm turning strict through a remake: what a member planted in the registry before +/// (an entry written plain, as a user who may) is gone unless the operator's manifest +/// keeps it; the policy is strict after. A node without the operator's key doesn't vote +/// for it, and follows once the others commit it. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn a_swarm_turning_strict_through_a_remake() { + use crate::access::{sshsig, strict}; + let key = ed25519_dalek::SigningKey::from_bytes(&[12; 32]); + let public = sshsig::PublicKey(key.verifying_key().to_bytes()); + let line = public.line(); + let sim = Sim::with_identity(92); + let with_key = move |config: &mut crate::node::NodeConfig| config.strict_keys = vec![public]; + let a = sim.start_with("a", &[], Start::Genesis, { + move |config| { + config.first_users = [( + "alice".to_owned(), + [("laptop".to_owned(), line.clone())].into(), + )] + .into(); + with_key(config); + } + }); + let b = sim.start_with("b", &["a"], Start::Join, with_key); + 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/policy", + json!({"version": 1, "signatures": "required"}), + ) + .await; + // c plants mallory, as alice, who may: taken, the policy not being strict. + let mallory = crate::access::registry::entry("alice", 1, &crate::access::registry::all_caps()); + write_as(&c, "alice", "_continuum/users/mallory", mallory).await; + sim.wait_converged(&[&a, &b, &c], SOON).await; + assert!(holds(&a, "_continuum/users/mallory").await); + + // The operator's manifest, made on a, keeping everything but mallory. + let (text, heads) = { + let replica = a.doc.read().await; + let policy = json!({"version": 1, "signatures": "required", "values": "signed"}); + let swarm = store::read_path(&replica.doc, &path("_continuum/swarm")) + .unwrap() + .unwrap(); + let made = strict::manifest_of(&replica.doc, swarm.as_str().unwrap(), policy, "alice", 1); + let mut manifest: strict::Manifest = serde_json::from_str(&made).unwrap(); + manifest + .keep + .retain(|kept| kept.path != path("_continuum/users/mallory")); + let line = serde_json::to_string(&manifest).unwrap(); + let signed = sshsig::sign(&key, strict::NAMESPACE, line.as_bytes()); + let heads: Vec<String> = replica + .doc + .get_heads() + .iter() + .map(ToString::to_string) + .collect(); + (format!("{line}\n{signed}"), heads) + }; + let (status, report) = crate::remake::testing::start( + a.clone(), + json!({"kind": "strict", "spec": {"manifest": text}, "baseHeads": heads}), + None, + ) + .await; + assert!(status.is_success(), "proposal refused: {status} {report}"); + sim.wait_until( + "every node is on generation 1", + Duration::from_secs(60), + || { + let nodes = nodes.clone(); + async move { + for node in &nodes { + if node.local_epoch().generation != 1 || !node.is_ready().await { + return false; + } + } + true + } + }, + ) + .await; + for node in [&a, &b, &c] { + let replica = node.doc.read().await; + assert!( + store::read_path(&replica.doc, &path("_continuum/users/mallory")) + .unwrap() + .is_none(), + "mallory planted, kept" + ); + assert!( + store::read_path(&replica.doc, &path("_continuum/users/alice")) + .unwrap() + .is_some() + ); + assert!(crate::access::policy::of(&replica.doc).strict()); + } +} |
