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 /// Configured opaque Bearer credential (operator/auditor).
15 Bearer,
16 /// mTLS client certificate authentication.
17 #[default]
18 Mtls,
19 /// HTTP Basic authentication.
20 Basic,
21 /// GSSAPI/SPNEGO (Kerberos) authentication.
22 Gssapi,
23}
24
25/// `[admin]` section — administrative API configuration.
26///
27/// When this section is absent, admin endpoints return 404.
28///
29/// ```toml
30/// [admin]
31/// enabled = true
32/// listen_addr = "127.0.0.1:8444"
33/// auth_method = "mtls"
34/// allowed_operators = ["admin@example.com"]
35/// ```
36#[derive(Debug, Clone, Deserialize)]
37#[serde(deny_unknown_fields)]
38pub struct AdminConfig {
39 /// Enable the admin API.
40 #[serde(default)]
41 pub enabled: bool,
42
43 /// Listen address for the admin API.
44 ///
45 /// When absent, admin endpoints are served on the main EST listener.
46 /// Setting a separate address allows binding the admin API to a
47 /// management network interface.
48 pub listen_addr: Option<String>,
49
50 /// Authentication method for admin API access.
51 #[serde(default)]
52 pub auth_method: AdminAuthMethod,
53
54 /// List of allowed operator identities.
55 ///
56 /// The format depends on `auth_method`:
57 /// - `mtls` — Subject DN or SAN email of the client certificate.
58 /// - `basic` — Username (passwords stored in the database).
59 /// - `gssapi` — Kerberos principal name.
60 #[serde(default)]
61 pub allowed_operators: Vec<String>,
62
63 /// List of allowed auditor identities (read-only role).
64 ///
65 /// NIAP CA PP FMT_SMR.1: an auditor may query audit and status endpoints
66 /// but is refused all mutating operations. Format matches
67 /// `allowed_operators` (per `auth_method`). An identity listed in both
68 /// `allowed_operators` and here is treated as an operator (operator
69 /// precedence).
70 #[serde(default)]
71 pub allowed_auditors: Vec<String>,
72
73 /// Path to the CA certificate bundle (PEM) for admin mTLS.
74 ///
75 /// RHELBU-3536 R18: separate truststore from the EST client truststore.
76 /// Required when `auth_method = "mtls"`.
77 pub admin_ca_file: Option<String>,
78
79 /// Session TTL in seconds.
80 ///
81 /// Admin sessions expire after this duration of inactivity.
82 /// Default: 3600 (1 hour).
83 #[serde(default = "default_session_ttl_secs")]
84 pub session_ttl_secs: u64,
85
86 /// Maximum concurrent admin sessions.
87 /// Default: 16.
88 #[serde(default = "default_max_sessions")]
89 pub max_sessions: usize,
90
91 /// Bearer token for admin API authentication.
92 ///
93 /// When set, `Authorization: Bearer <token>` requests are validated
94 /// against this value using constant-time comparison. Required when
95 /// operators authenticate via Bearer tokens (e.g., CI pipelines,
96 /// monitoring systems).
97 pub bearer_token: Option<super::SecretRef>,
98
99 /// Bearer token for read-only auditor API access.
100 ///
101 /// When set, `Authorization: Bearer <token>` requests matching this value
102 /// (constant-time) are granted the [`crate::routes::admin::AdminRole::Auditor`]
103 /// role. A token matching `bearer_token` takes precedence (operator).
104 pub auditor_bearer_token: Option<super::SecretRef>,
105
106 /// GSSAPI configuration (required when `auth_method = "gssapi"`).
107 pub gssapi: Option<AdminGssapiConfig>,
108}
109
110/// GSSAPI/SPNEGO configuration for admin authentication.
111#[derive(Debug, Clone, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct AdminGssapiConfig {
114 /// Path to the Kerberos keytab file.
115 pub keytab_file: Option<String>,
116
117 /// Service principal name.
118 /// Default: `"HTTP"` (the hostname is appended automatically).
119 #[serde(default = "default_service_name")]
120 pub service_name: String,
121
122 /// Use gssproxy for credential management instead of a keytab.
123 #[serde(default)]
124 pub gssproxy: bool,
125
126 /// Require cryptographic verification of Kerberos tickets.
127 ///
128 /// When `true` (the default), GSSAPI authentication is rejected unless
129 /// libgssapi integration is compiled in and a valid keytab/gssproxy is
130 /// available to decrypt and verify the ticket. When `false`, structural
131 /// parsing of the SPNEGO token is allowed — the service name (sname) is
132 /// extracted from the cleartext portion of the ticket, but no
133 /// cryptographic verification is performed and the client identity
134 /// cannot be authenticated.
135 ///
136 /// **Security warning:** Setting this to `false` should only be used
137 /// for development, logging, or environments where TLS client
138 /// certificates provide the primary authentication.
139 #[serde(default = "default_require_crypto_verification")]
140 pub require_crypto_verification: bool,
141}
142
143fn default_require_crypto_verification() -> bool {
144 true
145}
146
147fn default_session_ttl_secs() -> u64 {
148 3600
149}
150
151fn default_max_sessions() -> usize {
152 16
153}
154
155fn default_service_name() -> String {
156 "HTTP".to_string()
157}
158
159impl Default for AdminConfig {
160 fn default() -> Self {
161 Self {
162 enabled: false,
163 listen_addr: None,
164 auth_method: AdminAuthMethod::default(),
165 allowed_operators: Vec::new(),
166 allowed_auditors: Vec::new(),
167 admin_ca_file: None,
168 session_ttl_secs: default_session_ttl_secs(),
169 max_sessions: default_max_sessions(),
170 bearer_token: None,
171 auditor_bearer_token: None,
172 gssapi: None,
173 }
174 }
175}
176
177impl AdminConfig {
178 /// Validate admin configuration constraints.
179 pub fn validate(&self) -> std::result::Result<(), String> {
180 if !self.enabled {
181 return Ok(());
182 }
183
184 if self.auth_method == AdminAuthMethod::Mtls && self.admin_ca_file.is_none() {
185 return Err("[admin].admin_ca_file is required when auth_method = \"mtls\"".into());
186 }
187
188 if self.auth_method == AdminAuthMethod::Gssapi {
189 match &self.gssapi {
190 None => {
191 return Err(
192 "[admin].gssapi section is required when auth_method = \"gssapi\"".into(),
193 );
194 }
195 Some(g) => {
196 if !g.gssproxy && g.keytab_file.is_none() {
197 return Err(
198 "[admin.gssapi]: set `keytab_file` or enable `gssproxy = true`".into(),
199 );
200 }
201 if g.gssproxy && g.keytab_file.is_some() {
202 return Err(
203 "[admin.gssapi]: `keytab_file` and `gssproxy = true` are mutually exclusive".into(),
204 );
205 }
206 }
207 }
208 }
209
210 if self.session_ttl_secs == 0 {
211 return Err("[admin].session_ttl_secs must be at least 1".into());
212 }
213
214 Ok(())
215 }
216}