1use core::fmt::Debug;
3use std::ops::{Deref, DerefMut};
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context as _, Result};
7use url::Url;
8
9use crate::misc::serde_ext::bytes_wrapper::B64UU;
10use crate::servers::{for_all_servers, server::Server as _};
11use crate::{
12 api::{self},
13 attr,
14 common::elgamal,
15 hub,
16 misc::{jwt, serde_ext, time_ext},
17 servers::yivi,
18};
19
20use super::host_aliases::{HostAliases, UrlPwa};
21use super::log::LogConfig;
22
23#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
27#[serde(deny_unknown_fields)]
28pub struct Config {
29 pub phc_url: UrlPwa,
33
34 #[serde(default)]
36 pub log: Option<LogConfig>,
37
38 #[serde(skip)]
39 pub(crate) preparation_state: PreparationState,
40
41 #[serde(default)]
46 pub host_aliases: HostAliases,
47
48 #[serde(default)]
50 pub wd: PathBuf,
51
52 pub phc: Option<ServerConfig<phc::ExtraConfig>>,
54
55 pub transcryptor: Option<ServerConfig<transcryptor::ExtraConfig>>,
57
58 pub auths: Option<ServerConfig<auths::ExtraConfig>>,
60}
61
62#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub(crate) enum PreparationState {
65 #[default]
67 Unprepared,
68
69 Preliminary,
71
72 Complete,
74}
75
76#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
78#[serde(deny_unknown_fields)]
79pub struct ServerConfig<ServerSpecific> {
80 #[serde(default)]
82 pub port: u16,
83
84 #[serde(default = "default_ips")]
86 pub ips: Box<[std::net::IpAddr]>,
87
88 pub bind_to: serde_ext::Skip,
91
92 pub self_check_code: Option<String>,
95
96 pub jwt_key: Option<api::SigningKey>,
101
102 pub enc_key: Option<elgamal::PrivateKey>,
110
111 pub admin_key: Option<api::VerifyingKey>,
114
115 pub object_store: Option<ObjectStoreConfig>,
117
118 #[serde(default = "default_version")]
122 pub version: Option<String>,
123
124 #[serde(flatten)]
125 extra: ServerSpecific,
127}
128
129impl<X> Deref for ServerConfig<X> {
130 type Target = X;
131
132 fn deref(&self) -> &X {
133 &self.extra
134 }
135}
136
137impl<X> DerefMut for ServerConfig<X> {
138 fn deref_mut(&mut self) -> &mut X {
139 &mut self.extra
140 }
141}
142
143fn default_version() -> Option<String> {
144 crate::servers::version().map(str::to_string)
145}
146
147fn default_ips() -> Box<[std::net::IpAddr]> {
148 Box::new([
149 std::net::Ipv6Addr::UNSPECIFIED.into(), std::net::Ipv4Addr::UNSPECIFIED.into(), ])
154}
155
156impl<'a, X> std::net::ToSocketAddrs for &'a ServerConfig<X> {
157 type Iter = Box<dyn Iterator<Item = std::net::SocketAddr> + 'a>;
158
159 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
160 Ok(Box::new(self.ips.iter().map(|ip| (*ip, self.port).into())))
161 }
162}
163
164impl Config {
165 pub fn load_from_path(path: &Path) -> Result<Option<Self>> {
169 let mut res: Self = toml::from_str(&match std::fs::read_to_string(path) {
171 Ok(contents) => contents,
172 Err(e) => match e.kind() {
173 std::io::ErrorKind::NotFound => return Ok(None),
174 _ => {
175 return Err(e)
176 .with_context(|| format!("could not open config file {}", path.display()));
177 }
178 },
179 })
180 .with_context(|| format!("could not parse config file {}", path.display()))?;
181
182 if res.wd.as_os_str().is_empty() {
183 res.wd = path
184 .canonicalize()
185 .with_context(|| format!("failed to canonicalize path {}", path.display()))?
186 .parent()
187 .expect("did not expect a configuration file without a parent directory")
188 .into();
189 }
190
191 if !res.wd.is_absolute() {
192 anyhow::bail!(
193 "if you specify a working directory (`wd` in {}) it must be absolute",
194 path.display()
195 );
196 }
197
198 res.preliminary_prep()?;
199
200 log::info!(
201 "loaded config file from {}; interpretting relative paths in {}",
202 path.display(),
203 res.wd.display()
204 );
205
206 Ok(Some(res))
207 }
208
209 pub fn preliminary_prep(&mut self) -> Result<()> {
210 anyhow::ensure!(
211 self.preparation_state == PreparationState::Unprepared,
212 "configuration already (partially) prepared: {:?}",
213 self.preparation_state
214 );
215
216 self.host_aliases.resolve_all()?;
217 self.host_aliases.dealias(&mut self.phc_url);
218
219 self.preparation_state = PreparationState::Preliminary;
220
221 Ok(())
222 }
223
224 pub async fn prepare_for(&self, server: crate::servers::Name) -> Result<Self> {
227 anyhow::ensure!(
228 self.preparation_state == PreparationState::Preliminary,
229 "configuration not in the correct preparation state"
230 );
231
232 let Self {
234 log: _,
235 host_aliases,
236 phc_url,
237 wd,
238 preparation_state,
239 phc: _,
240 transcryptor: _,
241 auths: _,
242 } = self;
243
244 let mut config: Config = Config {
245 log: None, host_aliases: host_aliases.clone(),
247 phc_url: phc_url.clone(),
248 wd: wd.clone(),
249 preparation_state: *preparation_state,
250 phc: None,
251 transcryptor: None,
252 auths: None,
253 };
254
255 macro_rules! clone_only_server {
256 ($server:ident) => {
257 if crate::servers::$server::Server::NAME == server {
258 assert!(self.$server.is_some());
259 config.$server.clone_from(&self.$server);
260 }
261 };
262 }
263
264 for_all_servers!(clone_only_server);
265
266 config.prepare().await?;
267
268 Ok(config)
269 }
270
271 pub async fn prepare(&mut self) -> anyhow::Result<()> {
273 let pcc = Pcc::new(actix_web::dev::Extensions::new());
274
275 PrepareConfig::prepare(self, pcc).await
276 }
277
278 pub fn json_updated(&self, pointer: &str, new_value: serde_json::Value) -> Result<Self> {
280 let mut json_config: serde_json::Value =
281 serde_json::to_value(self).context("failed to serialize config")?;
282
283 let to_be_modified: &mut serde_json::Value =
284 json_config.pointer_mut(pointer).with_context(|| {
285 format!(
286 "wanted to modify {pointer} of the configuration file, but that points nowhere"
287 )
288 })?;
289
290 to_be_modified.clone_from(&new_value);
291
292 let new_config: Config = serde_json::from_value(json_config).with_context(|| {
293 format!(
294 "wanted to change {pointer} of the configuration file to {new_value}, but the new configuration did not deserialize",
295 )
296 })?;
297
298 Ok(new_config)
299 }
300}
301
302#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
303pub struct ObjectStoreConfig {
304 pub url: UrlPwa,
310
311 #[serde(default)]
313 pub options: std::collections::HashMap<String, String>,
314}
315
316impl Default for ObjectStoreConfig {
317 fn default() -> Self {
318 Self {
319 url: From::<Url>::from("memory:///".try_into().unwrap()),
320 options: Default::default(),
321 }
322 }
323}
324
325pub mod phc {
326 use super::*;
327
328 #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
329 #[serde(deny_unknown_fields)]
330 pub struct ExtraConfig {
331 pub transcryptor_url: UrlPwa,
333
334 pub auths_url: UrlPwa,
336
337 pub global_client_url: UrlPwa,
342
343 pub hubs: Vec<hub::BasicInfo<UrlPwa>>,
345
346 pub master_enc_key_part: Option<elgamal::PrivateKey>,
350
351 pub attr_id_secret: Option<B64UU>,
357
358 #[serde(with = "time_ext::human_duration")]
365 #[serde(default = "default_auth_token_validity")]
366 pub auth_token_validity: core::time::Duration,
367
368 #[serde(with = "time_ext::human_duration")]
370 #[serde(default = "default_pp_nonce_validity")]
371 pub pp_nonce_validity: core::time::Duration,
372
373 #[serde(with = "time_ext::human_duration")]
376 #[serde(default = "default_card_pseud_validity")]
377 pub card_pseud_validity: core::time::Duration,
378
379 pub user_object_hmac_secret: Option<B64UU>,
383
384 #[serde(default)]
386 pub user_quota: api::phc::user::Quota,
387
388 pub card: serde_ext::Skip,
390
391 #[serde(default)]
392 pub hub_cache: crate::servers::phc::HubCacheConfig,
393 }
394
395 fn default_auth_token_validity() -> core::time::Duration {
396 core::time::Duration::from_secs(60 * 60) }
399
400 fn default_pp_nonce_validity() -> core::time::Duration {
401 core::time::Duration::from_secs(30)
402 }
404
405 fn default_card_pseud_validity() -> core::time::Duration {
406 core::time::Duration::from_secs(30)
407 }
409}
410
411pub mod transcryptor {
412 use super::*;
413
414 #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
415 #[serde(deny_unknown_fields)]
416 pub struct ExtraConfig {
417 pub master_enc_key_part: Option<elgamal::PrivateKey>,
421
422 pub pseud_factor_secret: Option<B64UU>,
428 }
429}
430
431pub mod auths {
432 use super::*;
433
434 #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
435 #[serde(deny_unknown_fields)]
436 pub struct ExtraConfig {
437 #[serde(default)]
438 pub attribute_types: Vec<attr::Type>,
439
440 pub yivi: Option<YiviConfig>,
442
443 #[serde(with = "time_ext::human_duration")]
446 #[serde(default = "default_auth_window")]
447 pub auth_window: core::time::Duration,
448
449 pub attr_key_secret: Option<B64UU>,
454 }
455
456 fn default_auth_window() -> core::time::Duration {
457 core::time::Duration::from_secs(60 * 60) }
460
461 impl ExtraConfig {
462 pub(super) fn filter_attribute_types(&mut self) {
467 let mut supported_sources: std::collections::HashSet<attr::Source> = Default::default();
468
469 if self.yivi.is_some() {
470 assert!(supported_sources.insert(attr::Source::Yivi));
471 }
472
473 for attr_type in self.attribute_types.iter_mut() {
474 attr_type.filter_sources(|s| supported_sources.contains(&s))
475 }
476 }
477 }
478
479 #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
480 #[serde(deny_unknown_fields)]
481 pub struct YiviConfig {
482 pub requestor_url: UrlPwa,
485
486 pub requestor_creds: yivi::Credentials<yivi::SigningKey>,
487
488 pub server_name: String,
490
491 pub server_key: Option<yivi::VerifyingKey>,
494
495 #[serde(default)]
498 pub chained_sessions: crate::servers::auths::yivi::ChainedSessionsConfig,
499
500 #[serde(default)]
502 pub card: crate::servers::auths::card::CardConfig,
503 }
504
505 impl YiviConfig {
506 pub fn server_creds(&self) -> yivi::Credentials<yivi::VerifyingKey> {
507 yivi::Credentials {
508 name: self.server_name.clone(),
509 key: self
510 .server_key
511 .clone()
512 .expect("bug: YiviConfig was not properly prepared"),
513 }
514 }
515 }
516}
517
518trait PrepareConfig<C> {
521 async fn prepare(&mut self, context: C) -> anyhow::Result<()>;
522}
523
524type Pcc = std::rc::Rc<actix_web::dev::Extensions>;
525
526impl PrepareConfig<Pcc> for Config {
527 async fn prepare(&mut self, mut c: Pcc) -> anyhow::Result<()> {
528 anyhow::ensure!(
529 self.preparation_state == PreparationState::Preliminary,
530 "configuration not properly prepared"
531 );
532
533 Pcc::get_mut(&mut c)
535 .unwrap()
536 .insert(std::mem::take(&mut self.host_aliases));
537
538 macro_rules! prep {
539 ($server:ident) => {
540 if let Some(ref mut server) = self.$server {
541 server.prepare(c.clone()).await?;
542 }
543 };
544 }
545
546 for_all_servers!(prep);
547
548 drop(std::mem::replace(
550 &mut self.host_aliases,
551 Pcc::get_mut(&mut c)
552 .unwrap()
553 .remove::<HostAliases>()
554 .unwrap(),
555 ));
556
557 self.preparation_state = PreparationState::Complete;
558
559 Ok(())
560 }
561}
562
563impl<Extra: PrepareConfig<Pcc> + GetServerType> PrepareConfig<Pcc> for ServerConfig<Extra> {
564 async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
565 if self.port == 0 {
566 self.port = Extra::ServerT::default_port();
567 }
568
569 self.self_check_code
570 .get_or_insert_with(crate::misc::crypto::random_alphanumeric);
571
572 self.jwt_key.get_or_insert_with(api::SigningKey::generate);
573 self.enc_key.get_or_insert_with(elgamal::PrivateKey::random);
574
575 self.admin_key.get_or_insert_with(|| {
576 let sk = api::SigningKey::generate();
577
578 log::info!(
579 "{} admin key: {}",
580 Extra::ServerT::NAME,
581 serde_json::to_string(&sk)
582 .expect("unexpected error during serialization of admin key")
583 );
584
585 sk.verifying_key().into()
586 });
587
588 if let &mut Some(&mut ref mut osc) = &mut self.object_store.as_mut() {
589 c.get::<HostAliases>()
590 .expect("host aliases were not passed along")
591 .dealias(&mut osc.url);
592 }
593
594 self.extra.prepare(c).await?;
595
596 Ok(())
597 }
598}
599
600impl PrepareConfig<Pcc> for transcryptor::ExtraConfig {
601 async fn prepare(&mut self, _c: Pcc) -> anyhow::Result<()> {
602 self.master_enc_key_part
603 .get_or_insert_with(elgamal::PrivateKey::random);
604
605 self.pseud_factor_secret.get_or_insert_with(|| {
606 serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
607 });
608
609 Ok(())
610 }
611}
612
613impl PrepareConfig<Pcc> for phc::ExtraConfig {
614 async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
615 self.master_enc_key_part
616 .get_or_insert_with(elgamal::PrivateKey::random);
617
618 self.attr_id_secret.get_or_insert_with(|| {
619 serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
620 });
621
622 self.user_object_hmac_secret.get_or_insert_with(|| {
623 serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
624 });
625
626 let ha: &HostAliases = c.get::<HostAliases>().unwrap();
627
628 ha.dealias(&mut self.transcryptor_url);
629 ha.dealias(&mut self.auths_url);
630 ha.dealias(&mut self.global_client_url);
631
632 for hub in self.hubs.iter_mut() {
633 hub.prepare(c.clone()).await?;
634 }
635
636 Ok(())
637 }
638}
639
640impl PrepareConfig<Pcc> for hub::BasicInfo<UrlPwa> {
641 async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
642 let ha: &HostAliases = c.get::<HostAliases>().unwrap();
643
644 ha.dealias(&mut self.url);
645
646 Ok(())
647 }
648}
649
650impl PrepareConfig<Pcc> for auths::ExtraConfig {
651 async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
652 if let Some(ref mut yivi_cfg) = self.yivi {
653 yivi_cfg.prepare(c).await?;
654 }
655
656 self.filter_attribute_types();
657
658 self.attr_key_secret.get_or_insert_with(|| {
659 serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
660 });
661
662 Ok(())
663 }
664}
665
666impl PrepareConfig<Pcc> for auths::YiviConfig {
667 async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
668 let ha: &HostAliases = c.get::<HostAliases>().unwrap();
669
670 ha.dealias(&mut self.requestor_url);
671
672 if self.server_key.is_none() {
673 let pk_url = self.requestor_url.as_ref().join("publickey")?.to_string();
674 log::debug!("yivi server key not set; retrieving from {pk_url}");
675
676 let client = awc::Client::default();
677 let payload: bytes::Bytes = crate::misc::task::retry(|| async {
678 match client.get(&pk_url).send().await {
679 Ok(mut res) => match res.body().await {
680 Ok(body) => Ok::<_, std::convert::Infallible>(Some(body)),
681 Err(err) => {
682 log::warn!(
683 "error reading yivi server response at {}, retrying: {err}",
684 self.requestor_url
685 );
686 Ok(None)
687 }
688 },
689 Err(err) => {
690 log::warn!(
691 "could not reach yivi server at {}, retrying: {err}",
692 self.requestor_url
693 );
694 Ok(None)
695 }
696 }
697 })
698 .await
699 .expect("retry does not fail")
700 .ok_or_else(|| {
701 log::error!("could not reach yivi server at {}", self.requestor_url);
702 anyhow::anyhow!(
703 "getting Yivi server's public key from {pk_url} failed after retries"
704 )
705 })?;
706
707 self.server_key = Some(yivi::VerifyingKey::RS256(
708 jwt::RS256Vk::from_public_key_pem(std::str::from_utf8(&payload)?)
709 .context("decoding public key at {pk_url}")?,
710 ));
711 }
712
713 Ok(())
714 }
715}
716
717pub trait GetServerConfig {
719 type Extra;
720
721 fn server_config(config: &Config) -> &ServerConfig<Self::Extra>;
722}
723
724macro_rules! implement_get_server_config {
725 ($server:ident) => {
726 impl GetServerConfig for crate::servers::$server::Details {
727 type Extra = crate::servers::config::$server::ExtraConfig;
728
729 fn server_config(config: &Config) -> &ServerConfig<Self::Extra> {
730 &config.$server.as_ref().unwrap()
731 }
732 }
733 };
734}
735
736for_all_servers!(implement_get_server_config);
737
738trait GetServerType {
740 type ServerT: crate::servers::Server;
741}
742
743macro_rules! implement_server_type {
744 ($server:ident) => {
745 impl GetServerType for $server::ExtraConfig {
746 type ServerT = crate::servers::$server::Server;
747 }
748 };
749}
750
751for_all_servers!(implement_server_type);