1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-06 06:02:40 +03:00

New config options to set the database certificates

This commit is contained in:
Quentin Gliech
2024-07-02 17:15:27 +02:00
parent bd3b19e122
commit eff66726d5
4 changed files with 243 additions and 8 deletions

View File

@@ -228,6 +228,54 @@ fn database_connect_options_from_config(
opts opts
}; };
let options = match (config.ssl_ca.as_deref(), config.ssl_ca_file.as_deref()) {
(None, None) => options,
(Some(pem), None) => options.ssl_root_cert_from_pem(pem.as_bytes().to_owned()),
(None, Some(path)) => options.ssl_root_cert(path),
(Some(_), Some(_)) => {
anyhow::bail!("invalid database configuration: both `ssl_ca` and `ssl_ca_file` are set")
}
};
let options = match (
config.ssl_certificate.as_deref(),
config.ssl_certificate_file.as_deref(),
) {
(None, None) => options,
(Some(pem), None) => options.ssl_client_cert_from_pem(pem.as_bytes()),
(None, Some(path)) => options.ssl_client_cert(path),
(Some(_), Some(_)) => {
anyhow::bail!("invalid database configuration: both `ssl_certificate` and `ssl_certificate_file` are set")
}
};
let options = match (config.ssl_key.as_deref(), config.ssl_key_file.as_deref()) {
(None, None) => options,
(Some(pem), None) => options.ssl_client_key_from_pem(pem.as_bytes()),
(None, Some(path)) => options.ssl_client_key(path),
(Some(_), Some(_)) => {
anyhow::bail!(
"invalid database configuration: both `ssl_key` and `ssl_key_file` are set"
)
}
};
let options = match &config.ssl_mode {
Some(ssl_mode) => {
let ssl_mode = match ssl_mode {
mas_config::PgSslMode::Disable => sqlx::postgres::PgSslMode::Disable,
mas_config::PgSslMode::Allow => sqlx::postgres::PgSslMode::Allow,
mas_config::PgSslMode::Prefer => sqlx::postgres::PgSslMode::Prefer,
mas_config::PgSslMode::Require => sqlx::postgres::PgSslMode::Require,
mas_config::PgSslMode::VerifyCa => sqlx::postgres::PgSslMode::VerifyCa,
mas_config::PgSslMode::VerifyFull => sqlx::postgres::PgSslMode::VerifyFull,
};
options.ssl_mode(ssl_mode)
}
None => options,
};
let options = options let options = options
.log_statements(LevelFilter::Debug) .log_statements(LevelFilter::Debug)
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(100)); .log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));

View File

