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 db: sqlx::AnyPool,
23
24 pub db_ro: sqlx::AnyPool,
31
32 pub db_kind: crate::db::DbKind,
34
35 pub cas: Arc<IndexMap<String, Arc<CaState>>>,
37
38 pub default_ca_id: Arc<String>,
40
41 pub otp_store: Option<Arc<kipuka_otp::OtpStore>>,
43
44 pub hsm: Option<Arc<kipuka_hsm::HsmContext>>,
46
47 pub audit: Arc<AuditState>,
49
50 pub ha_manager: Option<Arc<crate::ha::HaManager>>,
52
53 pub gss_cred: Option<Arc<dyn std::any::Any + Send + Sync>>,
58
59 pub star_manager: Option<Arc<crate::star::StarManager>>,
63
64 pub startup_time: Instant,
69}
70
71impl AppState {
72 pub fn default_ca(&self) -> &Arc<CaState> {
80 self.cas
81 .get(self.default_ca_id.as_str())
82 .expect("default CA always present in cas")
83 }
84
85 pub fn get_ca(&self, ca_id: &str) -> Option<&Arc<CaState>> {
87 self.cas.get(ca_id)
88 }
89
90 pub fn default_ca_cert_der(&self) -> Option<Vec<u8>> {
96 let ca = self.default_ca();
97 if ca.cert_der.is_empty() {
98 None
99 } else {
100 Some(ca.cert_der.clone())
101 }
102 }
103
104 pub async fn record_audit_event(&self, event_type: &str, detail: &str) {
109 let audit_type = match event_type {
112 "cacerts" => crate::audit::AuditEventType::EnrollRequest,
113 "simpleenroll_success" | "simpleenroll_deferred" => {
114 crate::audit::AuditEventType::CertIssue
115 }
116 "simplereenroll_success" => crate::audit::AuditEventType::CertReenroll,
117 "fullcmc_success" => crate::audit::AuditEventType::CertIssue,
118 "serverkeygen_success" => crate::audit::AuditEventType::CertIssue,
119 "otp_generated" => crate::audit::AuditEventType::OtpCreate,
120 "otp_revoked" => crate::audit::AuditEventType::OtpRevoke,
121 "otp_auth_failure" => crate::audit::AuditEventType::AuthFailure,
122 "cert_revoked" => crate::audit::AuditEventType::CertRevoke,
123 "star_order_created" | "star_renewal_success" => {
124 crate::audit::AuditEventType::CertIssue
125 }
126 "star_order_cancelled" => crate::audit::AuditEventType::AdminAction,
127 _ => crate::audit::AuditEventType::AdminAction,
128 };
129
130 crate::audit::record(
131 &self.db,
132 &self.audit,
133 crate::audit::AuditEvent::new(audit_type).with_detail(detail),
134 )
135 .await;
136 }
137}
138
139pub struct CaState {
145 pub id: String,
147
148 pub key_type: String,
150
151 pub cert_der: Vec<u8>,
153
154 pub cert_chain: Vec<Vec<u8>>,
158
159 pub hash_algorithm: String,
161
162 pub validity_days: u32,
164
165 pub crl_url: Option<String>,
167
168 pub ocsp_url: Option<String>,
170
171 pub crl_cache: parking_lot::Mutex<Option<(Vec<u8>, std::time::Instant)>>,
176
177 pub cab_forum_compliant: bool,
179}
180
181pub struct AppStateBuilder {
186 config: Option<Arc<Config>>,
187 db: Option<sqlx::AnyPool>,
188 db_ro: Option<sqlx::AnyPool>,
189 db_kind: Option<crate::db::DbKind>,
190 cas: Option<Arc<IndexMap<String, Arc<CaState>>>>,
191 default_ca_id: Option<Arc<String>>,
192 otp_store: Option<Arc<kipuka_otp::OtpStore>>,
193 hsm: Option<Arc<kipuka_hsm::HsmContext>>,
194 audit: Option<Arc<AuditState>>,
195 ha_manager: Option<Arc<crate::ha::HaManager>>,
196 gss_cred: Option<Arc<dyn std::any::Any + Send + Sync>>,
197 star_manager: Option<Arc<crate::star::StarManager>>,
198}
199
200impl AppStateBuilder {
201 pub fn new() -> Self {
203 Self {
204 config: None,
205 db: None,
206 db_ro: None,
207 db_kind: None,
208 cas: None,
209 default_ca_id: None,
210 otp_store: None,
211 hsm: None,
212 audit: None,
213 ha_manager: None,
214 gss_cred: None,
215 star_manager: None,
216 }
217 }
218
219 pub fn config(mut self, config: Arc<Config>) -> Self {
220 self.config = Some(config);
221 self
222 }
223
224 pub fn db(mut self, pool: sqlx::AnyPool) -> Self {
225 self.db = Some(pool);
226 self
227 }
228
229 pub fn db_ro(mut self, pool: sqlx::AnyPool) -> Self {
230 self.db_ro = Some(pool);
231 self
232 }
233
234 pub fn db_kind(mut self, kind: crate::db::DbKind) -> Self {
235 self.db_kind = Some(kind);
236 self
237 }
238
239 pub fn cas(mut self, cas: IndexMap<String, Arc<CaState>>) -> Self {
240 self.cas = Some(Arc::new(cas));
241 self
242 }
243
244 pub fn default_ca_id(mut self, id: String) -> Self {
245 self.default_ca_id = Some(Arc::new(id));
246 self
247 }
248
249 pub fn otp_store(mut self, store: Arc<kipuka_otp::OtpStore>) -> Self {
250 self.otp_store = Some(store);
251 self
252 }
253
254 pub fn hsm(mut self, ctx: Arc<kipuka_hsm::HsmContext>) -> Self {
255 self.hsm = Some(ctx);
256 self
257 }
258
259 pub fn audit(mut self, state: Arc<AuditState>) -> Self {
260 self.audit = Some(state);
261 self
262 }
263
264 pub fn ha_manager(mut self, manager: Arc<crate::ha::HaManager>) -> Self {
265 self.ha_manager = Some(manager);
266 self
267 }
268
269 pub fn gss_cred(mut self, cred: Arc<dyn std::any::Any + Send + Sync>) -> Self {
270 self.gss_cred = Some(cred);
271 self
272 }
273
274 pub fn star_manager(mut self, manager: Arc<crate::star::StarManager>) -> Self {
275 self.star_manager = Some(manager);
276 self
277 }
278
279 pub fn build(self) -> AppState {
286 let db = self.db.expect("db is required");
287 let db_ro = self.db_ro.unwrap_or_else(|| db.clone());
288
289 AppState {
290 config: self.config.expect("config is required"),
291 db,
292 db_ro,
293 db_kind: self.db_kind.expect("db_kind is required"),
294 cas: self.cas.expect("cas is required"),
295 default_ca_id: self.default_ca_id.expect("default_ca_id is required"),
296 otp_store: self.otp_store,
297 hsm: self.hsm,
298 audit: self.audit.expect("audit is required"),
299 ha_manager: self.ha_manager,
300 gss_cred: self.gss_cred,
301 star_manager: self.star_manager,
302 startup_time: Instant::now(),
303 }
304 }
305}
306
307impl Default for AppStateBuilder {
308 fn default() -> Self {
309 Self::new()
310 }
311}