Skip to main content

kipuka/config/
ha.rs

1//! Explicit opt-in HA routing within each label's authorized CA pool.
2use serde::Deserialize;
3
4#[derive(Debug, Clone, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct HaConfig {
7    #[serde(default)]
8    pub enabled: bool,
9    #[serde(default = "strategy")]
10    pub strategy: crate::ha::FailoverStrategy,
11    #[serde(default = "interval")]
12    pub probe_interval_secs: u64,
13    #[serde(default = "timeout")]
14    pub probe_timeout_secs: u64,
15    #[serde(default = "threshold")]
16    pub failure_threshold: u32,
17    #[serde(default = "cooldown")]
18    pub cooldown_secs: u64,
19    /// Verified HTTPS health endpoints keyed by configured CA identifier.
20    #[serde(default)]
21    pub endpoints: std::collections::BTreeMap<String, String>,
22}
23fn strategy() -> crate::ha::FailoverStrategy {
24    crate::ha::FailoverStrategy::ActivePassive
25}
26fn interval() -> u64 {
27    30
28}
29fn timeout() -> u64 {
30    5
31}
32fn threshold() -> u32 {
33    3
34}
35fn cooldown() -> u64 {
36    60
37}
38
39impl HaConfig {
40    pub fn validate(&self, cas: &[super::CaConfig]) -> Result<(), String> {
41        if !self.enabled {
42            return Ok(());
43        }
44        if self.probe_interval_secs == 0
45            || self.probe_timeout_secs == 0
46            || self.failure_threshold == 0
47            || self.cooldown_secs == 0
48        {
49            return Err("HA timing and failure threshold values must be positive".into());
50        }
51        for (id, endpoint) in &self.endpoints {
52            if !cas.iter().any(|ca| &ca.id == id) {
53                return Err(format!("unknown HA CA: {id}"));
54            }
55            let url = url::Url::parse(endpoint).map_err(|e| e.to_string())?;
56            if url.scheme() != "https"
57                || url.host_str().is_none()
58                || !url.username().is_empty()
59                || url.password().is_some()
60            {
61                return Err("HA endpoints must use HTTPS without URL credentials".into());
62            }
63        }
64        Ok(())
65    }
66}