@@ -55,6 +55,13 @@ impl Default for DatabaseConfig {
username: None, username: None,
password: None, password: None,
database: None, database: None,
ssl_mode: None,
ssl_ca: None,
ssl_ca_file: None,
ssl_certificate: None,
ssl_certificate_file: None,
ssl_key: None,
ssl_key_file: None,
max_connections: default_max_connections(), max_connections: default_max_connections(),
min_connections: Default::default(), min_connections: Default::default(),
connect_timeout: default_connect_timeout(), connect_timeout: default_connect_timeout(),
@@ -64,6 +71,34 @@ impl Default for DatabaseConfig {
} }
} }
/// Options for controlling the level of protection provided for PostgreSQL SSL
/// connections.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum PgSslMode {
/// Only try a non-SSL connection.
Disable,
/// First try a non-SSL connection; if that fails, try an SSL connection.
Allow,
/// First try an SSL connection; if that fails, try a non-SSL connection.
Prefer,
/// Only try an SSL connection. If a root CA file is present, verify the
/// connection in the same way as if `VerifyCa` was specified.
Require,
/// Only try an SSL connection, and verify that the server certificate is
/// issued by a trusted certificate authority (CA).
VerifyCa,
/// Only try an SSL connection; verify that the server certificate is issued
/// by a trusted CA and that the requested server host name matches that
/// in the certificate.
VerifyFull,
}
/// Database connection configuration /// Database connection configuration
#[serde_as] #[serde_as]
#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -115,6 +150,50 @@ pub struct DatabaseConfig {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<String>, pub database: Option<String>,
/// How to handle SSL connections
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_mode: Option<PgSslMode>,
/// The PEM-encoded root certificate for SSL connections
///
/// This must not be specified if the `ssl_ca_file` option is specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_ca: Option<String>,
/// Path to the root certificate for SSL connections
///
/// This must not be specified if the `ssl_ca` option is specified.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(with = "Option<String>")]
pub ssl_ca_file: Option<Utf8PathBuf>,
/// The PEM-encoded client certificate for SSL connections
///
/// This must not be specified if the `ssl_certificate_file` option is
/// specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_certificate: Option<String>,
/// Path to the client certificate for SSL connections
///
/// This must not be specified if the `ssl_certificate` option is specified.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(with = "Option<String>")]
pub ssl_certificate_file: Option<Utf8PathBuf>,
/// The PEM-encoded client key for SSL connections
///
/// This must not be specified if the `ssl_key_file` option is specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_key: Option<String>,
/// Path to the client key for SSL connections
///
/// This must not be specified if the `ssl_key` option is specified.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(with = "Option<String>")]
pub ssl_key_file: Option<Utf8PathBuf>,
/// Set the maximum number of connections the pool should maintain /// Set the maximum number of connections the pool should maintain
#[serde(default = "default_max_connections")] #[serde(default = "default_max_connections")]
pub max_connections: NonZeroU32, pub max_connections: NonZeroU32,
@@ -153,6 +232,12 @@ impl ConfigurationSection for DatabaseConfig {
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> { fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> {
let metadata = figment.find_metadata(Self::PATH.unwrap()); let metadata = figment.find_metadata(Self::PATH.unwrap());
let annotate = |mut error: figment::Error| {
error.metadata = metadata.cloned();
error.profile = Some(figment::Profile::Default);
error.path = vec![Self::PATH.unwrap().to_owned()];
Err(error)
};
// Check that the user did not specify both `uri` and the split options at the // Check that the user did not specify both `uri` and the split options at the
// same time // same time
@@ -164,19 +249,42 @@ impl ConfigurationSection for DatabaseConfig {
|| self.database.is_some(); || self.database.is_some();
if self.uri.is_some() && has_split_options { if self.uri.is_some() && has_split_options {
let mut error = figment::error::Error::from( return annotate(figment::error::Error::from(
"uri must not be specified if host, port, socket, username, password, or database are specified".to_owned(), "uri must not be specified if host, port, socket, username, password, or database are specified".to_owned(),
); ));
error.metadata = metadata.cloned(); }
error.profile = Some(figment::Profile::Default);
error.path = vec![Self::PATH.unwrap().to_owned(), "uri".to_owned()]; if self.ssl_ca.is_some() && self.ssl_ca_file.is_some() {
return Err(error); return annotate(figment::error::Error::from(
"ssl_ca must not be specified if ssl_ca_file is specified".to_owned(),
));
}
if self.ssl_certificate.is_some() && self.ssl_certificate_file.is_some() {
return annotate(figment::error::Error::from(
"ssl_certificate must not be specified if ssl_certificate_file is specified"
.to_owned(),
));
}
if self.ssl_key.is_some() && self.ssl_key_file.is_some() {
return annotate(figment::error::Error::from(
"ssl_key must not be specified if ssl_key_file is specified".to_owned(),
));
}
if (self.ssl_key.is_some() || self.ssl_key_file.is_some())
^ (self.ssl_certificate.is_some() || self.ssl_certificate_file.is_some())
{
return annotate(figment::error::Error::from(
"both a ssl_certificate and a ssl_key must be set at the same time or none of them"
.to_owned(),
));
} }
Ok(()) Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use figment::{ use figment::{

View File

@@ -35,7 +35,7 @@ pub use self::{
branding::BrandingConfig, branding::BrandingConfig,
captcha::{CaptchaConfig, CaptchaServiceKind}, captcha::{CaptchaConfig, CaptchaServiceKind},
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig}, clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
database::DatabaseConfig, database::{DatabaseConfig, PgSslMode},
email::{EmailConfig, EmailSmtpMode, EmailTransportKind}, email::{EmailConfig, EmailSmtpMode, EmailTransportKind},
experimental::ExperimentalConfig, experimental::ExperimentalConfig,
http::{ http::{

View File

@@ -1003,6 +1003,38 @@
"description": "The database name\n\nThis must not be specified if `uri` is specified.", "description": "The database name\n\nThis must not be specified if `uri` is specified.",
"type": "string" "type": "string"
}, },
"ssl_mode": {
"description": "How to handle SSL connections",
"allOf": [
{
"$ref": "#/definitions/PgSslMode"
}
]
},
"ssl_ca": {
"description": "The PEM-encoded root certificate for SSL connections\n\nThis must not be specified if the `ssl_ca_file` option is specified.",
"type": "string"
},
"ssl_ca_file": {
"description": "Path to the root certificate for SSL connections\n\nThis must not be specified if the `ssl_ca` option is specified.",
"type": "string"
},
"ssl_certificate": {
"description": "The PEM-encoded client certificate for SSL connections\n\nThis must not be specified if the `ssl_certificate_file` option is specified.",
"type": "string"
},
"ssl_certificate_file": {
"description": "Path to the client certificate for SSL connections\n\nThis must not be specified if the `ssl_certificate` option is specified.",
"type": "string"
},
"ssl_key": {
"description": "The PEM-encoded client key for SSL connections\n\nThis must not be specified if the `ssl_key_file` option is specified.",
"type": "string"
},
"ssl_key_file": {
"description": "Path to the client key for SSL connections\n\nThis must not be specified if the `ssl_key` option is specified.",
"type": "string"
},
"max_connections": { "max_connections": {
"description": "Set the maximum number of connections the pool should maintain", "description": "Set the maximum number of connections the pool should maintain",
"default": 10, "default": 10,
@@ -1044,6 +1076,53 @@
"type": "string", "type": "string",
"format": "hostname" "format": "hostname"
}, },
"PgSslMode": {
"description": "Options for controlling the level of protection provided for PostgreSQL SSL connections.",
"oneOf": [
{
"description": "Only try a non-SSL connection.",
"type": "string",
"enum": [
"disable"
]
},
{
"description": "First try a non-SSL connection; if that fails, try an SSL connection.",
"type": "string",
"enum": [
"allow"
]
},
{
"description": "First try an SSL connection; if that fails, try a non-SSL connection.",
"type": "string",
"enum": [
"prefer"
]
},
{
"description": "Only try an SSL connection. If a root CA file is present, verify the connection in the same way as if `VerifyCa` was specified.",
"type": "string",
"enum": [
"require"
]
},
{
"description": "Only try an SSL connection, and verify that the server certificate is issued by a trusted certificate authority (CA).",
"type": "string",
"enum": [
"verify-ca"
]
},
{
"description": "Only try an SSL connection; verify that the server certificate is issued by a trusted CA and that the requested server host name matches that in the certificate.",
"type": "string",
"enum": [
"verify-full"
]
}
]
},
"TelemetryConfig": { "TelemetryConfig": {
"description": "Configuration related to sending monitoring data", "description": "Configuration related to sending monitoring data",
"type": "object", "type": "object",