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 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
385 bindaddress: Option<Vec<String>>,
386 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
387 ldapbindaddress: Option<Vec<String>>,
388
389 role: Option<ServerRole>,
390 log_level: Option<LogLevel>,
391 online_backup: Option<OnlineBackup>,
392
393 http_client_address_info: Option<HttpAddressInfo>,
394 ldap_client_address_info: Option<LdapAddressInfo>,
395
396 adminbindpath: Option<String>,
397 thread_count: Option<usize>,
398 maximum_request_size_bytes: Option<usize>,
399 #[allow(dead_code)]
400 db_arc_size: Option<usize>,
401 #[serde(default)]
402 #[serde(rename = "replication")]
403 repl_config: Option<ReplicationConfiguration>,
404 otel_grpc_url: Option<String>,
405}
406
407#[derive(Debug, Clone)]
408pub struct IntegrationTestConfig {
409 pub admin_user: String,
410 pub admin_password: String,
411 pub idm_admin_user: String,
412 pub idm_admin_password: String,
413}
414
415#[derive(Debug, Clone)]
416pub struct IntegrationReplConfig {
417 }
423
424#[derive(Debug, Clone)]
426pub struct Configuration {
427 pub address: Vec<String>,
428 pub ldapbindaddress: Option<Vec<String>>,
429 pub adminbindpath: String,
430 pub threads: usize,
431 pub db_path: Option<PathBuf>,
433 pub db_fs_type: Option<FsType>,
434 pub db_arc_size: Option<usize>,
435 pub maximum_request: usize,
436
437 pub http_client_address_info: HttpAddressInfo,
438 pub ldap_client_address_info: LdapAddressInfo,
439
440 pub tls_config: Option<TlsConfiguration>,
441 pub integration_test_config: Option<Box<IntegrationTestConfig>>,
442 pub online_backup: Option<OnlineBackup>,
443 pub domain: String,
444 pub origin: Url,
445 pub role: ServerRole,
446 pub log_level: LogLevel,
447 pub repl_config: Option<ReplicationConfiguration>,
449 pub integration_repl_config: Option<Box<IntegrationReplConfig>>,
451 pub otel_grpc_url: Option<String>,
452}
453
454impl Configuration {
455 pub fn build() -> ConfigurationBuilder {
456 ConfigurationBuilder {
457 bindaddress: None,
458 ldapbindaddress: None,
459 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
461 threads: std::thread::available_parallelism()
462 .map(|t| t.get())
463 .unwrap_or_else(|_e| {
464 eprintln!("WARNING: Unable to read number of available CPUs, defaulting to 4");
465 4
466 }),
467 db_path: None,
468 db_fs_type: None,
469 db_arc_size: None,
470 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
472 ldap_client_address_info: LdapAddressInfo::default(),
473 tls_key: None,
474 tls_chain: None,
475 tls_client_ca: None,
476 online_backup: None,
477 domain: None,
478 origin: None,
479 log_level: None,
480 role: None,
481 repl_config: None,
482 otel_grpc_url: None,
483 }
484 }
485
486 pub fn new_for_test() -> Self {
487 #[allow(clippy::expect_used)]
488 Configuration {
489 address: vec![DEFAULT_SERVER_ADDRESS.to_string()],
490 ldapbindaddress: None,
491 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
492 threads: 1,
493 db_path: None,
494 db_fs_type: None,
495 db_arc_size: None,
496 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
498 ldap_client_address_info: LdapAddressInfo::default(),
499 tls_config: None,
500 integration_test_config: None,
501 online_backup: None,
502 domain: "idm.example.com".to_string(),
503 origin: Url::from_str("https://idm.example.com")
504 .expect("Failed to parse built-in string as URL"),
505 log_level: LogLevel::default(),
506 role: ServerRole::WriteReplica,
507 repl_config: None,
508 integration_repl_config: None,
509 otel_grpc_url: None,
510 }
511 }
512}
513
514impl fmt::Display for Configuration {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 for a in &self.address {
517 write!(f, "address: {a}, ")?;
518 }
519 write!(f, "domain: {}, ", self.domain)?;
520 match &self.ldapbindaddress {
521 Some(las) => {
522 for la in las {
523 write!(f, "ldap address: {la}, ")?;
524 }
525 }
526 None => write!(f, "ldap address: disabled, ")?,
527 };
528 write!(f, "origin: {} ", self.origin)?;
529 write!(f, "admin bind path: {}, ", self.adminbindpath)?;
530 write!(f, "thread count: {}, ", self.threads)?;
531 write!(
532 f,
533 "dbpath: {}, ",
534 self.db_path
535 .as_ref()
536 .map(|p| p.to_string_lossy().to_string())
537 .unwrap_or("MEMORY".to_string())
538 )?;
539 match self.db_arc_size {
540 Some(v) => write!(f, "arcsize: {v}, "),
541 None => write!(f, "arcsize: AUTO, "),
542 }?;
543 write!(f, "max request size: {}b, ", self.maximum_request)?;
544 write!(
545 f,
546 "http client address info: {}, ",
547 self.http_client_address_info
548 )?;
549 write!(
550 f,
551 "ldap client address info: {}, ",
552 self.ldap_client_address_info
553 )?;
554
555 write!(f, "with TLS: {}, ", self.tls_config.is_some())?;
556 match &self.online_backup {
557 Some(bck) => write!(
558 f,
559 "online_backup: enabled: {} - schedule: {} versions: {} path: {}, ",
560 bck.enabled,
561 bck.schedule,
562 bck.versions,
563 bck.path
564 .as_ref()
565 .map(|p| p.to_string_lossy().to_string())
566 .unwrap_or("<unset>".to_string())
567 ),
568 None => write!(f, "online_backup: disabled, "),
569 }?;
570 write!(
571 f,
572 "integration mode: {}, ",
573 self.integration_test_config.is_some()
574 )?;
575 write!(f, "log_level: {}", self.log_level)?;
576 write!(f, "role: {}, ", self.role)?;
577 match &self.repl_config {
578 Some(repl) => {
579 write!(f, "replication: enabled")?;
580 write!(f, "repl_origin: {} ", repl.origin)?;
581 write!(f, "repl_address: {} ", repl.bindaddress)?;
582 write!(
583 f,
584 "integration repl config mode: {}, ",
585 self.integration_repl_config.is_some()
586 )?;
587 }
588 None => {
589 write!(f, "replication: disabled, ")?;
590 }
591 }
592 write!(f, "otel_grpc_url: {:?}", self.otel_grpc_url)?;
593 Ok(())
594 }
595}
596
597#[derive(Debug, Clone)]
599pub struct ConfigurationBuilder {
600 bindaddress: Option<Vec<String>>,
601 ldapbindaddress: Option<Vec<String>>,
602 adminbindpath: String,
603 threads: usize,
604 db_path: Option<PathBuf>,
605 db_fs_type: Option<FsType>,
606 db_arc_size: Option<usize>,
607 maximum_request: usize,
608 http_client_address_info: HttpAddressInfo,
609 ldap_client_address_info: LdapAddressInfo,
610 tls_key: Option<PathBuf>,
611 tls_chain: Option<PathBuf>,
612 tls_client_ca: Option<PathBuf>,
613 online_backup: Option<OnlineBackup>,
614 domain: Option<String>,
615 origin: Option<Url>,
616 role: Option<ServerRole>,
617 log_level: Option<LogLevel>,
618 repl_config: Option<ReplicationConfiguration>,
619 otel_grpc_url: Option<String>,
620}
621
622impl ConfigurationBuilder {
623 #![allow(clippy::needless_pass_by_value)]
624 pub fn add_cli_config(mut self, cli_config: &kanidm_proto::cli::KanidmdCli) -> Self {
625 if let Some(log_level) = &cli_config.log_level {
627 self.log_level = Some(*log_level);
628 }
629
630 if let Some(otel_grpc_url) = &cli_config.otel_grpc_url {
631 self.otel_grpc_url = Some(otel_grpc_url.clone());
632 }
633
634 if let Some(domain) = &cli_config.domain {
636 self.domain = Some(domain.clone());
637 }
638
639 if let Some(origin) = &cli_config.origin {
640 self.origin = Some(origin.clone());
641 }
642
643 if let Some(role) = &cli_config.role {
644 self.role = Some(*role);
645 }
646
647 if cli_config.bindaddress.is_some() {
650 self.bindaddress = cli_config
651 .bindaddress
652 .clone()
653 .map(|s| s.split(',').map(|s| s.to_string()).collect())
654 }
655
656 if cli_config.ldapbindaddress.is_some() {
657 self.ldapbindaddress = cli_config
658 .ldapbindaddress
659 .clone()
660 .map(|s| s.split(',').map(|s| s.to_string()).collect())
661 }
662
663 if let Some(repl_origin) = cli_config.replication_origin.clone() {
666 if let Some(repl) = &mut self.repl_config {
667 repl.origin = repl_origin
668 } else {
669 self.repl_config = Some(ReplicationConfiguration {
670 origin: repl_origin,
671 ..Default::default()
672 });
673 }
674 }
675
676 if let Some(replication_bindaddress) = &cli_config.replication_bindaddress {
677 if let Some(repl_config) = &mut self.repl_config {
678 repl_config.bindaddress = *replication_bindaddress;
679 } else {
680 self.repl_config = Some(ReplicationConfiguration {
681 bindaddress: *replication_bindaddress,
682 ..Default::default()
683 });
684 }
685 }
686
687 if let Some(task_poll_interval) = cli_config.replication_task_poll_interval {
688 if let Some(repl) = &mut self.repl_config {
689 repl.task_poll_interval = Some(task_poll_interval);
690 } else {
691 self.repl_config = Some(ReplicationConfiguration {
692 task_poll_interval: Some(task_poll_interval),
693 ..Default::default()
694 });
695 }
696 }
697
698 if let Some(tls_key) = &cli_config.tls_key {
701 self.tls_key = Some(tls_key.clone());
702 }
703
704 if let Some(tls_chain) = &cli_config.tls_chain {
705 self.tls_chain = Some(tls_chain.clone());
706 }
707
708 if let Some(tls_client_ca) = &cli_config.tls_client_ca {
709 self.tls_client_ca = Some(tls_client_ca.clone());
710 }
711
712 if let Some(adminbindpath) = &cli_config.admin_bind_path {
714 self.adminbindpath = adminbindpath.clone();
715 }
716
717 if let Some(db_path) = &cli_config.db_path {
718 self.db_path = Some(db_path.clone());
719 }
720
721 if let Some(db_fs_type) = &cli_config.db_fs_type {
722 self.db_fs_type = Some(*db_fs_type);
723 }
724
725 if cli_config.db_arc_size.is_some() {
726 self.db_arc_size = cli_config.db_arc_size;
727 }
728
729 if let Some(online_backup_path) = &cli_config.online_backup_path {
731 if let Some(backup) = &mut self.online_backup {
732 backup.path = Some(online_backup_path.clone());
733 } else {
734 self.online_backup = Some(OnlineBackup {
735 path: Some(online_backup_path.clone()),
736 ..Default::default()
737 });
738 }
739 }
740
741 if let Some(online_backup_schedule) = &cli_config.online_backup_schedule {
742 if let Some(backup) = &mut self.online_backup {
743 backup.schedule = online_backup_schedule.clone();
744 } else {
745 self.online_backup = Some(OnlineBackup {
746 schedule: online_backup_schedule.clone(),
747 ..Default::default()
748 });
749 }
750 }
751
752 if let Some(online_backup_versions) = &cli_config.online_backup_versions {
753 if let Some(backup) = &mut self.online_backup {
754 backup.versions = *online_backup_versions;
755 } else {
756 self.online_backup = Some(OnlineBackup {
757 versions: *online_backup_versions,
758 ..Default::default()
759 });
760 }
761 }
762
763 if let Some(true) = cli_config.trust_all_x_forwarded_for {
765 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
766 }
767
768 self
769 }
770
771 pub fn add_opt_toml_config(self, toml_config: Option<ServerConfigUntagged>) -> Self {
772 let Some(toml_config) = toml_config else {
774 return self;
775 };
776
777 match toml_config {
778 ServerConfigUntagged::Version(ServerConfigVersion::V2 { values }) => {
779 self.add_v2_config(values)
780 }
781 ServerConfigUntagged::Legacy(config) => self.add_legacy_config(config),
782 }
783 }
784
785 fn add_legacy_config(mut self, config: ServerConfig) -> Self {
786 if config.domain.is_some() {
787 self.domain = config.domain;
788 }
789
790 if config.origin.is_some() {
791 self.origin = config.origin;
792 }
793
794 if config.db_path.is_some() {
795 self.db_path = config.db_path;
796 }
797
798 if config.db_fs_type.is_some() {
799 self.db_fs_type = config.db_fs_type;
800 }
801
802 if config.tls_key.is_some() {
803 self.tls_key = config.tls_key;
804 }
805
806 if config.tls_chain.is_some() {
807 self.tls_chain = config.tls_chain;
808 }
809
810 if config.tls_client_ca.is_some() {
811 self.tls_client_ca = config.tls_client_ca;
812 }
813
814 if config.bindaddress.is_some() {
815 self.bindaddress = config.bindaddress.map(|a| vec![a]);
816 }
817
818 if config.ldapbindaddress.is_some() {
819 self.ldapbindaddress = config.ldapbindaddress.map(|a| vec![a]);
820 }
821
822 if let Some(adminbindpath) = config.adminbindpath {
823 self.adminbindpath = adminbindpath;
824 }
825
826 if config.role.is_some() {
827 self.role = config.role;
828 }
829
830 if config.log_level.is_some() {
831 self.log_level = config.log_level;
832 }
833
834 if let Some(threads) = config.thread_count {
835 self.threads = threads;
836 }
837
838 if let Some(maximum) = config.maximum_request_size_bytes {
839 self.maximum_request = maximum;
840 }
841
842 if config.db_arc_size.is_some() {
843 self.db_arc_size = config.db_arc_size;
844 }
845
846 if config.trust_x_forward_for == Some(true) {
847 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
848 }
849
850 if config.online_backup.is_some() {
851 self.online_backup = config.online_backup;
852 }
853
854 if config.repl_config.is_some() {
855 self.repl_config = config.repl_config;
856 }
857
858 if config.otel_grpc_url.is_some() {
859 self.otel_grpc_url = config.otel_grpc_url;
860 }
861
862 self
863 }
864
865 fn add_v2_config(mut self, config: ServerConfigV2) -> Self {
866 if config.domain.is_some() {
867 self.domain = config.domain;
868 }
869
870 if config.origin.is_some() {
871 self.origin = config.origin;
872 }
873
874 if config.db_path.is_some() {
875 self.db_path = config.db_path;
876 }
877
878 if config.db_fs_type.is_some() {
879 self.db_fs_type = config.db_fs_type;
880 }
881
882 if config.tls_key.is_some() {
883 self.tls_key = config.tls_key;
884 }
885
886 if config.tls_chain.is_some() {
887 self.tls_chain = config.tls_chain;
888 }
889
890 if config.tls_client_ca.is_some() {
891 self.tls_client_ca = config.tls_client_ca;
892 }
893
894 if config.bindaddress.is_some() {
895 self.bindaddress = config.bindaddress;
896 }
897
898 if config.ldapbindaddress.is_some() {
899 self.ldapbindaddress = config.ldapbindaddress;
900 }
901
902 if let Some(adminbindpath) = config.adminbindpath {
903 self.adminbindpath = adminbindpath;
904 }
905
906 if config.role.is_some() {
907 self.role = config.role;
908 }
909
910 if config.log_level.is_some() {
911 self.log_level = config.log_level;
912 }
913
914 if let Some(threads) = config.thread_count {
915 self.threads = threads;
916 }
917
918 if let Some(maximum) = config.maximum_request_size_bytes {
919 self.maximum_request = maximum;
920 }
921
922 if config.db_arc_size.is_some() {
923 self.db_arc_size = config.db_arc_size;
924 }
925
926 if let Some(http_client_address_info) = config.http_client_address_info {
927 self.http_client_address_info = http_client_address_info
928 }
929
930 if let Some(ldap_client_address_info) = config.ldap_client_address_info {
931 self.ldap_client_address_info = ldap_client_address_info
932 }
933
934 if config.online_backup.is_some() {
935 self.online_backup = config.online_backup;
936 }
937
938 if config.repl_config.is_some() {
939 self.repl_config = config.repl_config;
940 }
941
942 if config.otel_grpc_url.is_some() {
943 self.otel_grpc_url = config.otel_grpc_url;
944 }
945
946 self
947 }
948
949 pub fn is_server_mode(mut self, is_server: bool) -> Self {
951 if is_server {
952 self.threads = 1;
953 }
954 self
955 }
956
957 pub fn finish(self) -> Option<Configuration> {
958 let ConfigurationBuilder {
959 bindaddress,
960 ldapbindaddress,
961 adminbindpath,
962 threads,
963 db_path,
964 db_fs_type,
965 db_arc_size,
966 maximum_request,
967 http_client_address_info,
968 ldap_client_address_info,
969 tls_key,
970 tls_chain,
971 tls_client_ca,
972 mut online_backup,
973 domain,
974 origin,
975 role,
976 log_level,
977 repl_config,
978 otel_grpc_url,
979 } = self;
980
981 let tls_config = match (tls_key, tls_chain, tls_client_ca) {
982 (Some(key), Some(chain), client_ca) => Some(TlsConfiguration {
983 chain,
984 key,
985 client_ca,
986 }),
987 _ => {
988 eprintln!("ERROR: Tls Private Key and Certificate Chain are required.");
989 return None;
990 }
991 };
992
993 let domain = domain.or_else(|| {
994 eprintln!("ERROR: domain was not set.");
995 None
996 })?;
997
998 let origin = origin.or_else(|| {
999 eprintln!("ERROR: origin was not set.");
1000 None
1001 })?;
1002
1003 if let Some(online_backup_ref) = online_backup.as_mut() {
1004 if online_backup_ref.path.is_none() {
1005 if let Some(db_path) = db_path.as_ref() {
1006 if let Some(db_parent_path) = db_path.parent() {
1007 online_backup_ref.path = Some(db_parent_path.to_path_buf());
1008 } else {
1009 eprintln!("ERROR: when db_path has no parent, and can not be used for online backups.");
1010 return None;
1011 }
1012 } else {
1013 eprintln!("ERROR: when db_path is unset (in memory) then online backup paths must be declared.");
1014 return None;
1015 }
1016 }
1017 };
1018
1019 let address = bindaddress.unwrap_or(vec![DEFAULT_SERVER_ADDRESS.to_string()]);
1021 let role = role.unwrap_or(ServerRole::WriteReplica);
1022 let log_level = log_level.unwrap_or_default();
1023
1024 Some(Configuration {
1025 address,
1026 ldapbindaddress,
1027 adminbindpath,
1028 threads,
1029 db_path,
1030 db_fs_type,
1031 db_arc_size,
1032 maximum_request,
1033 http_client_address_info,
1034 ldap_client_address_info,
1035 tls_config,
1036 online_backup,
1037 domain,
1038 origin,
1039 role,
1040 log_level,
1041 repl_config,
1042 otel_grpc_url,
1043 integration_repl_config: None,
1044 integration_test_config: None,
1045 })
1046 }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051 use cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr};
1052 use std::net::{Ipv4Addr, Ipv6Addr};
1053
1054 #[test]
1055 fn assert_cidr_parsing_behaviour() {
1056 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.1\"").unwrap();
1058 let expect_ip_cidr = IpCidr::from(Ipv4Addr::new(127, 0, 0, 1));
1059 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1060
1061 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.0/8\"").unwrap();
1062 let expect_ip_cidr = IpCidr::from(Ipv4Cidr::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap());
1063 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1064
1065 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::1\"").unwrap();
1067 let expect_ip_cidr = IpCidr::from(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0001));
1068 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1069
1070 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::/64\"").unwrap();
1071 let expect_ip_cidr = IpCidr::from(
1072 Ipv6Cidr::new(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0), 64).unwrap(),
1073 );
1074 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1075 }
1076}