1use cidr::IpCidr;
8use kanidm_proto::backup::BackupCompression;
9use kanidm_proto::config::ServerRole;
10use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
11use kanidm_proto::internal::FsType;
12use serde::Deserialize;
13use serde_with::{formats::PreferOne, serde_as, OneOrMany};
14use sketching::LogLevel;
15use std::fmt::{self, Display};
16use std::fs::File;
17use std::io::Read;
18use std::net::IpAddr;
19use std::path::{Path, PathBuf};
20use std::str::FromStr;
21use std::sync::Arc;
22use url::Url;
23
24use crate::repl::config::ReplicationConfiguration;
25
26#[derive(Debug, Deserialize)]
27struct VersionDetection {
28 #[serde(default)]
29 version: Version,
30}
31
32#[derive(Debug, Deserialize, Default)]
33pub enum Version {
35 #[serde(rename = "2")]
36 V2,
37
38 #[default]
39 Legacy,
40}
41
42#[allow(clippy::large_enum_variant)]
44pub enum ServerConfigUntagged {
45 Version(ServerConfigVersion),
46 Legacy(ServerConfig),
47}
48
49pub enum ServerConfigVersion {
50 V2 { values: ServerConfigV2 },
51}
52
53#[derive(Deserialize, Debug, Clone)]
54pub struct OnlineBackup {
55 pub path: Option<PathBuf>,
57 pub schedule: String,
75 #[serde(default = "default_online_backup_versions")]
76 pub versions: usize,
78 #[serde(default = "default_online_backup_enabled")]
80 pub enabled: bool,
81
82 #[serde(default)]
83 pub compression: BackupCompression,
84}
85
86impl Default for OnlineBackup {
87 fn default() -> Self {
88 OnlineBackup {
89 path: None, schedule: default_online_backup_schedule(),
91 versions: default_online_backup_versions(),
92 enabled: default_online_backup_enabled(),
93 compression: BackupCompression::default(),
94 }
95 }
96}
97
98fn default_online_backup_enabled() -> bool {
99 true
100}
101
102fn default_online_backup_schedule() -> String {
103 "@daily".to_string()
104}
105
106fn default_online_backup_versions() -> usize {
107 7
108}
109
110#[derive(Deserialize, Debug, Clone)]
111pub struct TlsConfiguration {
112 pub chain: PathBuf,
113 pub key: PathBuf,
114 pub client_ca: Option<PathBuf>,
115}
116
117#[derive(Debug, Default)]
118pub enum TcpAddressInfo {
119 #[default]
120 None,
121 ProxyV2(Vec<IpCidr>),
122 ProxyV1(Vec<IpCidr>),
123}
124
125#[derive(Deserialize, Debug, Clone, Default)]
126pub enum LdapAddressInfo {
127 #[default]
128 None,
129 #[serde(rename = "proxy-v2")]
130 ProxyV2(Vec<IpCidr>),
131 #[serde(rename = "proxy-v1")]
132 ProxyV1(Vec<IpCidr>),
133}
134
135impl LdapAddressInfo {
136 pub fn trusted_tcp_info(&self) -> Arc<TcpAddressInfo> {
137 Arc::new(match self {
138 LdapAddressInfo::None => TcpAddressInfo::None,
139 LdapAddressInfo::ProxyV2(trusted) => TcpAddressInfo::ProxyV2(trusted.clone()),
140 LdapAddressInfo::ProxyV1(trusted) => TcpAddressInfo::ProxyV1(trusted.clone()),
141 })
142 }
143}
144
145impl Display for LdapAddressInfo {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 Self::None => f.write_str("none"),
149 Self::ProxyV2(trusted) => {
150 f.write_str("proxy-v2 [ ")?;
151 for ip in trusted {
152 write!(f, "{ip} ")?;
153 }
154 f.write_str("]")
155 }
156 Self::ProxyV1(trusted) => {
157 f.write_str("proxy-v1 [ ")?;
158 for ip in trusted {
159 write!(f, "{ip} ")?;
160 }
161 f.write_str("]")
162 }
163 }
164 }
165}
166
167pub(crate) enum AddressSet {
168 NonContiguousIpSet(Vec<IpCidr>),
169 All,
170}
171
172impl AddressSet {
173 pub(crate) fn contains(&self, ip_addr: &IpAddr) -> bool {
174 match self {
175 Self::All => true,
176 Self::NonContiguousIpSet(range) => {
177 range.iter().any(|ip_cidr| ip_cidr.contains(ip_addr))
178 }
179 }
180 }
181}
182
183#[derive(Deserialize, Debug, Clone, Default)]
184pub enum HttpAddressInfo {
185 #[default]
186 None,
187 #[serde(rename = "x-forward-for")]
188 XForwardFor(Vec<IpCidr>),
189 #[serde(rename = "x-forward-for-all-source-trusted")]
192 XForwardForAllSourcesTrusted,
193 #[serde(rename = "proxy-v2")]
194 ProxyV2(Vec<IpCidr>),
195 #[serde(rename = "proxy-v1")]
196 ProxyV1(Vec<IpCidr>),
197}
198
199impl HttpAddressInfo {
200 pub(crate) fn trusted_x_forward_for(&self) -> Option<AddressSet> {
201 match self {
202 Self::XForwardForAllSourcesTrusted => Some(AddressSet::All),
203 Self::XForwardFor(trusted) => Some(AddressSet::NonContiguousIpSet(trusted.clone())),
204 _ => None,
205 }
206 }
207
208 pub fn trusted_tcp_info(&self) -> Arc<TcpAddressInfo> {
209 Arc::new(match self {
210 Self::ProxyV2(trusted) => TcpAddressInfo::ProxyV2(trusted.clone()),
211 Self::ProxyV1(trusted) => TcpAddressInfo::ProxyV1(trusted.clone()),
212 Self::None | Self::XForwardFor(_) | Self::XForwardForAllSourcesTrusted => {
213 TcpAddressInfo::None
214 }
215 })
216 }
217}
218
219impl Display for HttpAddressInfo {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 Self::None => f.write_str("none"),
223
224 Self::XForwardFor(trusted) => {
225 f.write_str("x-forward-for [ ")?;
226 for ip in trusted {
227 write!(f, "{ip} ")?;
228 }
229 f.write_str("]")
230 }
231 Self::XForwardForAllSourcesTrusted => {
232 f.write_str("x-forward-for [ ALL SOURCES TRUSTED ]")
233 }
234 Self::ProxyV2(trusted) => {
235 f.write_str("proxy-v2 [ ")?;
236 for ip in trusted {
237 write!(f, "{ip} ")?;
238 }
239 f.write_str("]")
240 }
241 Self::ProxyV1(trusted) => {
242 f.write_str("proxy-v1 [ ")?;
243 for ip in trusted {
244 write!(f, "{ip} ")?;
245 }
246 f.write_str("]")
247 }
248 }
249 }
250}
251
252#[derive(Debug, Deserialize, Default)]
261#[serde(deny_unknown_fields)]
262pub struct ServerConfig {
263 domain: Option<String>,
265 origin: Option<Url>,
267 db_path: Option<PathBuf>,
269 db_fs_type: Option<kanidm_proto::internal::FsType>,
271
272 tls_chain: Option<PathBuf>,
274 tls_key: Option<PathBuf>,
276
277 tls_client_ca: Option<PathBuf>,
279
280 bindaddress: Option<String>,
284 ldapbindaddress: Option<String>,
290 role: Option<ServerRole>,
292 log_level: Option<LogLevel>,
294
295 online_backup: Option<OnlineBackup>,
297
298 trust_x_forward_for: Option<bool>,
300
301 adminbindpath: Option<String>,
303
304 thread_count: Option<usize>,
307
308 maximum_request_size_bytes: Option<usize>,
310
311 #[allow(dead_code)]
313 db_arc_size: Option<usize>,
314 #[serde(default)]
315 #[serde(rename = "replication")]
316 repl_config: Option<ReplicationConfiguration>,
318 otel_grpc_url: Option<String>,
320}
321
322impl ServerConfigUntagged {
323 pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, std::io::Error> {
325 let mut f: File = File::open(config_path.as_ref()).inspect_err(|e| {
327 eprintln!("Unable to open config file [{e:?}] 🥺");
328 let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
329 eprintln!("{diag}");
330 })?;
331
332 let mut contents = String::new();
333
334 f.read_to_string(&mut contents).inspect_err(|e| {
335 eprintln!("unable to read contents {e:?}");
336 let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
337 eprintln!("{diag}");
338 })?;
339
340 let config_version = toml::from_str::<VersionDetection>(contents.as_str())
342 .map(|vd| vd.version)
343 .map_err(|err| {
344 eprintln!(
345 "Unable to parse config version from '{:?}': {:?}",
346 config_path.as_ref(),
347 err
348 );
349 std::io::Error::new(std::io::ErrorKind::InvalidData, err)
350 })?;
351
352 match config_version {
353 Version::V2 => toml::from_str::<ServerConfigV2>(contents.as_str())
354 .map(|values| ServerConfigUntagged::Version(ServerConfigVersion::V2 { values })),
355 Version::Legacy => {
356 toml::from_str::<ServerConfig>(contents.as_str()).map(ServerConfigUntagged::Legacy)
357 }
358 }
359 .map_err(|err| {
360 eprintln!(
361 "Unable to parse config from '{:?}': {:?}",
362 config_path.as_ref(),
363 err
364 );
365 std::io::Error::new(std::io::ErrorKind::InvalidData, err)
366 })
367 }
368}
369
370#[serde_as]
371#[derive(Debug, Deserialize, Default)]
372#[serde(deny_unknown_fields)]
373pub struct ServerConfigV2 {
374 #[allow(dead_code)]
375 version: String,
376 domain: Option<String>,
377 origin: Option<Url>,
378 db_path: Option<PathBuf>,
379 db_fs_type: Option<kanidm_proto::internal::FsType>,
380 tls_chain: Option<PathBuf>,
381 tls_key: Option<PathBuf>,
382 tls_client_ca: Option<PathBuf>,
383
384 migration_path: Option<PathBuf>,
385
386 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
387 bindaddress: Option<Vec<String>>,
388 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
389 ldapbindaddress: Option<Vec<String>>,
390
391 role: Option<ServerRole>,
392 log_level: Option<LogLevel>,
393 online_backup: Option<OnlineBackup>,
394
395 http_client_address_info: Option<HttpAddressInfo>,
396 ldap_client_address_info: Option<LdapAddressInfo>,
397
398 adminbindpath: Option<String>,
399 thread_count: Option<usize>,
400 maximum_request_size_bytes: Option<usize>,
401 #[allow(dead_code)]
402 db_arc_size: Option<usize>,
403 #[serde(default)]
404 #[serde(rename = "replication")]
405 repl_config: Option<ReplicationConfiguration>,
406 otel_grpc_url: Option<String>,
407}
408
409#[derive(Debug, Clone)]
410pub struct IntegrationTestConfig {
411 pub admin_user: String,
412 pub admin_password: String,
413 pub idm_admin_user: String,
414 pub idm_admin_password: String,
415}
416
417#[derive(Debug, Clone)]
418pub struct IntegrationReplConfig {
419 }
425
426#[derive(Debug, Clone)]
428pub struct Configuration {
429 pub address: Vec<String>,
430 pub ldapbindaddress: Option<Vec<String>>,
431 pub adminbindpath: String,
432 pub threads: usize,
433 pub db_path: Option<PathBuf>,
435 pub db_fs_type: Option<FsType>,
436 pub db_arc_size: Option<usize>,
437 pub maximum_request: usize,
438
439 pub migration_path: Option<PathBuf>,
440
441 pub http_client_address_info: HttpAddressInfo,
442 pub ldap_client_address_info: LdapAddressInfo,
443
444 pub tls_config: Option<TlsConfiguration>,
445 pub integration_test_config: Option<Box<IntegrationTestConfig>>,
446 pub online_backup: Option<OnlineBackup>,
447 pub domain: String,
448 pub origin: Url,
449 pub role: ServerRole,
450 pub log_level: LogLevel,
451 pub repl_config: Option<ReplicationConfiguration>,
453 pub integration_repl_config: Option<Box<IntegrationReplConfig>>,
455 pub otel_grpc_url: Option<String>,
456}
457
458impl Configuration {
459 pub fn build() -> ConfigurationBuilder {
460 ConfigurationBuilder {
461 bindaddress: None,
462 ldapbindaddress: None,
463 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
465 threads: std::thread::available_parallelism()
466 .map(|t| t.get())
467 .unwrap_or_else(|_e| {
468 eprintln!("WARNING: Unable to read number of available CPUs, defaulting to 4");
469 4
470 }),
471 db_path: None,
472 db_fs_type: None,
473 db_arc_size: None,
474 migration_path: None,
475 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
477 ldap_client_address_info: LdapAddressInfo::default(),
478 tls_key: None,
479 tls_chain: None,
480 tls_client_ca: None,
481 online_backup: None,
482 domain: None,
483 origin: None,
484 log_level: None,
485 role: None,
486 repl_config: None,
487 otel_grpc_url: None,
488 }
489 }
490
491 pub fn new_for_test() -> Self {
492 #[allow(clippy::expect_used)]
493 Configuration {
494 address: vec![DEFAULT_SERVER_ADDRESS.to_string()],
495 ldapbindaddress: None,
496 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
497 threads: 1,
498 db_path: None,
499 db_fs_type: None,
500 db_arc_size: None,
501 migration_path: None,
502 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
504 ldap_client_address_info: LdapAddressInfo::default(),
505 tls_config: None,
506 integration_test_config: None,
507 online_backup: None,
508 domain: "idm.example.com".to_string(),
509 origin: Url::from_str("https://idm.example.com")
510 .expect("Failed to parse built-in string as URL"),
511 log_level: LogLevel::default(),
512 role: ServerRole::WriteReplica,
513 repl_config: None,
514 integration_repl_config: None,
515 otel_grpc_url: None,
516 }
517 }
518}
519
520impl fmt::Display for Configuration {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 for a in &self.address {
523 write!(f, "address: {a}, ")?;
524 }
525 write!(f, "domain: {}, ", self.domain)?;
526 match &self.ldapbindaddress {
527 Some(las) => {
528 for la in las {
529 write!(f, "ldap address: {la}, ")?;
530 }
531 }
532 None => write!(f, "ldap address: disabled, ")?,
533 };
534 write!(f, "origin: {} ", self.origin)?;
535 write!(f, "admin bind path: {}, ", self.adminbindpath)?;
536 write!(f, "thread count: {}, ", self.threads)?;
537 write!(
538 f,
539 "dbpath: {}, ",
540 self.db_path
541 .as_ref()
542 .map(|p| p.to_string_lossy().to_string())
543 .unwrap_or("MEMORY".to_string())
544 )?;
545 match self.db_arc_size {
546 Some(v) => write!(f, "arcsize: {v}, "),
547 None => write!(f, "arcsize: AUTO, "),
548 }?;
549 write!(f, "max request size: {}b, ", self.maximum_request)?;
550 write!(
551 f,
552 "http client address info: {}, ",
553 self.http_client_address_info
554 )?;
555 write!(
556 f,
557 "ldap client address info: {}, ",
558 self.ldap_client_address_info
559 )?;
560
561 write!(f, "with TLS: {}, ", self.tls_config.is_some())?;
562 match &self.online_backup {
563 Some(bck) => write!(
564 f,
565 "online_backup: enabled: {} - schedule: {} versions: {} path: {}, ",
566 bck.enabled,
567 bck.schedule,
568 bck.versions,
569 bck.path
570 .as_ref()
571 .map(|p| p.to_string_lossy().to_string())
572 .unwrap_or("<unset>".to_string())
573 ),
574 None => write!(f, "online_backup: disabled, "),
575 }?;
576 write!(
577 f,
578 "integration mode: {}, ",
579 self.integration_test_config.is_some()
580 )?;
581 write!(f, "log_level: {}", self.log_level)?;
582 write!(f, "role: {}, ", self.role)?;
583 match &self.repl_config {
584 Some(repl) => {
585 write!(f, "replication: enabled")?;
586 write!(f, "repl_origin: {} ", repl.origin)?;
587 write!(f, "repl_address: {} ", repl.bindaddress)?;
588 write!(
589 f,
590 "integration repl config mode: {}, ",
591 self.integration_repl_config.is_some()
592 )?;
593 }
594 None => {
595 write!(f, "replication: disabled, ")?;
596 }
597 }
598 write!(f, "otel_grpc_url: {:?}", self.otel_grpc_url)?;
599 Ok(())
600 }
601}
602
603#[derive(Debug, Clone)]
605pub struct ConfigurationBuilder {
606 bindaddress: Option<Vec<String>>,
607 ldapbindaddress: Option<Vec<String>>,
608 adminbindpath: String,
609 threads: usize,
610 db_path: Option<PathBuf>,
611 db_fs_type: Option<FsType>,
612 db_arc_size: Option<usize>,
613 migration_path: Option<PathBuf>,
614 maximum_request: usize,
615 http_client_address_info: HttpAddressInfo,
616 ldap_client_address_info: LdapAddressInfo,
617 tls_key: Option<PathBuf>,
618 tls_chain: Option<PathBuf>,
619 tls_client_ca: Option<PathBuf>,
620 online_backup: Option<OnlineBackup>,
621 domain: Option<String>,
622 origin: Option<Url>,
623 role: Option<ServerRole>,
624 log_level: Option<LogLevel>,
625 repl_config: Option<ReplicationConfiguration>,
626 otel_grpc_url: Option<String>,
627}
628
629impl ConfigurationBuilder {
630 #![allow(clippy::needless_pass_by_value)]
631 pub fn add_cli_config(mut self, cli_config: &kanidm_proto::cli::KanidmdCli) -> Self {
632 if let Some(log_level) = &cli_config.log_level {
634 self.log_level = Some(*log_level);
635 }
636
637 if let Some(otel_grpc_url) = &cli_config.otel_grpc_url {
638 self.otel_grpc_url = Some(otel_grpc_url.clone());
639 }
640
641 if let Some(domain) = &cli_config.domain {
643 self.domain = Some(domain.clone());
644 }
645
646 if let Some(origin) = &cli_config.origin {
647 self.origin = Some(origin.clone());
648 }
649
650 if let Some(role) = &cli_config.role {
651 self.role = Some(*role);
652 }
653
654 if cli_config.bindaddress.is_some() {
657 self.bindaddress = cli_config
658 .bindaddress
659 .clone()
660 .map(|s| s.split(',').map(|s| s.to_string()).collect())
661 }
662
663 if cli_config.ldapbindaddress.is_some() {
664 self.ldapbindaddress = cli_config
665 .ldapbindaddress
666 .clone()
667 .map(|s| s.split(',').map(|s| s.to_string()).collect())
668 }
669
670 if let Some(repl_origin) = cli_config.replication_origin.clone() {
673 if let Some(repl) = &mut self.repl_config {
674 repl.origin = repl_origin
675 } else {
676 self.repl_config = Some(ReplicationConfiguration {
677 origin: repl_origin,
678 ..Default::default()
679 });
680 }
681 }
682
683 if let Some(replication_bindaddress) = &cli_config.replication_bindaddress {
684 if let Some(repl_config) = &mut self.repl_config {
685 repl_config.bindaddress = *replication_bindaddress;
686 } else {
687 self.repl_config = Some(ReplicationConfiguration {
688 bindaddress: *replication_bindaddress,
689 ..Default::default()
690 });
691 }
692 }
693
694 if let Some(task_poll_interval) = cli_config.replication_task_poll_interval {
695 if let Some(repl) = &mut self.repl_config {
696 repl.task_poll_interval = Some(task_poll_interval);
697 } else {
698 self.repl_config = Some(ReplicationConfiguration {
699 task_poll_interval: Some(task_poll_interval),
700 ..Default::default()
701 });
702 }
703 }
704
705 if let Some(tls_key) = &cli_config.tls_key {
708 self.tls_key = Some(tls_key.clone());
709 }
710
711 if let Some(tls_chain) = &cli_config.tls_chain {
712 self.tls_chain = Some(tls_chain.clone());
713 }
714
715 if let Some(tls_client_ca) = &cli_config.tls_client_ca {
716 self.tls_client_ca = Some(tls_client_ca.clone());
717 }
718
719 if let Some(adminbindpath) = &cli_config.admin_bind_path {
721 self.adminbindpath = adminbindpath.clone();
722 }
723
724 if let Some(db_path) = &cli_config.db_path {
725 self.db_path = Some(db_path.clone());
726 }
727
728 if let Some(db_fs_type) = &cli_config.db_fs_type {
729 self.db_fs_type = Some(*db_fs_type);
730 }
731
732 if cli_config.db_arc_size.is_some() {
733 self.db_arc_size = cli_config.db_arc_size;
734 }
735
736 if let Some(online_backup_path) = &cli_config.online_backup_path {
738 if let Some(backup) = &mut self.online_backup {
739 backup.path = Some(online_backup_path.clone());
740 } else {
741 self.online_backup = Some(OnlineBackup {
742 path: Some(online_backup_path.clone()),
743 ..Default::default()
744 });
745 }
746 }
747
748 if let Some(online_backup_schedule) = &cli_config.online_backup_schedule {
749 if let Some(backup) = &mut self.online_backup {
750 backup.schedule = online_backup_schedule.clone();
751 } else {
752 self.online_backup = Some(OnlineBackup {
753 schedule: online_backup_schedule.clone(),
754 ..Default::default()
755 });
756 }
757 }
758
759 if let Some(online_backup_versions) = &cli_config.online_backup_versions {
760 if let Some(backup) = &mut self.online_backup {
761 backup.versions = *online_backup_versions;
762 } else {
763 self.online_backup = Some(OnlineBackup {
764 versions: *online_backup_versions,
765 ..Default::default()
766 });
767 }
768 }
769
770 if let Some(true) = cli_config.trust_all_x_forwarded_for {
772 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
773 }
774
775 self
776 }
777
778 pub fn add_opt_toml_config(self, toml_config: Option<ServerConfigUntagged>) -> Self {
779 let Some(toml_config) = toml_config else {
781 return self;
782 };
783
784 match toml_config {
785 ServerConfigUntagged::Version(ServerConfigVersion::V2 { values }) => {
786 self.add_v2_config(values)
787 }
788 ServerConfigUntagged::Legacy(config) => self.add_legacy_config(config),
789 }
790 }
791
792 fn add_legacy_config(mut self, config: ServerConfig) -> Self {
793 if config.domain.is_some() {
794 self.domain = config.domain;
795 }
796
797 if config.origin.is_some() {
798 self.origin = config.origin;
799 }
800
801 if config.db_path.is_some() {
802 self.db_path = config.db_path;
803 }
804
805 if config.db_fs_type.is_some() {
806 self.db_fs_type = config.db_fs_type;
807 }
808
809 if config.tls_key.is_some() {
810 self.tls_key = config.tls_key;
811 }
812
813 if config.tls_chain.is_some() {
814 self.tls_chain = config.tls_chain;
815 }
816
817 if config.tls_client_ca.is_some() {
818 self.tls_client_ca = config.tls_client_ca;
819 }
820
821 if config.bindaddress.is_some() {
822 self.bindaddress = config.bindaddress.map(|a| vec![a]);
823 }
824
825 if config.ldapbindaddress.is_some() {
826 self.ldapbindaddress = config.ldapbindaddress.map(|a| vec![a]);
827 }
828
829 if let Some(adminbindpath) = config.adminbindpath {
830 self.adminbindpath = adminbindpath;
831 }
832
833 if config.role.is_some() {
834 self.role = config.role;
835 }
836
837 if config.log_level.is_some() {
838 self.log_level = config.log_level;
839 }
840
841 if let Some(threads) = config.thread_count {
842 self.threads = threads;
843 }
844
845 if let Some(maximum) = config.maximum_request_size_bytes {
846 self.maximum_request = maximum;
847 }
848
849 if config.db_arc_size.is_some() {
850 self.db_arc_size = config.db_arc_size;
851 }
852
853 if config.trust_x_forward_for == Some(true) {
854 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
855 }
856
857 if config.online_backup.is_some() {
858 self.online_backup = config.online_backup;
859 }
860
861 if config.repl_config.is_some() {
862 self.repl_config = config.repl_config;
863 }
864
865 if config.otel_grpc_url.is_some() {
866 self.otel_grpc_url = config.otel_grpc_url;
867 }
868
869 self
870 }
871
872 fn add_v2_config(mut self, config: ServerConfigV2) -> Self {
873 if config.domain.is_some() {
874 self.domain = config.domain;
875 }
876
877 if config.origin.is_some() {
878 self.origin = config.origin;
879 }
880
881 if config.db_path.is_some() {
882 self.db_path = config.db_path;
883 }
884
885 if config.migration_path.is_some() {
886 self.migration_path = config.migration_path;
887 }
888
889 if config.db_fs_type.is_some() {
890 self.db_fs_type = config.db_fs_type;
891 }
892
893 if config.tls_key.is_some() {
894 self.tls_key = config.tls_key;
895 }
896
897 if config.tls_chain.is_some() {
898 self.tls_chain = config.tls_chain;
899 }
900
901 if config.tls_client_ca.is_some() {
902 self.tls_client_ca = config.tls_client_ca;
903 }
904
905 if config.bindaddress.is_some() {
906 self.bindaddress = config.bindaddress;
907 }
908
909 if config.ldapbindaddress.is_some() {
910 self.ldapbindaddress = config.ldapbindaddress;
911 }
912
913 if let Some(adminbindpath) = config.adminbindpath {
914 self.adminbindpath = adminbindpath;
915 }
916
917 if config.role.is_some() {
918 self.role = config.role;
919 }
920
921 if config.log_level.is_some() {
922 self.log_level = config.log_level;
923 }
924
925 if let Some(threads) = config.thread_count {
926 self.threads = threads;
927 }
928
929 if let Some(maximum) = config.maximum_request_size_bytes {
930 self.maximum_request = maximum;
931 }
932
933 if config.db_arc_size.is_some() {
934 self.db_arc_size = config.db_arc_size;
935 }
936
937 if let Some(http_client_address_info) = config.http_client_address_info {
938 self.http_client_address_info = http_client_address_info
939 }
940
941 if let Some(ldap_client_address_info) = config.ldap_client_address_info {
942 self.ldap_client_address_info = ldap_client_address_info
943 }
944
945 if config.online_backup.is_some() {
946 self.online_backup = config.online_backup;
947 }
948
949 if config.repl_config.is_some() {
950 self.repl_config = config.repl_config;
951 }
952
953 if config.otel_grpc_url.is_some() {
954 self.otel_grpc_url = config.otel_grpc_url;
955 }
956
957 self
958 }
959
960 pub fn is_server_mode(mut self, is_server: bool) -> Self {
962 if is_server {
963 self.threads = 1;
964 }
965 self
966 }
967
968 pub fn finish(self) -> Option<Configuration> {
969 let ConfigurationBuilder {
970 bindaddress,
971 ldapbindaddress,
972 adminbindpath,
973 threads,
974 db_path,
975 db_fs_type,
976 db_arc_size,
977 migration_path,
978 maximum_request,
979 http_client_address_info,
980 ldap_client_address_info,
981 tls_key,
982 tls_chain,
983 tls_client_ca,
984 mut online_backup,
985 domain,
986 origin,
987 role,
988 log_level,
989 repl_config,
990 otel_grpc_url,
991 } = self;
992
993 let tls_config = match (tls_key, tls_chain, tls_client_ca) {
994 (Some(key), Some(chain), client_ca) => Some(TlsConfiguration {
995 chain,
996 key,
997 client_ca,
998 }),
999 _ => {
1000 eprintln!("ERROR: Tls Private Key and Certificate Chain are required.");
1001 return None;
1002 }
1003 };
1004
1005 let domain = domain.or_else(|| {
1006 eprintln!("ERROR: domain was not set.");
1007 None
1008 })?;
1009
1010 let origin = origin.or_else(|| {
1011 eprintln!("ERROR: origin was not set.");
1012 None
1013 })?;
1014
1015 if let Some(online_backup_ref) = online_backup.as_mut() {
1016 if online_backup_ref.path.is_none() {
1017 if let Some(db_path) = db_path.as_ref() {
1018 if let Some(db_parent_path) = db_path.parent() {
1019 online_backup_ref.path = Some(db_parent_path.to_path_buf());
1020 } else {
1021 eprintln!("ERROR: when db_path has no parent, and can not be used for online backups.");
1022 return None;
1023 }
1024 } else {
1025 eprintln!("ERROR: when db_path is unset (in memory) then online backup paths must be declared.");
1026 return None;
1027 }
1028 }
1029 };
1030
1031 let address = bindaddress.unwrap_or(vec![DEFAULT_SERVER_ADDRESS.to_string()]);
1033 let role = role.unwrap_or(ServerRole::WriteReplica);
1034 let log_level = log_level.unwrap_or_default();
1035
1036 Some(Configuration {
1037 address,
1038 ldapbindaddress,
1039 adminbindpath,
1040 threads,
1041 db_path,
1042 db_fs_type,
1043 db_arc_size,
1044 migration_path,
1045 maximum_request,
1046 http_client_address_info,
1047 ldap_client_address_info,
1048 tls_config,
1049 online_backup,
1050 domain,
1051 origin,
1052 role,
1053 log_level,
1054 repl_config,
1055 otel_grpc_url,
1056 integration_repl_config: None,
1057 integration_test_config: None,
1058 })
1059 }
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr};
1065 use std::net::{Ipv4Addr, Ipv6Addr};
1066
1067 #[test]
1068 fn assert_cidr_parsing_behaviour() {
1069 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.1\"").unwrap();
1071 let expect_ip_cidr = IpCidr::from(Ipv4Addr::new(127, 0, 0, 1));
1072 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1073
1074 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.0/8\"").unwrap();
1075 let expect_ip_cidr = IpCidr::from(Ipv4Cidr::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap());
1076 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1077
1078 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::1\"").unwrap();
1080 let expect_ip_cidr = IpCidr::from(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0001));
1081 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1082
1083 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::/64\"").unwrap();
1084 let expect_ip_cidr = IpCidr::from(
1085 Ipv6Cidr::new(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0), 64).unwrap(),
1086 );
1087 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1088 }
1089}