显式引入数据库 TLS 配置并在生产环境拒绝弱 sslmode,避免连接静默降级。同步更新 deploy/README 与运维 runbook,落地 db.refining.ltd 的证书与服务器配置流程。 Made-with: Cursor
83 lines
2.3 KiB
Rust
83 lines
2.3 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use anyhow::{Context, Result};
|
|
use sqlx::postgres::PgSslMode;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DatabaseConfig {
|
|
pub url: String,
|
|
pub ssl_mode: Option<PgSslMode>,
|
|
pub ssl_root_cert: Option<PathBuf>,
|
|
pub enforce_strict_tls: bool,
|
|
}
|
|
|
|
/// Resolve database URL from environment.
|
|
/// Priority: `SECRETS_DATABASE_URL` env var → error.
|
|
pub fn resolve_db_url(override_url: &str) -> Result<String> {
|
|
if !override_url.is_empty() {
|
|
return Ok(override_url.to_string());
|
|
}
|
|
|
|
if let Ok(url) = std::env::var("SECRETS_DATABASE_URL")
|
|
&& !url.is_empty()
|
|
{
|
|
return Ok(url);
|
|
}
|
|
|
|
anyhow::bail!(
|
|
"Database not configured. Set the SECRETS_DATABASE_URL environment variable.\n\
|
|
Example: SECRETS_DATABASE_URL=postgres://user:pass@host:port/dbname"
|
|
)
|
|
}
|
|
|
|
fn env_var_non_empty(name: &str) -> Option<String> {
|
|
std::env::var(name)
|
|
.ok()
|
|
.filter(|value| !value.trim().is_empty())
|
|
}
|
|
|
|
fn parse_ssl_mode_from_env() -> Result<Option<PgSslMode>> {
|
|
let Some(mode) = env_var_non_empty("SECRETS_DATABASE_SSL_MODE") else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let parsed = mode.parse::<PgSslMode>().with_context(|| {
|
|
format!(
|
|
"Invalid SECRETS_DATABASE_SSL_MODE='{mode}'. Use one of: disable, allow, prefer, require, verify-ca, verify-full."
|
|
)
|
|
})?;
|
|
Ok(Some(parsed))
|
|
}
|
|
|
|
fn resolve_ssl_root_cert_from_env() -> Result<Option<PathBuf>> {
|
|
let Some(path) = env_var_non_empty("SECRETS_DATABASE_SSL_ROOT_CERT") else {
|
|
return Ok(None);
|
|
};
|
|
let path = PathBuf::from(path);
|
|
if !path.exists() {
|
|
anyhow::bail!(
|
|
"SECRETS_DATABASE_SSL_ROOT_CERT points to a missing file: {}",
|
|
path.display()
|
|
);
|
|
}
|
|
Ok(Some(path))
|
|
}
|
|
|
|
fn is_production_env() -> bool {
|
|
matches!(
|
|
env_var_non_empty("SECRETS_ENV")
|
|
.as_deref()
|
|
.map(|value| value.to_ascii_lowercase()),
|
|
Some(value) if value == "prod" || value == "production"
|
|
)
|
|
}
|
|
|
|
pub fn resolve_db_config(override_url: &str) -> Result<DatabaseConfig> {
|
|
Ok(DatabaseConfig {
|
|
url: resolve_db_url(override_url)?,
|
|
ssl_mode: parse_ssl_mode_from_env()?,
|
|
ssl_root_cert: resolve_ssl_root_cert_from_env()?,
|
|
enforce_strict_tls: is_production_env(),
|
|
})
|
|
}
|