summaryrefslogtreecommitdiffstats
path: root/src/config/mod.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-27 00:58:45 +0100
committertv <tv@krebsco.de>2026-10-27 00:58:45 +0100
commitb90cfd1fde4e1c00d03e44976753d0044b273bda (patch)
tree23c401e56b3caf6644c07091563e73827b43e177 /src/config/mod.rs
parentfde8a76ec5d8e697d9b13470cd15ed324bae3d00 (diff)
introduce node keys
Diffstat (limited to 'src/config/mod.rs')
-rw-r--r--src/config/mod.rs67
1 files changed, 55 insertions, 12 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 928730e..65d481d 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -180,6 +180,20 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"File where this node remembers its swarm across restarts: the swarm ID and some members to rejoin through (not the document). Written once ready, then rarely. Without it, a restarted node knows only its configured seeds.",
),
option(
+ "node-key-file",
+ Kind::Text,
+ "PATH",
+ None,
+ "This node's key (Ed25519, 32 raw bytes): who the node is, whatever transport reaches it. Keep it with the node: a new key is a new node. Required.",
+ ),
+ option(
+ "create-node-key",
+ Kind::Flag,
+ "BOOL",
+ Some("false"),
+ "Create a new node key at node-key-file if there is no file there (a new node). Without it, a missing key file is an error.",
+ ),
+ option(
"swarm-key-file",
Kind::Text,
"PATH",
@@ -311,6 +325,9 @@ pub(crate) struct Settings {
pub(crate) remake: Option<String>,
#[serde(default)]
pub(crate) force: Vec<String>,
+ pub(crate) node_key_file: PathBuf,
+ #[serde(default)]
+ pub(crate) create_node_key: bool,
pub(crate) swarm_key_file: Option<PathBuf>,
pub(crate) alias: Option<String>,
#[serde(default = "default_keepalive", with = "crate::units::duration_option")]
@@ -788,6 +805,7 @@ fn culprit(merged: &Layer) -> Option<(&String, &Entry)> {
}
let mut alone = serde_json::Map::new();
alone.insert("htpasswd".to_owned(), JsonValue::String(String::new()));
+ alone.insert("node-key-file".to_owned(), JsonValue::String(String::new()));
alone.insert((*name).clone(), entry.value.clone());
serde_json::from_value::<Settings>(JsonValue::Object(alone)).is_err()
})
@@ -1122,7 +1140,7 @@ mod tests {
written: None,
},
};
- let mut merged = layer(json!({ "htpasswd": "u" }));
+ let mut merged = layer(json!({ "htpasswd": "u", "node-key-file": "node.key" }));
merged.insert(
"force".to_owned(),
from(Provenance::File(PathBuf::from("continuum.toml"))),
@@ -1135,7 +1153,11 @@ mod tests {
#[test]
fn options_and_settings_agree() {
// Every option is a Settings field, and every field an option.
- let loaded = resolve(layer(json!({ "htpasswd": "users" })), Vec::new()).unwrap();
+ let loaded = resolve(
+ layer(json!({ "htpasswd": "users", "node-key-file": "node.key" })),
+ Vec::new(),
+ )
+ .unwrap();
let JsonValue::Object(fields) = serde_json::to_value(&loaded.settings).unwrap() else {
panic!()
};
@@ -1184,6 +1206,12 @@ mod tests {
);
overlay(
&mut merged,
+ "node-key-file".into(),
+ file(json!("node.key")),
+ Overlay::FileOverFile,
+ );
+ overlay(
+ &mut merged,
"mycelium-key-file".into(),
file(json!("k")),
Overlay::FileOverFile,
@@ -1230,7 +1258,9 @@ mod tests {
#[test]
fn report_hides_imported_values() {
- let mut values = layer(json!({ "htpasswd": "users", "alias": "secret-ish" }));
+ let mut values = layer(
+ json!({ "htpasswd": "users", "node-key-file": "node.key", "alias": "secret-ish" }),
+ );
values.get_mut("alias").unwrap().origin.imports = vec!["env:HOSTNAME".into()];
let report = resolve(values, Vec::new())
.unwrap()
@@ -1265,9 +1295,12 @@ mod tests {
/// `Settings::check` and the override rule name the same supervised-only options.
#[test]
fn supervised_only_options_are_the_conflicts() {
- let settings = resolve(layer(json!({ "htpasswd": "u" })), Vec::new())
- .unwrap()
- .settings;
+ let settings = resolve(
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key" })),
+ Vec::new(),
+ )
+ .unwrap()
+ .settings;
let names: Vec<&str> = settings
.supervised_only()
.iter()
@@ -1283,14 +1316,14 @@ mod tests {
assert!(resolve(layer(json!({})), Vec::new()).is_err()); // htpasswd is required
assert!(
resolve(
- layer(json!({ "htpasswd": "u", "neighbours": 1 })),
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "neighbours": 1 })),
Vec::new()
)
.is_err()
);
assert!(
resolve(
- layer(json!({ "htpasswd": "u", "debug-epoch": 3 })),
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "debug-epoch": 3 })),
Vec::new()
)
.is_err()
@@ -1299,6 +1332,7 @@ mod tests {
resolve(
layer(json!({
"htpasswd": "u",
+ "node-key-file": "node.key",
"mycelium-rest-url": "http://[::1]:8989",
"mycelium-key-file": "k",
})),
@@ -1306,10 +1340,16 @@ mod tests {
)
.is_err()
);
- assert!(resolve(layer(json!({ "htpasswd": "u", "alias": " " })), Vec::new()).is_err());
assert!(
resolve(
- layer(json!({ "htpasswd": "u", "keepalive": "x" })),
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "alias": " " })),
+ Vec::new()
+ )
+ .is_err()
+ );
+ assert!(
+ resolve(
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "keepalive": "x" })),
Vec::new()
)
.is_err()
@@ -1352,6 +1392,8 @@ mod tests {
"continuum",
"--htpasswd",
"users",
+ "--node-key-file",
+ "node.key",
"--alias",
"node-${env:CONTINUUM_TEST_SITE}",
"--seeds",
@@ -1397,8 +1439,9 @@ mod tests {
#[test]
fn secrets_are_never_shown() {
- let values =
- layer(json!({ "htpasswd": "users", "mycelium-rest-url": "http://u:pw@h:8989" }));
+ let values = layer(
+ json!({ "htpasswd": "users", "node-key-file": "node.key", "mycelium-rest-url": "http://u:pw@h:8989" }),
+ );
assert_eq!(
values["mycelium-rest-url"].shown("mycelium-rest-url"),
"<secret>"