Skip to main content

kipuka/config/
admin.rs

1//! Admin API configuration.
2//!
3//! The `[admin]` section controls the administrative REST API used for
4//! operator management, OTP provisioning, CA health monitoring, and
5//! audit log queries.
6
7use serde::Deserialize;
8
9/// Admin authentication method.
10#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "lowercase")]
12#[derive(Default)]
13pub enum AdminAuthMethod {
14    /// mTLS client certificate authentication.
15    #[default]
16    Mtls,
17    /// HTTP Basic authentication.
18    Basic,
19    /// GSSAPI/SPNEGO (Kerberos) authentication.
20    Gssapi,
21}
22
23/// `[admin]` section — administrative API configuration.
24///
25/// When this section is absent, admin endpoints return 404.
26///
27/// ```toml
28/// [admin]
29/// enabled = true
30/// listen_addr = "127.0.0.1:8444"
31/// auth_method = "mtls"
32/// allowed_operators = ["admin@example.com"]
33/// ```
34#[derive(Debug, Clone, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct AdminConfig {
37    /// Enable the admin API.
38    #[serde(default)]
39    pub enabled: bool,
40
41    /// Listen address for the admin API.
42    ///
43    /// When absent, admin endpoints are served on the main EST listener.
44    /// Setting a separate address allows binding the admin API to a
45    /// management network interface.
46    pub listen_addr: Option<String>,
47
48    /// Authentication method for admin API access.
49    #[serde(default)]
50    pub auth_method: AdminAuthMethod,
51
52    /// List of allowed operator identities.
53    ///
54    /// The format depends on `auth_method`:
55    /// - `mtls` — Subject DN or SAN email of the client certificate.
56    /// - `basic` — Username (passwords stored in the database).
57    /// - `gssapi` — Kerberos principal name.
58    #[serde(default)]
59    pub allowed_operators: Vec<String>,
60
61    /// Path to the CA certificate bundle (PEM) for admin mTLS.
62    ///
63    /// RHELBU-3536 R18: separate truststore from the EST client truststore.
64    /// Required when `auth_method = "mtls"`.
65    pub admin_ca_file: Option<String>,
66
67    /// Session TTL in seconds.
68    ///
69    /// Admin sessions expire after this duration of inactivity.
70    /// Default: 3600 (1 hour).
71    #[serde(default = "default_session_ttl_secs")]
72    pub session_ttl_secs: u64,
73
74    /// Maximum concurrent admin sessions.
75    /// Default: 16.
76    #[serde(default = "default_max_sessions")]
77    pub max_sessions: usize,
78
79    /// GSSAPI configuration (required when `auth_method = "gssapi"`).
80    pub gssapi: Option<AdminGssapiConfig>,
81}
82
83/// GSSAPI/SPNEGO configuration for admin authentication.
84#[derive(Debug, Clone, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct AdminGssapiConfig {
87    /// Path to the Kerberos keytab file.
88    pub keytab_file: Option<String>,
89
90    /// Service principal name.
91    /// Default: `"HTTP"` (the hostname is appended automatically).
92    #[serde(default = "default_service_name")]
93    pub service_name: String,
94
95    /// Use gssproxy for credential management instead of a keytab.
96    #[serde(default)]
97    pub gssproxy: bool,
98}
99
100fn default_session_ttl_secs() -> u64 {
101    3600
102}
103
104fn default_max_sessions() -> usize {
105    16
106}
107
108fn default_service_name() -> String {
109    "HTTP".to_string()
110}
111
112impl Default for AdminConfig {
113    fn default() -> Self {
114        Self {
115            enabled: false,
116            listen_addr: None,
117            auth_method: AdminAuthMethod::default(),
118            allowed_operators: Vec::new(),
119            admin_ca_file: None,
120            session_ttl_secs: default_session_ttl_secs(),
121            max_sessions: default_max_sessions(),
122            gssapi: None,
123        }
124    }
125}
126
127impl AdminConfig {
128    /// Validate admin configuration constraints.
129    pub fn validate(&self) -> std::result::Result<(), String> {
130        if !self.enabled {
131            return Ok(());
132        }
133
134        if self.auth_method == AdminAuthMethod::Mtls && self.admin_ca_file.is_none() {
135            return Err("[admin].admin_ca_file is required when auth_method = \"mtls\"".into());
136        }
137
138        if self.auth_method == AdminAuthMethod::Gssapi {
139            match &self.gssapi {
140                None => {
141                    return Err(
142                        "[admin].gssapi section is required when auth_method = \"gssapi\"".into(),
143                    );
144                }
145                Some(g) => {
146                    if !g.gssproxy && g.keytab_file.is_none() {
147                        return Err(
148                            "[admin.gssapi]: set `keytab_file` or enable `gssproxy = true`".into(),
149                        );
150                    }
151                    if g.gssproxy && g.keytab_file.is_some() {
152                        return Err(
153                            "[admin.gssapi]: `keytab_file` and `gssproxy = true` are mutually exclusive".into(),
154                        );
155                    }
156                }
157            }
158        }
159
160        if self.session_ttl_secs == 0 {
161            return Err("[admin].session_ttl_secs must be at least 1".into());
162        }
163
164        Ok(())
165    }
166}