aboutsummaryrefslogtreecommitdiffstats
path: root/src/access/registry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/access/registry.rs')
-rw-r--r--src/access/registry.rs72
1 files changed, 62 insertions, 10 deletions
diff --git a/src/access/registry.rs b/src/access/registry.rs
index 618402b..4ee51ff 100644
--- a/src/access/registry.rs
+++ b/src/access/registry.rs
@@ -180,15 +180,48 @@ pub(crate) fn capability(by: &str, since: u64, path: &[String], rights: &[Right]
}
/// The first users of a new swarm, as its genesis node records them (`first_users`),
-/// each with everything (`all_caps`).
-pub(crate) type FirstUsers = Vec<String>;
+/// each with everything (`all_caps`), and with its keys, by name (`User::keys`).
+pub(crate) type FirstUsers = BTreeMap<String, BTreeMap<String, String>>;
-/// The first users of a new swarm, from the genesis node's `--bootstrap-users`, sorted.
-/// None without: then the swarm has no registry, and its users are all alike
-/// (`access.md`, A swarm without a registry).
-pub(crate) fn first_users(bootstrap_users: &[String]) -> FirstUsers {
- let users: std::collections::BTreeSet<&String> = bootstrap_users.iter().collect();
- users.into_iter().cloned().collect()
+/// The first users of a new swarm, from the genesis node's `--bootstrap-users`, with
+/// their keys from `--bootstrap-keys` (`USER=ssh-ed25519 <base64> [comment]`, named by
+/// the comment if it is a name, else `key1`, `key2`…). None without: then the swarm has
+/// no registry, and its users are all alike (`access.md`, A swarm without a registry).
+/// A key of a user not among them, or that isn't one, is refused, saying why.
+pub(crate) fn first_users(
+ bootstrap_users: &[String],
+ bootstrap_keys: &[String],
+) -> Result<FirstUsers, String> {
+ let mut users: FirstUsers = bootstrap_users
+ .iter()
+ .map(|name| (name.clone(), BTreeMap::new()))
+ .collect();
+ for item in bootstrap_keys {
+ let Some((name, line)) = item.split_once('=') else {
+ return Err(format!(
+ "bootstrap-keys: {item:?} isn't USER=ssh-ed25519 <base64> [comment]"
+ ));
+ };
+ let name = name.trim();
+ let keys = users
+ .get_mut(name)
+ .ok_or_else(|| format!("bootstrap-keys: {name:?} isn't among bootstrap-users"))?;
+ let key = super::sshsig::PublicKey::parse(line)
+ .map_err(|why| format!("bootstrap-keys: {name}'s key: {why}"))?;
+ let comment = line
+ .split_whitespace()
+ .nth(2)
+ .filter(|comment| valid_name(comment));
+ let id = match comment {
+ Some(comment) if !keys.contains_key(comment) => comment.to_owned(),
+ _ => (1..)
+ .map(|n| format!("key{n}"))
+ .find(|id| !keys.contains_key(id))
+ .expect("a free name"),
+ };
+ keys.insert(id, key.line());
+ }
+ Ok(users)
}
/// Everything, as a first user has it (`first_users`): the swarm's data and protocol
@@ -281,12 +314,31 @@ mod tests {
#[test]
fn the_first_users() {
let names = |list: &[&str]| list.iter().map(|n| (*n).to_owned()).collect::<Vec<_>>();
+ let users = first_users(&names(&["bob", "alice", "bob"]), &[]).unwrap();
assert_eq!(
- first_users(&names(&["bob", "alice", "bob"])),
+ users.keys().cloned().collect::<Vec<_>>(),
names(&["alice", "bob"])
);
// Without bootstrap users, no registry.
- assert!(first_users(&[]).is_empty());
+ assert!(first_users(&[], &[]).unwrap().is_empty());
+ // Their keys: by the comment if it is a name, else numbered.
+ let key = super::super::sshsig::PublicKey([4; 32]).line();
+ let keyed = first_users(
+ &names(&["alice"]),
+ &[
+ format!("alice={key} laptop"),
+ format!("alice={key}"),
+ format!("alice={key} not/a/name"),
+ ],
+ )
+ .unwrap();
+ assert_eq!(
+ keyed["alice"].keys().cloned().collect::<Vec<_>>(),
+ names(&["key1", "key2", "laptop"])
+ );
+ assert!(first_users(&names(&["alice"]), &[format!("bob={key}")]).is_err());
+ assert!(first_users(&names(&["alice"]), &["alice=ssh-rsa AAAA".to_owned()]).is_err());
+ assert!(first_users(&names(&["alice"]), &[key]).is_err());
let all = Caps::new(
all_caps()
.into_iter()