diff --git a/README.md b/README.md index d3e6411..713b694 100644 --- a/README.md +++ b/README.md @@ -166,22 +166,28 @@ In Postgres, conventions used, including in connection URI are as follow: ### `pog` SSL usage -In `pog`, setting up an SSL connection simply ask you to indicate the proper flag -in `pog.Config`. The different options are `SslDisabled`, `SslUnverified` & -`SslVerified`. Because of the nature of the 3 modes of SSL, and because talking -to your database should be highly secured to protect you against man-in-the-middle -attacks, you should always try to use the most secured setting. +In `pog`, SSL configuration is designed to be both secure and flexible. The library provides three SSL modes through the `Ssl` type: + +- `SslVerified`: The most secure option that verifies CA certificates (recommended) +- `SslUnverified`: Enables SSL without certificate verification (use with caution) +- `SslDisabled`: No SSL encryption (not recommended for production) + +Both `SslVerified` and `SslUnverified` modes support Server Name Indication (SNI), which is essential for proper certificate verification when connecting to databases using virtual hosting or multi-domain certificates. ```gleam import pog pub fn connect() { pog.default_config() - |> pog.ssl(pog.SslVerified) + |> pog.ssl(pog.SslVerified(sni_enabled: True)) |> pog.connect } ``` +The `sni_enabled` parameter (defaults to `True`) helps ensure proper SSL certificate verification by sending the server name during the SSL handshake. This is particularly important for: +- Databases using virtual hosting +- Certificates covering multiple domain names + ### Need some help? You tried to setup a secured connection, but it does not work? Your container diff --git a/src/pog.gleam b/src/pog.gleam index 80b8867..24e4b94 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -69,14 +69,14 @@ pub type Ssl { /// option to use SSL and should be always used by default. /// Never ignore CA certificate checking _unless you know exactly what you are /// doing_. - SslVerified + SslVerified(sni_enabled: Bool) /// Enable SSL connection, but don't check CA certificate. /// `SslVerified` should always be prioritized upon `SslUnverified`. /// As it implies, that option enables SSL, but as it is unverified, the /// connection can be unsafe. _Use this option only if you know what you're /// doing._ In case `pog` can not find the proper CA certificate, take a look /// at the README to get some help to inject the CA certificate in your OS. - SslUnverified + SslUnverified(sni_enabled: Bool) /// Disable SSL connection completely. Using this option will let the /// connection unsecured, and should be avoided in production environment. SslDisabled @@ -113,7 +113,19 @@ pub fn password(config: Config, password: Option(String)) -> Config { /// Whether to use SSL or not. /// -/// (default: False) +/// The SSL configuration provides three modes: +/// - `SslVerified`: Most secure option that verifies CA certificates (recommended) +/// - `SslUnverified`: Enables SSL without certificate verification (use with caution) +/// - `SslDisabled`: No SSL encryption (not recommended for production) +/// +/// Each SSL mode can be configured with SNI (Server Name Indication) support, +/// which is particularly useful for virtual hosting and multi-domain certificates. +/// +/// Example: +/// ```gleam +/// pog.default_config() +/// |> pog.ssl(pog.SslVerified(sni_enabled: True)) +/// ``` pub fn ssl(config: Config, ssl: Ssl) -> Config { Config(..config, ssl:) } @@ -125,10 +137,10 @@ pub fn connection_parameter( name name: String, value value: String, ) -> Config { - Config( - ..config, - connection_parameters: [#(name, value), ..config.connection_parameters], - ) + Config(..config, connection_parameters: [ + #(name, value), + ..config.connection_parameters + ]) } /// Number of connections to keep open with the database @@ -290,8 +302,8 @@ fn extract_ssl_mode(query: option.Option(String)) -> Result(Ssl, Nil) { use query <- result.then(uri.parse_query(query)) use sslmode <- result.then(list.key_find(query, "sslmode")) case sslmode { - "require" -> Ok(SslUnverified) - "verify-ca" | "verify-full" -> Ok(SslVerified) + "require" -> Ok(SslUnverified(sni_enabled: True)) + "verify-ca" | "verify-full" -> Ok(SslVerified(sni_enabled: True)) "disable" -> Ok(SslDisabled) _ -> Error(Nil) } diff --git a/src/pog_ffi.erl b/src/pog_ffi.erl index f012650..597d197 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -26,18 +26,37 @@ coerce(Value) -> %% will not be handled correctly. default_ssl_options(Host, Ssl) -> case Ssl of - ssl_disabled -> {false, []}; - ssl_unverified -> {true, [{verify, verify_none}]}; - ssl_verified -> {true, [ - {verify, verify_peer}, - {cacerts, public_key:cacerts_get()}, - {server_name_indication, binary_to_list(Host)}, - {customize_hostname_check, [ - {match_fun, public_key:pkix_verify_hostname_match_fun(https)} - ]} - ]} + {ssl_disabled} -> {false, []}; + {ssl_unverified, SniEnabled} -> {true, get_unverified_options(Host, SniEnabled)}; + {ssl_verified, SniEnabled} -> {true, get_verified_options(Host, SniEnabled)} end. +%% Options for unverified SSL connections +get_unverified_options(Host, SniEnabled) -> + [ + {verify, verify_none} + ] ++ get_sni_options(Host, SniEnabled). + +%% Options for verified SSL connections +get_verified_options(Host, SniEnabled) -> + [ + {verify, verify_peer}, + {cacerts, public_key:cacerts_get()} + ] ++ get_hostname_check_options() ++ get_sni_options(Host, SniEnabled). + +%% Server Name Indication (SNI) options +get_sni_options(Host, true) -> + [{server_name_indication, binary_to_list(Host)}]; +get_sni_options(_Host, false) -> []. + +%% Hostname verification options for wildcard certificates +get_hostname_check_options() -> + [ + {customize_hostname_check, [ + {match_fun, public_key:pkix_verify_hostname_match_fun(https)} + ]} + ]. + connect(Config) -> Id = integer_to_list(erlang:unique_integer([positive])), PoolName = list_to_atom("pog_pool_" ++ Id),