diff options
| author | tv <tv@krebsco.de> | 2026-11-07 22:35:09 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-07 22:35:09 +0100 |
| commit | eaf25a3638568f580098bd56be76d80dc514e736 (patch) | |
| tree | 7abec8f4c6b2a0c11710445ce0870dbbf4865a6c | |
| parent | f2487f5a1f897bb5499ffd782562f53967f20606 (diff) | |
access: genesis of swarms from before the registry
Swarms started before the registry keep working without one; the
genesis node's options for a registry apply only to new swarms. What
tests and migration leave is noted in future-work.
Auth plan M1, bootstrap and migration (2.5).
| -rw-r--r-- | docs/access.md | 13 | ||||
| -rw-r--r-- | docs/example.toml | 11 | ||||
| -rw-r--r-- | src/access/registry.rs | 53 | ||||
| -rw-r--r-- | src/config/mod.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 7 |
5 files changed, 34 insertions, 54 deletions
diff --git a/docs/access.md b/docs/access.md index b9e0296..cd11723 100644 --- a/docs/access.md +++ b/docs/access.md @@ -32,14 +32,11 @@ password hashes: every member reads the document). ## A new swarm -Its genesis node records the first users, with the swarm ID, in its first change: - -* those of `--bootstrap-users`, each with everything (`[]` and `["_continuum"]`); -* unset: the users of its `--htpasswd` with `[]`, and those of them, or of the - proxy's, in `--protocol-writers` with `["_continuum"]` too. - -A swarm nobody could manage doesn't start: with none of them with `["_continuum"]`, -`--genesis` is refused, naming both options. +A swarm gets a registry from its start if its genesis node is given its first users, +`--bootstrap-users`: it records them, with the swarm ID, in its first change, each with +everything (`[]` and `["_continuum"]`), and the other users of its `--htpasswd` with +`[]`. Without it, the swarm has no registry, and is as swarms were before: users all +alike (An existing swarm, below), until someone gives it one. ## At the HTTP API diff --git a/docs/example.toml b/docs/example.toml index da808ec..e48bd2c 100644 --- a/docs/example.toml +++ b/docs/example.toml @@ -83,14 +83,13 @@ htpasswd = "/etc/continuum/htpasswd" # (remake records, members, forget records), e.g. to repair a stuck remake, in a swarm # without a user registry. Such writes act on the whole swarm and are logged. Epoch # metadata and the swarm ID are never writable. With a registry its capabilities decide -# instead (docs/access.md); on a genesis node, those of htpasswd's users named here may -# do everything in the new swarm's registry. +# instead (docs/access.md). # protocol-writers = ["alice"] -# With genesis: the new swarm's first users, each may do everything, protocol data and -# managing users included (docs/access.md). Unset, the users of htpasswd may read and -# write the swarm's data, and those also in protocol-writers everything. A swarm nobody -# could manage doesn't start: set this, or protocol-writers, for one of htpasswd's users. +# With genesis: give the new swarm a user registry (docs/access.md), these its first +# users, each may do everything, protocol data and managing users included; htpasswd's +# other users may read and write the swarm's data. Unset, the swarm has no registry: its +# users are all alike, and only those in protocol-writers write protocol data. # bootstrap-users = ["alice"] # How log lines look: text (timestamped), journal (for systemd's journal: no timestamp or diff --git a/src/access/registry.rs b/src/access/registry.rs index 948e0de..094e62c 100644 --- a/src/access/registry.rs +++ b/src/access/registry.rs @@ -147,34 +147,23 @@ pub(crate) fn capability(by: &str, since: u64, path: &[String], rights: &[Right] pub(crate) type FirstUsers = Vec<(String, bool)>; /// The first users of a new swarm, from the genesis node's options: those of -/// `bootstrap_users`, each may do everything; unset, the users of its htpasswd file -/// (`htpasswd`) may read and write the swarm's data, and those of them, or of the proxy's -/// (`--user-header`), in `protocol_writers` everything. Refused if nobody could manage -/// the swarm: a registry nobody may change would lock everyone out for good. -pub(crate) fn first_users( - bootstrap_users: &[String], - htpasswd: &[String], - protocol_writers: &[String], -) -> anyhow::Result<FirstUsers> { - let mut users: BTreeMap<String, bool> = BTreeMap::new(); +/// `bootstrap_users` may do everything, and the other users of its htpasswd file +/// (`htpasswd`) read and write the swarm's data. None without `bootstrap_users`: then +/// the swarm has no registry, and its users are all alike, as before there were +/// registries (`access.md`). +pub(crate) fn first_users(bootstrap_users: &[String], htpasswd: &[String]) -> FirstUsers { if bootstrap_users.is_empty() { - for name in htpasswd.iter().filter(|name| valid_name(name)) { - users.insert(name.clone(), false); - } - for name in protocol_writers.iter().filter(|name| valid_name(name)) { - users.insert(name.clone(), true); - } - } else { - for name in bootstrap_users { - users.insert(name.clone(), true); - } + return Vec::new(); } - if !users.values().any(|admin| *admin) { - anyhow::bail!( - "genesis: nobody could manage the new swarm's users; name one with bootstrap-users, or put one of htpasswd's users in protocol-writers (docs/access.md)" - ); + let mut users: BTreeMap<String, bool> = htpasswd + .iter() + .filter(|name| valid_name(name)) + .map(|name| (name.clone(), false)) + .collect(); + for name in bootstrap_users { + users.insert(name.clone(), true); } - Ok(users.into_iter().collect()) + users.into_iter().collect() } /// The capabilities of a first user (`first_users`): the swarm's data, and for one who @@ -272,20 +261,16 @@ mod tests { fn the_first_users() { let names = |list: &[&str]| list.iter().map(|n| (*n).to_owned()).collect::<Vec<_>>(); assert_eq!( - first_users(&[], &names(&["alice", "bob"]), &names(&["alice"])).unwrap(), + first_users(&names(&["alice"]), &names(&["alice", "bob"])), vec![("alice".to_owned(), true), ("bob".to_owned(), false)] ); - // A protocol writer the proxy names, not in htpasswd. + // One the proxy names, not in htpasswd. assert_eq!( - first_users(&[], &names(&["bob"]), &names(&["carol"])).unwrap(), + first_users(&names(&["carol"]), &names(&["bob"])), vec![("bob".to_owned(), false), ("carol".to_owned(), true)] ); - assert_eq!( - first_users(&names(&["root"]), &names(&["alice"]), &names(&["alice"])).unwrap(), - vec![("root".to_owned(), true)] - ); - assert!(first_users(&[], &names(&["alice"]), &[]).is_err()); - assert!(first_users(&[], &[], &[]).is_err()); + // Without bootstrap users, no registry. + assert!(first_users(&[], &names(&["alice"])).is_empty()); let admin = Caps::new( first_caps(true) .into_iter() diff --git a/src/config/mod.rs b/src/config/mod.rs index ac6d1a3..320d2a8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -121,14 +121,14 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::List, "USER,[USER...]", None, - "HTTP API users (from htpasswd or user-header) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake, in a swarm without a user registry. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable. With a registry its capabilities decide instead (docs/access.md); on a genesis node, those of htpasswd's users named here may do everything in the new swarm's registry.", + "HTTP API users (from htpasswd or user-header) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake, in a swarm without a user registry. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable. With a registry its capabilities decide instead (docs/access.md).", ), option( "bootstrap-users", Kind::List, "USER,[USER...]", None, - "With genesis: the new swarm's first users, each may do everything, protocol data and managing users included (docs/access.md). Unset, the users of htpasswd may read and write the swarm's data, and those also in protocol-writers everything. A swarm nobody could manage doesn't start: set this, or protocol-writers, for one of htpasswd's users.", + "With genesis: give the new swarm a user registry (docs/access.md), these its first users, each may do everything, protocol data and managing users included; htpasswd's other users may read and write the swarm's data. Unset, the swarm has no registry: its users are all alike, and only those in protocol-writers write protocol data.", ), option( "log-format", diff --git a/src/main.rs b/src/main.rs index b3eb438..2b8c376 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,14 +97,13 @@ async fn main() -> Result<()> { // A broken htpasswd will abort startup. let auth_state = AuthState::from_settings(&args)?.context("htpasswd or user-header is required")?; - // A new swarm's first users, before the minutes of startup too: one nobody could - // manage doesn't start. - let first_users = if args.genesis { + // A new swarm's first users, if it is to have a user registry. + let first_users = if args.genesis && !args.bootstrap_users.is_empty() { let htpasswd = match args.htpasswd.as_deref() { Some(path) => api::auth::htpasswd_names(path)?, None => Vec::new(), }; - access::registry::first_users(&args.bootstrap_users, &htpasswd, &args.protocol_writers)? + access::registry::first_users(&args.bootstrap_users, &htpasswd) } else { Vec::new() }; |
