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 = ["[email protected]"]
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}