fix: TLS imposé sur le pool Postgres quelle que soit l'URLUnverified
PgConnectOptions + ssl_mode(PgSslMode::Require) en dur, au lieu d'ajouter sslmode=require à l'URL seulement quand il manquait (une URL sslmode=disable/prefer passait en l'état — et le serveur CC accepte le clair, vérifié via pg_stat_ssl). Test unitaire sur les 4 variantes d'URL ; connexion TLSv1.3 constatée même avec sslmode=disable.
df9986a parent: e8f7a6a modified
src/cli.rs +1 -1 | @@ -37,7 +37,7 @@ pub async fn user_add(username: &str, password_arg: Option<String>) -> Result<() | ||
| 37 | 37 | |
| 38 | 38 | let pool = PgPoolOptions::new() |
| 39 | 39 | .max_connections(1) |
| 40 | - .connect(&config::database_url()?) | |
| 40 | + .connect_with(config::pg_options()?) | |
| 41 | 41 | .await |
| 42 | 42 | .context("connexion à la base")?; |
| 43 | 43 | sqlx::migrate!().run(&pool).await?; |
| @@ -37,7 +37,7 @@ pub async fn user_add(username: &str, password_arg: Option<String>) -> Result<() | |||
| 37 | 37 | ||
| 38 | let pool = PgPoolOptions::new() | 38 | let pool = PgPoolOptions::new() |
| 39 | .max_connections(1) | 39 | .max_connections(1) |
| 40 | - .connect(&config::database_url()?) | 40 | + .connect_with(config::pg_options()?) |
| 41 | .await | 41 | .await |
| 42 | .context("connexion à la base")?; | 42 | .context("connexion à la base")?; |
| 43 | sqlx::migrate!().run(&pool).await?; | 43 | sqlx::migrate!().run(&pool).await?; |
modified
src/config.rs +24 -25 | @@ -1,22 +1,21 @@ | ||
| 1 | +use std::str::FromStr; | |
| 2 | + | |
| 1 | 3 | use anyhow::{Context, Result}; |
| 4 | +use sqlx::postgres::{PgConnectOptions, PgSslMode}; | |
| 2 | 5 | |
| 3 | -/// URL de la base : POSTGRESQL_ADDON_URI (Clever Cloud) sinon DATABASE_URL, | |
| 4 | -/// avec sslmode=require imposé si absent. | |
| 5 | -pub fn database_url() -> Result<String> { | |
| 6 | +/// Options de connexion : POSTGRESQL_ADDON_URI (Clever Cloud) sinon | |
| 7 | +/// DATABASE_URL, avec TLS **imposé** (`PgSslMode::Require`) quoi que | |
| 8 | +/// dise l'URL — y compris `sslmode=disable`. | |
| 9 | +pub fn pg_options() -> Result<PgConnectOptions> { | |
| 6 | 10 | let url = std::env::var("POSTGRESQL_ADDON_URI") |
| 7 | 11 | .or_else(|_| std::env::var("DATABASE_URL")) |
| 8 | 12 | .context("POSTGRESQL_ADDON_URI ou DATABASE_URL doit être définie")?; |
| 9 | - Ok(ensure_sslmode_require(&url)) | |
| 13 | + Ok(force_tls(&url)?) | |
| 10 | 14 | } |
| 11 | 15 | |
| 12 | -fn ensure_sslmode_require(url: &str) -> String { | |
| 13 | - if url.contains("sslmode=") { | |
| 14 | - url.to_string() | |
| 15 | - } else if url.contains('?') { | |
| 16 | - format!("{url}&sslmode=require") | |
| 17 | - } else { | |
| 18 | - format!("{url}?sslmode=require") | |
| 19 | - } | |
| 16 | +fn force_tls(url: &str) -> Result<PgConnectOptions> { | |
| 17 | + let opts = PgConnectOptions::from_str(url).context("URL de base invalide")?; | |
| 18 | + Ok(opts.ssl_mode(PgSslMode::Require)) | |
| 20 | 19 | } |
| 21 | 20 | |
| 22 | 21 | pub fn port() -> u16 { |
| @@ -36,18 +35,18 @@ mod tests { | ||
| 36 | 35 | use super::*; |
| 37 | 36 | |
| 38 | 37 | #[test] |
| 39 | - fn sslmode_appended() { | |
| 40 | - assert_eq!( | |
| 41 | - ensure_sslmode_require("postgres://u:p@h:5432/db"), | |
| 42 | - "postgres://u:p@h:5432/db?sslmode=require" | |
| 43 | - ); | |
| 44 | - assert_eq!( | |
| 45 | - ensure_sslmode_require("postgres://u:p@h:5432/db?foo=1"), | |
| 46 | - "postgres://u:p@h:5432/db?foo=1&sslmode=require" | |
| 47 | - ); | |
| 48 | - assert_eq!( | |
| 49 | - ensure_sslmode_require("postgres://u:p@h:5432/db?sslmode=verify-full"), | |
| 50 | - "postgres://u:p@h:5432/db?sslmode=verify-full" | |
| 51 | - ); | |
| 38 | + fn tls_forced_whatever_the_url_says() { | |
| 39 | + for url in [ | |
| 40 | + "postgres://u:p@h:5432/db", | |
| 41 | + "postgres://u:p@h:5432/db?sslmode=disable", | |
| 42 | + "postgres://u:p@h:5432/db?sslmode=prefer", | |
| 43 | + "postgres://u:p@h:5432/db?sslmode=verify-full", | |
| 44 | + ] { | |
| 45 | + let opts = force_tls(url).unwrap(); | |
| 46 | + assert!( | |
| 47 | + format!("{opts:?}").contains("Require"), | |
| 48 | + "TLS non imposé pour {url}" | |
| 49 | + ); | |
| 50 | + } | |
| 52 | 51 | } |
| 53 | 52 | } |
| @@ -1,22 +1,21 @@ | |||
| 1 | +use std::str::FromStr; | ||
| 2 | + | ||
| 1 | use anyhow::{Context, Result}; | 3 | use anyhow::{Context, Result}; |
| 4 | +use sqlx::postgres::{PgConnectOptions, PgSslMode}; | ||
| 2 | 5 | ||
| 3 | -/// URL de la base : POSTGRESQL_ADDON_URI (Clever Cloud) sinon DATABASE_URL, | 6 | +/// Options de connexion : POSTGRESQL_ADDON_URI (Clever Cloud) sinon |
| 4 | -/// avec sslmode=require imposé si absent. | 7 | +/// DATABASE_URL, avec TLS **imposé** (`PgSslMode::Require`) quoi que |
| 5 | -pub fn database_url() -> Result<String> { | 8 | +/// dise l'URL — y compris `sslmode=disable`. |
| 9 | +pub fn pg_options() -> Result<PgConnectOptions> { | ||
| 6 | let url = std::env::var("POSTGRESQL_ADDON_URI") | 10 | let url = std::env::var("POSTGRESQL_ADDON_URI") |
| 7 | .or_else(|_| std::env::var("DATABASE_URL")) | 11 | .or_else(|_| std::env::var("DATABASE_URL")) |
| 8 | .context("POSTGRESQL_ADDON_URI ou DATABASE_URL doit être définie")?; | 12 | .context("POSTGRESQL_ADDON_URI ou DATABASE_URL doit être définie")?; |
| 9 | - Ok(ensure_sslmode_require(&url)) | 13 | + Ok(force_tls(&url)?) |
| 10 | } | 14 | } |
| 11 | 15 | ||
| 12 | -fn ensure_sslmode_require(url: &str) -> String { | 16 | +fn force_tls(url: &str) -> Result<PgConnectOptions> { |
| 13 | - if url.contains("sslmode=") { | 17 | + let opts = PgConnectOptions::from_str(url).context("URL de base invalide")?; |
| 14 | - url.to_string() | 18 | + Ok(opts.ssl_mode(PgSslMode::Require)) |
| 15 | - } else if url.contains('?') { | ||
| 16 | - format!("{url}&sslmode=require") | ||
| 17 | - } else { | ||
| 18 | - format!("{url}?sslmode=require") | ||
| 19 | - } | ||
| 20 | } | 19 | } |
| 21 | 20 | ||
| 22 | pub fn port() -> u16 { | 21 | pub fn port() -> u16 { |
| @@ -36,18 +35,18 @@ mod tests { | |||
| 36 | use super::*; | 35 | use super::*; |
| 37 | 36 | ||
| 38 | #[test] | 37 | #[test] |
| 39 | - fn sslmode_appended() { | 38 | + fn tls_forced_whatever_the_url_says() { |
| 40 | - assert_eq!( | 39 | + for url in [ |
| 41 | - ensure_sslmode_require("postgres://u:p@h:5432/db"), | 40 | + "postgres://u:p@h:5432/db", |
| 42 | - "postgres://u:p@h:5432/db?sslmode=require" | 41 | + "postgres://u:p@h:5432/db?sslmode=disable", |
| 43 | - ); | 42 | + "postgres://u:p@h:5432/db?sslmode=prefer", |
| 44 | - assert_eq!( | 43 | + "postgres://u:p@h:5432/db?sslmode=verify-full", |
| 45 | - ensure_sslmode_require("postgres://u:p@h:5432/db?foo=1"), | 44 | + ] { |
| 46 | - "postgres://u:p@h:5432/db?foo=1&sslmode=require" | 45 | + let opts = force_tls(url).unwrap(); |
| 47 | - ); | 46 | + assert!( |
| 48 | - assert_eq!( | 47 | + format!("{opts:?}").contains("Require"), |
| 49 | - ensure_sslmode_require("postgres://u:p@h:5432/db?sslmode=verify-full"), | 48 | + "TLS non imposé pour {url}" |
| 50 | - "postgres://u:p@h:5432/db?sslmode=verify-full" | 49 | + ); |
| 51 | - ); | 50 | + } |
| 52 | } | 51 | } |
| 53 | } | 52 | } |
modified
src/main.rs +1 -1 | @@ -70,7 +70,7 @@ async fn serve() -> Result<()> { | ||
| 70 | 70 | |
| 71 | 71 | let pool = PgPoolOptions::new() |
| 72 | 72 | .max_connections(5) |
| 73 | - .connect(&config::database_url()?) | |
| 73 | + .connect_with(config::pg_options()?) | |
| 74 | 74 | .await |
| 75 | 75 | .context("connexion à la base")?; |
| 76 | 76 | sqlx::migrate!().run(&pool).await.context("migrations")?; |
| @@ -70,7 +70,7 @@ async fn serve() -> Result<()> { | |||
| 70 | 70 | ||
| 71 | let pool = PgPoolOptions::new() | 71 | let pool = PgPoolOptions::new() |
| 72 | .max_connections(5) | 72 | .max_connections(5) |
| 73 | - .connect(&config::database_url()?) | 73 | + .connect_with(config::pg_options()?) |
| 74 | .await | 74 | .await |
| 75 | .context("connexion à la base")?; | 75 | .context("connexion à la base")?; |
| 76 | sqlx::migrate!().run(&pool).await.context("migrations")?; | 76 | sqlx::migrate!().run(&pool).await.context("migrations")?; |