1use std::sync::Arc;
8use std::time::Instant;
9
10use indexmap::IndexMap;
11
12use crate::audit::AuditState;
13use crate::config::Config;
14
15#[derive(Clone)]
17pub struct AppState {
18 pub config: Arc<Config>,
20
21 pub secrets: Arc<crate::config::ResolvedSecrets>,
23
24 pub db: sqlx::AnyPool,
26
27 pub db_ro: sqlx::AnyPool,
34
35 pub db_kind: crate::db::DbKind,
37
38 pub cas: Arc<IndexMap<String, Arc<CaState>>>,
40
41 pub default_ca_id: Arc<String>,
43
44 pub otp_store: Option<Arc<kipuka_otp::OtpStore>>,
46
47 pub hsm: Option<Arc<kipuka_hsm::HsmContext>>,
49
50 pub audit: Arc<AuditState>,
52
53 pub failure_tracker: Arc<crate::auth::failure_tracker::FailureTracker>,
59
60 pub ha_manager: Option<Arc<crate::ha::HaManager>>,
62
63 pub gss_cred: Option<Arc<dyn std::any::Any + Send + Sync>>,
68
69 pub gssapi_require_crypto: bool,
77
78 pub dogtag: Option<Arc<kipuka_dogtag::DogtagPool>>,
83
84 pub star_manager: Option<Arc<crate::star::StarManager>>,
88
89 pub startup_time: Instant,
94}
95
96impl AppState {
97 pub fn default_ca(&self) -> &Arc<CaState> {
105 self.cas
106 .get(self.default_ca_id.as_str())
107 .expect("default CA always present in cas")
108 }
109
110 pub fn get_ca(&self, ca_id: &str) -> Option<&Arc<CaState>> {
112 self.cas.get(ca_id)
113 }
114
115 pub fn default_ca_cert_der(&self) -> Option<Vec<u8>> {
121 let ca = self.default_ca();
122 if ca.cert_der.is_empty() {
123 None
124 } else {
125 Some(ca.cert_der.clone())
126 }
127 }
128
129 pub async fn admit_enrollment(
131 &self,
132 identity: &str,
133 operation: &str,
134 ) -> Result<(), crate::error::KipukaError> {
135 crate::audit::record_checked(
136 &self.db,
137 &self.audit,
138 crate::audit::AuditEvent::new(crate::audit::AuditEventType::EnrollRequest)
139 .with_operator(identity)
140 .with_detail(operation),
141 )
142 .await
143 }
144
145 pub async fn record_audit_event(&self, event_type: &str, detail: &str) {
150 crate::audit::record(
151 &self.db,
152 &self.audit,
153 crate::audit::AuditEvent::new(Self::audit_type_for(event_type)).with_detail(detail),
154 )
155 .await;
156 }
157
158 pub async fn record_audit_event_with_actor(&self, event_type: &str, actor: &str, detail: &str) {
168 crate::audit::record(
169 &self.db,
170 &self.audit,
171 crate::audit::AuditEvent::new(Self::audit_type_for(event_type))
172 .with_operator(actor)
173 .with_detail(detail),
174 )
175 .await;
176 }
177
178 fn audit_type_for(event_type: &str) -> crate::audit::AuditEventType {
184 match event_type {
185 "cacerts" => crate::audit::AuditEventType::EnrollRequest,
186 "cert_issued" | "simpleenroll_success" | "simpleenroll_deferred" => {
187 crate::audit::AuditEventType::CertIssue
188 }
189 "simplereenroll_success" => crate::audit::AuditEventType::CertReenroll,
190 "fullcmc_success" => crate::audit::AuditEventType::CertIssue,
191 "serverkeygen_success" => crate::audit::AuditEventType::CertIssue,
192 "simpleenroll_denied"
198 | "simplereenroll_denied"
199 | "serverkeygen_denied"
200 | "cms_simpleenroll_denied"
201 | "cms_simplereenroll_denied"
202 | "cms_serverkeygen_denied" => crate::audit::AuditEventType::EnrollReject,
203 "otp_generated" => crate::audit::AuditEventType::OtpCreate,
204 "otp_revoked" => crate::audit::AuditEventType::OtpRevoke,
205 "otp_auth_failure" => crate::audit::AuditEventType::AuthFailure,
206 "auth_lockout" => crate::audit::AuditEventType::SecurityViolation,
209 "cert_revoked" => crate::audit::AuditEventType::CertRevoke,
210 "star_order_created" | "star_renewal_success" => {
211 crate::audit::AuditEventType::CertIssue
212 }
213 "star_order_cancelled" | "admin_audit_review" => {
214 crate::audit::AuditEventType::AdminAction
215 }
216 "coap_cacerts" | "coap_csrattrs" => crate::audit::AuditEventType::EnrollRequest,
222 "coap_simpleenroll" => crate::audit::AuditEventType::CertIssue,
223 "coap_simplereenroll" => crate::audit::AuditEventType::CertReenroll,
224 "coap_simpleenroll_denied" | "coap_simplereenroll_denied" => {
225 crate::audit::AuditEventType::EnrollReject
226 }
227 _ => crate::audit::AuditEventType::AdminAction,
228 }
229 }
230}
231
232pub struct CaState {
238 pub id: String,
240
241 pub key_type: String,
243
244 pub cert_der: Vec<u8>,
246
247 pub cert_chain: Vec<Vec<u8>>,
251
252 pub hash_algorithm: String,
254
255 pub validity_days: u32,
257
258 pub crl_url: Option<String>,
260
261 pub ocsp_url: Option<String>,
263
264 pub crl_cache: parking_lot::Mutex<Option<(Vec<u8>, std::time::Instant)>>,
269
270 pub cab_forum_compliant: bool,
272}
273
274pub struct AppStateBuilder {
279 config: Option<Arc<Config>>,
280 secrets: Option<Arc<crate::config::ResolvedSecrets>>,
281 db: Option<sqlx::AnyPool>,
282 db_ro: Option<sqlx::AnyPool>,
283 db_kind: Option<crate::db::DbKind>,
284 cas: Option<Arc<IndexMap<String, Arc<CaState>>>>,
285 default_ca_id: Option<Arc<String>>,
286 otp_store: Option<Arc<kipuka_otp::OtpStore>>,
287 hsm: Option<Arc<kipuka_hsm::HsmContext>>,
288 audit: Option<Arc<AuditState>>,
289 failure_tracker: Option<Arc<crate::auth::failure_tracker::FailureTracker>>,
290 ha_manager: Option<Arc<crate::ha::HaManager>>,
291 gss_cred: Option<Arc<dyn std::any::Any + Send + Sync>>,
292 gssapi_require_crypto: bool,
293 dogtag: Option<Arc<kipuka_dogtag::DogtagPool>>,
294 star_manager: Option<Arc<crate::star::StarManager>>,
295}
296
297impl AppStateBuilder {
298 pub fn new() -> Self {
300 Self {
301 config: None,
302 secrets: None,
303 db: None,
304 db_ro: None,
305 db_kind: None,
306 cas: None,
307 default_ca_id: None,
308 otp_store: None,
309 hsm: None,
310 audit: None,
311 failure_tracker: None,
312 ha_manager: None,
313 gss_cred: None,
314 gssapi_require_crypto: true,
315 dogtag: None,
316 star_manager: None,
317 }
318 }
319
320 pub fn config(mut self, config: Arc<Config>) -> Self {
321 self.config = Some(config);
322 self
323 }
324
325 pub fn secrets(mut self, secrets: Arc<crate::config::ResolvedSecrets>) -> Self {
326 self.secrets = Some(secrets);
327 self
328 }
329
330 pub fn db(mut self, pool: sqlx::AnyPool) -> Self {
331 self.db = Some(pool);
332 self
333 }
334
335 pub fn db_ro(mut self, pool: sqlx::AnyPool) -> Self {
336 self.db_ro = Some(pool);
337 self
338 }
339
340 pub fn db_kind(mut self, kind: crate::db::DbKind) -> Self {
341 self.db_kind = Some(kind);
342 self
343 }
344
345 pub fn cas(mut self, cas: IndexMap<String, Arc<CaState>>) -> Self {
346 self.cas = Some(Arc::new(cas));
347 self
348 }
349
350 pub fn default_ca_id(mut self, id: String) -> Self {
351 self.default_ca_id = Some(Arc::new(id));
352 self
353 }
354
355 pub fn otp_store(mut self, store: Arc<kipuka_otp::OtpStore>) -> Self {
356 self.otp_store = Some(store);
357 self
358 }
359
360 pub fn hsm(mut self, ctx: Arc<kipuka_hsm::HsmContext>) -> Self {
361 self.hsm = Some(ctx);
362 self
363 }
364
365 pub fn audit(mut self, state: Arc<AuditState>) -> Self {
366 self.audit = Some(state);
367 self
368 }
369
370 pub fn failure_tracker(
371 mut self,
372 tracker: Arc<crate::auth::failure_tracker::FailureTracker>,
373 ) -> Self {
374 self.failure_tracker = Some(tracker);
375 self
376 }
377
378 pub fn ha_manager(mut self, manager: Arc<crate::ha::HaManager>) -> Self {
379 self.ha_manager = Some(manager);
380 self
381 }
382
383 pub fn gss_cred(mut self, cred: Arc<dyn std::any::Any + Send + Sync>) -> Self {
384 self.gss_cred = Some(cred);
385 self
386 }
387
388 pub fn gssapi_require_crypto(mut self, require: bool) -> Self {
389 self.gssapi_require_crypto = require;
390 self
391 }
392
393 pub fn dogtag(mut self, pool: Arc<kipuka_dogtag::DogtagPool>) -> Self {
394 self.dogtag = Some(pool);
395 self
396 }
397
398 pub fn star_manager(mut self, manager: Arc<crate::star::StarManager>) -> Self {
399 self.star_manager = Some(manager);
400 self
401 }
402
403 pub fn build(self) -> AppState {
410 let db = self.db.expect("db is required");
411 let db_ro = self.db_ro.unwrap_or_else(|| db.clone());
412 let config = self.config.expect("config is required");
413
414 let failure_tracker = self.failure_tracker.unwrap_or_else(|| {
416 Arc::new(crate::auth::failure_tracker::FailureTracker::new(
417 config.auth_lockout.to_policy(),
418 ))
419 });
420
421 AppState {
422 config,
423 secrets: self.secrets.expect("secrets is required"),
424 db,
425 db_ro,
426 db_kind: self.db_kind.expect("db_kind is required"),
427 cas: self.cas.expect("cas is required"),
428 default_ca_id: self.default_ca_id.expect("default_ca_id is required"),
429 otp_store: self.otp_store,
430 hsm: self.hsm,
431 audit: self.audit.expect("audit is required"),
432 failure_tracker,
433 ha_manager: self.ha_manager,
434 gss_cred: self.gss_cred,
435 gssapi_require_crypto: self.gssapi_require_crypto,
436 dogtag: self.dogtag,
437 star_manager: self.star_manager,
438 startup_time: Instant::now(),
439 }
440 }
441}
442
443impl Default for AppStateBuilder {
444 fn default() -> Self {
445 Self::new()
446 }
447}