Preserve API credentials across config updates

This commit is contained in:
Herbert Wolverson committed 2026-07-17 10:41:10 -05:00
1 parent bfc1d3f2d9
commit dd5ed5d15b
4 files changed
+81 -16

No files matched your search

+8
View File
@@ -647,6 +647,11 @@ pub enum BusRequest {
/// Retrieve current Insight license summary (licensed + optional max circuits).
GetInsightLicenseSummary,
/// Update configuration while preserving authoritative API credentials.
///
/// This variant is appended to preserve existing bincode discriminants.
UpdateLqosdConfigPreserveApiCredentials(Box<lqos_config::Config>),
}
impl BusRequest {
@@ -756,6 +761,9 @@ impl BusRequest {
Self::GetGlobalWarnings => "GetGlobalWarnings",
Self::GetLtsCapabilities => "GetLtsCapabilities",
Self::GetInsightLicenseSummary => "GetInsightLicenseSummary",
Self::UpdateLqosdConfigPreserveApiCredentials(_) => {
"UpdateLqosdConfigPreserveApiCredentials"
}
}
}
@@ -80,6 +80,12 @@ async fn handle_requests_with_deadline_for_duration(
) -> BusReply {
let request_count = requests.len();
let request_kinds = requests.iter().map(BusRequest::kind).collect::<Vec<_>>();
debug!(
source = request_source,
request_count,
request_kinds = %request_kind_summary(&request_kinds),
"Handling bus request"
);
let can_fail_fast = requests.iter().all(BusRequest::can_fail_fast_on_timeout);
let start = Instant::now();
let mut handler = spawn_blocking(move || {
@@ -331,8 +337,6 @@ impl UnixSocketServer {
warn!("Invalid data on local socket");
break;
};
debug!("Received request: {:?}", request);
// Handle the request and build the response
let response = handle_requests_with_deadline(
handle_bus_requests,
+33 -12
View File
@@ -509,6 +509,21 @@ fn memory_debug() {
#[cfg(not(feature = "flamegraphs"))]
fn memory_debug() {}
fn update_lqosd_config_from_bus(config: &lqos_config::Config) -> BusResponse {
match lqos_config::update_config(config) {
Ok(()) => {
if let Ok(cfg) = lqos_config::load_config() {
let _ = stick::recompute_stick_offset(&cfg);
}
BusResponse::Ack
}
Err(err) => {
error!("Error updating config: {err:?}");
BusResponse::Fail(err.to_string())
}
}
}
fn handle_bus_requests(requests: &[BusRequest], responses: &mut Vec<BusResponse>) {
for req in requests.iter() {
//println!("Request: {:?}", req);
@@ -612,18 +627,7 @@ fn handle_bus_requests(requests: &[BusRequest], responses: &mut Vec<BusResponse>
lqos_bus::BusResponse::Ack
}
BusRequest::UpdateLqosDTuning(..) => tuning::tune_lqosd_from_bus(req),
BusRequest::UpdateLqosdConfig(config) => match lqos_config::update_config(config) {
Ok(()) => {
if let Ok(cfg) = lqos_config::load_config() {
let _ = stick::recompute_stick_offset(&cfg);
}
BusResponse::Ack
}
Err(err) => {
error!("Error updating config: {err:?}");
BusResponse::Fail(err.to_string())
}
},
BusRequest::UpdateLqosdConfig(config) => update_lqosd_config_from_bus(config),
BusRequest::CreateDynamicCircuit { shaped_device } => {
crate::dynamic_circuits::create_dynamic_circuit((**shaped_device).clone())
}
@@ -1198,6 +1202,23 @@ fn handle_bus_requests(requests: &[BusRequest], responses: &mut Vec<BusResponse>
max_circuits,
})
}
BusRequest::UpdateLqosdConfigPreserveApiCredentials(config) => {
let _guard =
node_manager::local_api::local_api_keys::lock_config_update_blocking();
match lqos_config::load_config() {
Ok(current) => {
let config = node_manager::local_api::local_api_keys::preserve_api_credentials(
(**config).clone(),
&current,
);
update_lqosd_config_from_bus(&config)
}
Err(error) => {
error!("Unable to load config while preserving local API keys: {error:?}");
BusResponse::Fail(error.to_string())
}
}
}
});
}
}
@@ -153,6 +153,14 @@ pub(crate) fn lock_config_update_blocking() -> MutexGuard<'static, ()> {
CONFIG_UPDATE_LOCK.blocking_lock()
}
/// Replaces a proposed configuration's API credentials with the current
/// authoritative local API section and Insight/API license key.
pub(crate) fn preserve_api_credentials(mut proposed: Config, current: &Config) -> Config {
proposed.local_api = current.local_api.clone();
proposed.long_term_stats.license_key = current.long_term_stats.license_key.clone();
proposed
}
/// Persists an updated configuration through `lqosd`'s local bus.
pub(super) async fn persist_config(config: Config) -> Result<(), String> {
let mut responses = bus_request_with_timeout(
@@ -243,8 +251,8 @@ pub async fn remove_legacy(login: LoginResult) -> Result<(), String> {
#[cfg(test)]
mod tests {
use super::{
append_key, build_key, create, remove_legacy, remove_legacy_from_config, revoke,
revoke_from_config,
append_key, build_key, create, preserve_api_credentials, remove_legacy,
remove_legacy_from_config, revoke, revoke_from_config,
};
use crate::node_manager::auth::LoginResult;
use lqos_config::{Config, MAX_LOCAL_API_KEYS};
@@ -349,4 +357,28 @@ mod tests {
Err("Unauthorized".into())
);
}
#[test]
fn generic_config_updates_preserve_authoritative_api_credentials() {
let mut current = Config::default();
current.local_api.bearer_token = Some("legacy".to_string());
let creation = append_key(&mut current, "Monitor", Uuid::from_u128(7), &[7; 32], 7)
.expect("fixture key should be valid");
let mut proposed = Config::default();
proposed.local_api.bearer_token = Some("replacement".to_string());
current.long_term_stats.license_key = Some("license-secret".to_string());
proposed.long_term_stats.license_key = Some("replacement-license".to_string());
let merged = preserve_api_credentials(proposed, &current);
assert_eq!(merged.local_api, current.local_api);
assert_eq!(
merged.long_term_stats.license_key,
current.long_term_stats.license_key
);
assert!(
!toml::to_string(&merged)
.expect("config should serialize")
.contains(&creation.api_key)
);
}
}