aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:09 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:09 +0100
commiteaf25a3638568f580098bd56be76d80dc514e736 (patch)
tree7abec8f4c6b2a0c11710445ce0870dbbf4865a6c
parentf2487f5a1f897bb5499ffd782562f53967f20606 (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.md13
-rw-r--r--docs/example.toml11
-rw-r--r--src/access/registry.rs53
-rw-r--r--src/config/mod.rs4
-rw-r--r--src/main.rs7
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()
};