1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! TLS/SSL configuration for **database connections**.
//!
//! Server-side TLS termination is **not supported**: FraiseQL serves plaintext HTTP and
//! expects a reverse proxy (nginx, Caddy, a cloud load balancer, a service mesh) to
//! terminate TLS in front of it. The server **refuses to boot** if `[tls]` (server-side
//! TLS) is enabled — see `server/lifecycle.rs`. Previously a rustls `ServerConfig` was
//! built from `[tls]` and then silently discarded while the server kept serving plaintext
//! (M-tls-enforce), so the dead `TlsEnforcer` / `create_rustls_config` plumbing was removed.
//!
//! This module retains only the **database** connection TLS settings (`postgres_ssl_mode`,
//! `redis_ssl`, etc.) and the URL-rewriting helpers that apply them, plus
//! [`TlsSetup::is_tls_enabled`] used by the boot-time refusal check.
use std::{fmt::Write as _, path::Path};
use crate::server_config::{DatabaseTlsConfig, TlsServerConfig};
/// Database connection TLS settings, plus the server-side `[tls]` config retained only so
/// the boot path can detect (and refuse) an enabled server-TLS configuration.
pub struct TlsSetup {
/// Server TLS configuration (server-side TLS termination is unsupported; this is read
/// only by [`is_tls_enabled`](Self::is_tls_enabled) for the boot-time refusal).
config: Option<TlsServerConfig>,
/// Database TLS configuration.
db_config: Option<DatabaseTlsConfig>,
}
impl TlsSetup {
/// Create new TLS setup from server configuration.
#[must_use]
pub const fn new(
tls_config: Option<TlsServerConfig>,
db_tls_config: Option<DatabaseTlsConfig>,
) -> Self {
Self {
config: tls_config,
db_config: db_tls_config,
}
}
/// Get the database TLS configuration.
#[must_use]
pub const fn db_config(&self) -> &Option<DatabaseTlsConfig> {
&self.db_config
}
/// Whether server-side `[tls]` is enabled in the configuration.
///
/// Server-side TLS termination is unsupported, so the boot path uses this to refuse to
/// start rather than serve plaintext under an enabled `[tls]` config (M-tls-enforce).
#[must_use]
pub fn is_tls_enabled(&self) -> bool {
self.config.as_ref().is_some_and(|c| c.enabled)
}
/// Get PostgreSQL SSL mode for database connections.
#[must_use]
pub fn postgres_ssl_mode(&self) -> &str {
self.db_config.as_ref().map_or("prefer", |c| c.postgres_ssl_mode.as_str())
}
/// Check if Redis TLS is enabled.
#[must_use]
pub fn redis_ssl_enabled(&self) -> bool {
self.db_config.as_ref().is_some_and(|c| c.redis_ssl)
}
/// Check if `ClickHouse` HTTPS is enabled.
#[must_use]
pub fn clickhouse_https_enabled(&self) -> bool {
self.db_config.as_ref().is_some_and(|c| c.clickhouse_https)
}
/// Check if Elasticsearch HTTPS is enabled.
#[must_use]
pub fn elasticsearch_https_enabled(&self) -> bool {
self.db_config.as_ref().is_some_and(|c| c.elasticsearch_https)
}
/// Check if certificate verification is enabled for databases.
#[must_use]
pub fn verify_certificates(&self) -> bool {
self.db_config.as_ref().is_none_or(|c| c.verify_certificates)
}
/// Get the CA bundle path for verifying database certificates.
#[must_use]
pub fn ca_bundle_path(&self) -> Option<&Path> {
self.db_config
.as_ref()
.and_then(|c| c.ca_bundle_path.as_ref())
.map(|p| p.as_path())
}
/// Get database URL with TLS applied (for PostgreSQL).
#[must_use]
pub fn apply_postgres_tls(&self, db_url: &str) -> String {
let mut url = db_url.to_string();
// Parse SSL mode into URL parameter
let ssl_mode = self.postgres_ssl_mode();
if !ssl_mode.is_empty() && ssl_mode != "prefer" {
// Add or update sslmode parameter
if url.contains('?') {
let _ = write!(url, "&sslmode={ssl_mode}");
} else {
let _ = write!(url, "?sslmode={ssl_mode}");
}
}
url
}
/// Get Redis URL with TLS applied.
#[must_use]
pub fn apply_redis_tls(&self, redis_url: &str) -> String {
if self.redis_ssl_enabled() {
// Replace redis:// with rediss://
redis_url.replace("redis://", "rediss://")
} else {
redis_url.to_string()
}
}
/// Get `ClickHouse` URL with TLS applied.
#[must_use]
pub fn apply_clickhouse_tls(&self, ch_url: &str) -> String {
if self.clickhouse_https_enabled() {
// Replace http:// with https://
ch_url.replace("http://", "https://")
} else {
ch_url.to_string()
}
}
/// Get Elasticsearch URL with TLS applied.
#[must_use]
pub fn apply_elasticsearch_tls(&self, es_url: &str) -> String {
if self.elasticsearch_https_enabled() {
// Replace http:// with https://
es_url.replace("http://", "https://")
} else {
es_url.to_string()
}
}
}