Remedial measures to enhance the detection of blank key situations

This commit is contained in:
lzt404 committed 2026-10-06 13:41:35 +08:00
1 parent b48128a81a
commit 8f23fac80b
6 files changed
+92 -46

No files matched your search

+25 -11
View File
@@ -108,28 +108,35 @@ pub(crate) fn validate_strong_management_secret_key(secret_key: &str) -> Result<
Ok(())
}
pub(crate) fn generate_management_secret_key() -> Result<String, String> {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
let mut random = [0_u8; 32];
getrandom::fill(&mut random).map_err(|error| format!("Failed to generate WebUI security key: {error}"))?;
Ok(format!("wui-Aa9_{}", URL_SAFE_NO_PAD.encode(random)))
}
pub(crate) fn management_secret_requires_rotation(secret_key: &str) -> bool {
let secret_key = secret_key.trim();
// Empty is an intentional disabled Management API, including after restart.
is_hashed_management_secret_key(secret_key)
!secret_key.is_empty() && validate_strong_management_secret_key(secret_key).is_err()
}
pub(crate) fn ensure_strong_management_secret(config: &mut GuiConfigFile) -> Result<bool, String> {
if !management_secret_requires_rotation(&config.management_secret_key) {
return Ok(false);
}
config.management_secret_key = generate_management_secret_key()?;
config.management_secret_key = LEGACY_DEFAULT_MANAGEMENT_SECRET_KEY.to_string();
Ok(true)
}
// An empty template or a hashed kernel value cannot supply a request credential.
// Preserve a saved plaintext key (including an explicitly disabled API) before
// falling back, so the shared GUI state and the next kernel config use one key.
pub(crate) fn recover_management_secret(imported: &str, saved: Option<&str>) -> String {
if validate_strong_management_secret_key(imported).is_ok() {
return imported.trim().to_string();
}
if let Some(saved) = saved {
if saved.is_empty() || validate_strong_management_secret_key(saved).is_ok() {
return saved.trim().to_string();
}
}
LEGACY_DEFAULT_MANAGEMENT_SECRET_KEY.to_string()
}
pub(crate) fn normalize_management_secret_key(secret_key: String) -> Result<String, String> {
let secret_key = secret_key.trim().to_string();
validate_strong_management_secret_key(&secret_key)?;
@@ -1337,6 +1344,8 @@ pub(crate) fn load_or_create_gui_config() -> Result<GuiConfigFile, String> {
)
};
let saved_management_secret = presence.management_secret_key.as_deref();
if gui_config_exists {
if let Ok(content) = fs::read_to_string(&config_path) {
changed |= [
@@ -1523,7 +1532,12 @@ pub(crate) fn load_or_create_gui_config() -> Result<GuiConfigFile, String> {
presence.proxy_override.is_some(),
);
config.prefer_gitcode_downloads = config.download_source == VersionDownloadSource::Gitcode;
let management_secret_rotated = ensure_strong_management_secret(&mut config)?;
let recovered_secret = recover_management_secret(
&config.management_secret_key,
saved_management_secret,
);
let management_secret_rotated = recovered_secret != config.management_secret_key;
config.management_secret_key = recovered_secret;
changed |= management_secret_rotated;
changed |= sanitize_gui_config(&mut config)?;
validate_gui_config(&config)?;
+4 -4
View File
@@ -2397,10 +2397,10 @@ fn main() {
return;
}
let mut config = GuiConfigFile::default();
if let Err(secret_error) = ensure_strong_management_secret(&mut config) {
eprintln!("Failed to initialize WebUI security key: {secret_error}");
return;
}
// If the GUI configuration cannot be read before the kernel starts,
// use the shared recovery key so requests and the generated kernel
// configuration stay synchronized.
config.management_secret_key = LEGACY_DEFAULT_MANAGEMENT_SECRET_KEY.to_string();
if let Err(sanitize_error) = sanitize_gui_config(&mut config) {
eprintln!("Failed to initialize fixed credentials directory: {sanitize_error}");
}
+33 -1
View File
@@ -150,7 +150,7 @@ fn management_secret_rotation_preserves_disabled_and_custom_values() {
..GuiConfigFile::default()
};
assert!(ensure_strong_management_secret(&mut hashed).unwrap());
assert!(hashed.management_secret_key.starts_with("wui-Aa9_"));
assert_eq!(hashed.management_secret_key, "123456");
let mut custom = GuiConfigFile {
management_secret_key: "user-selected-secret".to_string(),
@@ -160,6 +160,38 @@ fn management_secret_rotation_preserves_disabled_and_custom_values() {
assert_eq!(custom.management_secret_key, "user-selected-secret");
}
#[test]
fn management_secret_recovery_synchronizes_kernel_and_request_credentials() {
let hash = "$2a$10$abcdefghijklmnopqrstuuuuuuuuuuuuuuuuuuuuuuuuuuuuu";
for (imported, saved, expected) in [
("", None, "123456"),
(hash, None, "123456"),
(hash, Some(hash), "123456"),
("bad\nkey", None, "123456"),
(hash, Some("known-secret"), "known-secret"),
("", Some("known-secret"), "known-secret"),
("", Some(""), ""),
("new-secret", Some("old-secret"), "new-secret"),
] {
let config = GuiConfigFile {
management_secret_key: recover_management_secret(imported, saved),
..GuiConfigFile::default()
};
assert_eq!(config.management_secret_key, expected);
for (template, section) in [
("remote-management: {secret-key: stale}\n", "remote-management"),
("config-version: 8\nmanagement: {secret-key: stale}\n", "management"),
] {
let updated = apply_gui_managed_settings(template, &config).unwrap();
let document: serde_norway::Value = serde_norway::from_str(&updated).unwrap();
assert_eq!(document[section]["secret-key"], expected);
}
if !expected.is_empty() {
assert_eq!(management_api::management_authorization(&config).unwrap(), format!("Bearer {expected}"));
}
}
}
#[test]
fn management_secret_key_is_preserved_and_written_to_core() {
let mut config = GuiConfigFile {