fix(core): prevent duplicate management mapping during startup
- Preserve hashed management keys written by the v8 core - Avoid rewriting hashed keys with the GUI plaintext value - Prevent duplicate top-level management mappings Fixes #351
This commit is contained in:
1 parent
1d64a4535f
commit
317c6df98f
2 files changed
+61
-6
No files matched your search
@@ -621,12 +621,30 @@ pub(crate) fn apply_gui_managed_settings(
|
||||
&["observability", "logs", "request-log"],
|
||||
serde_norway::Value::Bool(config.request_log),
|
||||
)?;
|
||||
changed |= set_core_yaml_schema_value(
|
||||
document,
|
||||
&["remote-management", "secret-key"],
|
||||
&["management", "secret-key"],
|
||||
serde_norway::Value::String(config.management_secret_key.clone()),
|
||||
)?;
|
||||
// v8 kernels persist this credential as a bcrypt/argon2 hash after a
|
||||
// successful start. It is already the canonical value the kernel
|
||||
// expects; replacing it with the GUI plaintext needlessly enters the
|
||||
// format-preserving YAML editor and can produce a duplicate top-level
|
||||
// `management` mapping for some valid config layouts.
|
||||
let stored_management_key = document
|
||||
.as_mapping()
|
||||
.and_then(|root| nested_yaml_value(root, &["management", "secret-key"]))
|
||||
.or_else(|| {
|
||||
document.as_mapping().and_then(|root| {
|
||||
nested_yaml_value(root, &["remote-management", "secret-key"])
|
||||
})
|
||||
});
|
||||
if !stored_management_key
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
.is_some_and(is_hashed_management_secret_key)
|
||||
{
|
||||
changed |= set_core_yaml_schema_value(
|
||||
document,
|
||||
&["remote-management", "secret-key"],
|
||||
&["management", "secret-key"],
|
||||
serde_norway::Value::String(config.management_secret_key.clone()),
|
||||
)?;
|
||||
}
|
||||
changed |= set_core_yaml_nested_value(
|
||||
document,
|
||||
"plugins",
|
||||
|
||||
@@ -1556,6 +1556,43 @@ fn v8_core_config_reads_and_writes_canonical_nested_fields() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_inline_management_hash_is_preserved_without_duplicate_section() {
|
||||
let input = "config-version: 8\nserver: {host: 127.0.0.1, port: 8317}\nmanagement: {secret-key: $2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu}\naccess: {api-keys: [client-key]}\nrequests: {proxy-url: direct}\n";
|
||||
let mut config = GuiConfigFile::default();
|
||||
config.management_secret_key = "plaintext-management-key".into();
|
||||
let updated = apply_gui_managed_settings(input, &config)
|
||||
.unwrap_or_else(|error| panic!("management key update failed: {error}"));
|
||||
let parsed = serde_norway::from_str::<serde_norway::Value>(&updated).unwrap();
|
||||
assert_eq!(parsed["management"]["secret-key"], "$2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_multiline_management_hash_with_comments_is_preserved() {
|
||||
let input = "# generated by CLIProxyAPI\n# keep this comment\nconfig-version: 8\nserver:\n host: 127.0.0.1\n port: 8317\nmanagement:\n secret-key: $2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\n allow-remote: false\naccess:\n api-keys:\n - client-key\nrequests:\n proxy-url: direct\n";
|
||||
let mut config = GuiConfigFile::default();
|
||||
config.management_secret_key = "plaintext-management-key".into();
|
||||
let updated = apply_gui_managed_settings(input, &config)
|
||||
.unwrap_or_else(|error| panic!("management key update failed: {error}"));
|
||||
let parsed = serde_norway::from_str::<serde_norway::Value>(&updated).unwrap();
|
||||
assert_eq!(parsed["management"]["secret-key"], "$2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu");
|
||||
assert_eq!(parsed["management"]["allow-remote"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gui_start_preserves_hashed_v8_management_key() {
|
||||
let hash = "$2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu";
|
||||
let input = format!(
|
||||
"config-version: 8\nserver: {{host: 127.0.0.1, port: 8317}}\nmanagement: {{secret-key: {hash}}}\naccess: {{api-keys: [client-key]}}\nrequests: {{proxy-url: direct}}\n"
|
||||
);
|
||||
let mut config = GuiConfigFile::default();
|
||||
config.management_secret_key = "plaintext-management-key".into();
|
||||
let updated = apply_gui_managed_settings(&input, &config).unwrap();
|
||||
let parsed = serde_norway::from_str::<serde_norway::Value>(&updated).unwrap();
|
||||
assert_eq!(parsed["management"]["secret-key"], hash);
|
||||
assert!(!updated.contains("plaintext-management-key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_omitted_fields_use_runtime_defaults_instead_of_gui_presets() {
|
||||
let settings = core_config_settings_from_value(
|
||||
|
||||
Reference in new issue
Block a user