From 25b39f7216b7fa485a08f3f56550cb1186471efb Mon Sep 17 00:00:00 2001 From: sammyolo Date: Mon, 20 Jan 2025 20:13:50 +0100 Subject: [PATCH 1/6] Added configurable and extendable ssl options --- README.md | 16 ++++++++++++++++ src/pog.gleam | 23 +++++++++++++++++++++++ src/pog_ffi.erl | 40 +++++++++++++++++++++++++++------------- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d3e6411..639363d 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,22 @@ pub fn connect() { } ``` +### SSL Options +`pog` also provides additional SSL configuration options through SslOptions. Currently, you can configure: +- `sni_enabled`: Enable or disable Server Name Indication (SNI). By default, this is set to True and uses the connection hostname. +```gleam +import pog + +pub fn connect() { + pog.default_config() + |> pog.ssl(pog.SslVerified) + |> pog.ssl_options(pog.SslOptions(sni_enabled: True)) + |> pog.connect +} +``` +When SNI is enabled, it helps ensure proper SSL certificate verification by sending the server name during the SSL handshake. This is particularly important when connecting to databases that use virtual hosting or when the database certificate includes multiple domain names. +The default configuration (sni_enabled: True) is recommended for most use cases as it provides the most secure and reliable SSL connection setup. + ### 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..fbe8499 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -32,6 +32,8 @@ pub type Config { password: Option(String), /// (default: SslDisabled): Whether to use SSL or not. ssl: Ssl, + /// - information to be added here + ssl_options: SslOptions, /// (default: []): List of 2-tuples, where key and value must be binary /// strings. You can include any Postgres connection parameter here, such as /// `#("application_name", "myappname")` and `#("timezone", "GMT")`. @@ -82,6 +84,19 @@ pub type Ssl { SslDisabled } +pub type SslOptions { + /// Additional SSL configuration options for fine-tuning the SSL connection. + /// Currently supports Server Name Indication (SNI) configuration: + /// - `sni_enabled`: When set to `True` (default), enables SNI using the connection + /// hostname. SNI helps ensure proper SSL certificate verification by sending + /// the server name during the SSL handshake. This is particularly important + /// when connecting to databases that use virtual hosting or when the database + /// certificate includes multiple domain names. + SslOptions( + sni_enabled: Bool + ) +} + /// Database server hostname. /// /// (default: 127.0.0.1) @@ -118,6 +133,13 @@ pub fn ssl(config: Config, ssl: Ssl) -> Config { Config(..config, ssl:) } +/// Ssl options you'd like to change +/// +/// (default: SslOptions(sni_enabled: True)) +pub fn ssl_options(config: Config, ssl_options: SslOptions) -> Config { + Config(..config, ssl_options:) +} + /// Any Postgres connection parameter here, such as /// `"application_name: myappname"` and `"timezone: GMT"` pub fn connection_parameter( @@ -212,6 +234,7 @@ pub fn default_config() -> Config { user: "postgres", password: None, ssl: SslDisabled, + ssl_options: SslOptions(sni_enabled: True), connection_parameters: [], pool_size: 10, queue_target: 50, diff --git a/src/pog_ffi.erl b/src/pog_ffi.erl index f012650..15d0390 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -5,6 +5,7 @@ -record(pog_pool, {name, pid, default_timeout}). -include_lib("pog/include/pog_Config.hrl"). +-include_lib("pog/include/pog_SslOptions.hrl"). -include_lib("pg_types/include/pg_types.hrl"). null() -> @@ -20,24 +21,36 @@ coerce(Value) -> %% connection to Postgres uses a TCP connection that get upgraded to TLS, and %% the TLS socket is sent as is, meaning the Hostname is lost when ssl module %% get the socket. server_name_indication overrides that behaviour and send -%% the correct Hostname to the ssl module. +%% the correct Hostname to the ssl module. However, there may be old versions +%% that do not support server name indication and therefore it's made +%% configurable. Using SslOptions in a manner that will be extendable for +%% future options to be added with the functions below. %% `customize_hostname_check` should be set to with the verify hostname match %% with HTTPS, because otherwise wildcards certificaties (i.e. *.example.com) %% will not be handled correctly. -default_ssl_options(Host, Ssl) -> +default_ssl_options(Host, Ssl, SslOptions) -> 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_unverified -> {true, [ + {verify, verify_none} + ] ++ configurable_ssl_options(Host, SslOptions)}; + ssl_verified -> + BaseOptions = [ + {verify, verify_peer}, + {cacerts, public_key:cacerts_get()}, + {customize_hostname_check, [ + {match_fun, public_key:pkix_verify_hostname_match_fun(https)} + ]} + ], + {true, BaseOptions ++ configurable_ssl_options(Host, SslOptions)} end. +configurable_ssl_options(Host, SslOptions) -> sni_options(Host, SslOptions). + +sni_options(Host, SslOptions) when SslOptions#ssl_options.sni_enabled -> + [{server_name_indication, binary_to_list(Host)}]; +sni_options(_Host, _SslOptions) -> []. + connect(Config) -> Id = integer_to_list(erlang:unique_integer([positive])), PoolName = list_to_atom("pog_pool_" ++ Id), @@ -48,6 +61,7 @@ connect(Config) -> user = User, password = Password, ssl = Ssl, + ssl_options = SslOptions, connection_parameters = ConnectionParameters, pool_size = PoolSize, queue_target = QueueTarget, @@ -58,14 +72,14 @@ connect(Config) -> rows_as_map = RowsAsMap, default_timeout = DefaultTimeout } = Config, - {SslActivated, SslOptions} = default_ssl_options(Host, Ssl), + {SslActivated, ReturnedSslOptions} = default_ssl_options(Host, Ssl, SslOptions), Options1 = #{ host => Host, port => Port, database => Database, user => User, ssl => SslActivated, - ssl_options => SslOptions, + ssl_options => ReturnedSslOptions, connection_parameters => ConnectionParameters, pool_size => PoolSize, queue_target => QueueTarget, From 7ce9137195625c18d2a87576714afc60649cdfce Mon Sep 17 00:00:00 2001 From: sammyolo Date: Mon, 20 Jan 2025 20:31:52 +0100 Subject: [PATCH 2/6] add docs for the config ssl_options --- src/pog.gleam | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pog.gleam b/src/pog.gleam index fbe8499..b59c468 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -32,7 +32,10 @@ pub type Config { password: Option(String), /// (default: SslDisabled): Whether to use SSL or not. ssl: Ssl, - /// - information to be added here + /// (default: SslOptions(sni_enabled: True)): Additional SSL configuration options. + /// Used to fine-tune SSL connection behavior, such as SNI (Server Name Indication) + /// which is important for proper certificate verification when connecting to + /// databases with virtual hosting or multi-domain certificates. ssl_options: SslOptions, /// (default: []): List of 2-tuples, where key and value must be binary /// strings. You can include any Postgres connection parameter here, such as From c661fc56cabbd1cf06df58dafbee14c5b0e3ae91 Mon Sep 17 00:00:00 2001 From: sammyolo Date: Mon, 20 Jan 2025 20:36:28 +0100 Subject: [PATCH 3/6] add docs to configureable_ssl_options --- src/pog_ffi.erl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pog_ffi.erl b/src/pog_ffi.erl index 15d0390..e02ca4e 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -45,6 +45,11 @@ default_ssl_options(Host, Ssl, SslOptions) -> {true, BaseOptions ++ configurable_ssl_options(Host, SslOptions)} end. +%% This function can be extended with more functions with for example +%% alpn_preferred_protocols which will turn the function into: +%% configurable_ssl_options(Host, SslOptions) -> +%% sni_options(Host, SslOptions) ++ +%% alpn_options(SslOptions) configurable_ssl_options(Host, SslOptions) -> sni_options(Host, SslOptions). sni_options(Host, SslOptions) when SslOptions#ssl_options.sni_enabled -> From a3c7139c3cdb5d89684c1888c4e9388305337d3e Mon Sep 17 00:00:00 2001 From: sammyolo Date: Sat, 25 Jan 2025 18:18:11 +0100 Subject: [PATCH 4/6] change: ssl options are part of ssl type --- src/pog.gleam | 61 +++++++++++++++++++++++-------------------------- src/pog_ffi.erl | 58 +++++++++++++++++++++++----------------------- 2 files changed, 58 insertions(+), 61 deletions(-) diff --git a/src/pog.gleam b/src/pog.gleam index b59c468..9b4c3e4 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -32,11 +32,6 @@ pub type Config { password: Option(String), /// (default: SslDisabled): Whether to use SSL or not. ssl: Ssl, - /// (default: SslOptions(sni_enabled: True)): Additional SSL configuration options. - /// Used to fine-tune SSL connection behavior, such as SNI (Server Name Indication) - /// which is important for proper certificate verification when connecting to - /// databases with virtual hosting or multi-domain certificates. - ssl_options: SslOptions, /// (default: []): List of 2-tuples, where key and value must be binary /// strings. You can include any Postgres connection parameter here, such as /// `#("application_name", "myappname")` and `#("timezone", "GMT")`. @@ -74,30 +69,28 @@ 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 } pub type SslOptions { - /// Additional SSL configuration options for fine-tuning the SSL connection. - /// Currently supports Server Name Indication (SNI) configuration: - /// - `sni_enabled`: When set to `True` (default), enables SNI using the connection - /// hostname. SNI helps ensure proper SSL certificate verification by sending - /// the server name during the SSL handshake. This is particularly important - /// when connecting to databases that use virtual hosting or when the database - /// certificate includes multiple domain names. - SslOptions( - sni_enabled: Bool - ) + /// Additional SSL configuration options for fine-tuning the SSL connection. + /// Currently supports Server Name Indication (SNI) configuration: + /// - `sni_enabled`: When set to `True` (default), enables SNI using the connection + /// hostname. SNI helps ensure proper SSL certificate verification by sending + /// the server name during the SSL handshake. This is particularly important + /// when connecting to databases that use virtual hosting or when the database + /// certificate includes multiple domain names. + SslOptions(sni_enabled: Bool) } /// Database server hostname. @@ -131,18 +124,23 @@ 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:) } -/// Ssl options you'd like to change -/// -/// (default: SslOptions(sni_enabled: True)) -pub fn ssl_options(config: Config, ssl_options: SslOptions) -> Config { - Config(..config, ssl_options:) -} - /// Any Postgres connection parameter here, such as /// `"application_name: myappname"` and `"timezone: GMT"` pub fn connection_parameter( @@ -150,10 +148,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 @@ -237,7 +235,6 @@ pub fn default_config() -> Config { user: "postgres", password: None, ssl: SslDisabled, - ssl_options: SslOptions(sni_enabled: True), connection_parameters: [], pool_size: 10, queue_target: 50, @@ -316,8 +313,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 e02ca4e..eb9e614 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -5,7 +5,6 @@ -record(pog_pool, {name, pid, default_timeout}). -include_lib("pog/include/pog_Config.hrl"). --include_lib("pog/include/pog_SslOptions.hrl"). -include_lib("pg_types/include/pg_types.hrl"). null() -> @@ -21,40 +20,42 @@ coerce(Value) -> %% connection to Postgres uses a TCP connection that get upgraded to TLS, and %% the TLS socket is sent as is, meaning the Hostname is lost when ssl module %% get the socket. server_name_indication overrides that behaviour and send -%% the correct Hostname to the ssl module. However, there may be old versions -%% that do not support server name indication and therefore it's made -%% configurable. Using SslOptions in a manner that will be extendable for -%% future options to be added with the functions below. +%% the correct Hostname to the ssl module. %% `customize_hostname_check` should be set to with the verify hostname match %% with HTTPS, because otherwise wildcards certificaties (i.e. *.example.com) %% will not be handled correctly. -default_ssl_options(Host, Ssl, SslOptions) -> +default_ssl_options(Host, Ssl) -> case Ssl of - ssl_disabled -> {false, []}; - ssl_unverified -> {true, [ - {verify, verify_none} - ] ++ configurable_ssl_options(Host, SslOptions)}; - ssl_verified -> - BaseOptions = [ - {verify, verify_peer}, - {cacerts, public_key:cacerts_get()}, - {customize_hostname_check, [ - {match_fun, public_key:pkix_verify_hostname_match_fun(https)} - ]} - ], - {true, BaseOptions ++ configurable_ssl_options(Host, SslOptions)} + {ssl_disabled} -> {false, []}; + {ssl_unverified, SniEnabled} -> {true, get_unverified_options(Host, SniEnabled)}; + {ssl_verified, SniEnabled} -> {true, get_verified_options(Host, SniEnabled)} end. -%% This function can be extended with more functions with for example -%% alpn_preferred_protocols which will turn the function into: -%% configurable_ssl_options(Host, SslOptions) -> -%% sni_options(Host, SslOptions) ++ -%% alpn_options(SslOptions) -configurable_ssl_options(Host, SslOptions) -> sni_options(Host, SslOptions). +%% Options for unverified SSL connections +get_unverified_options(Host, SniEnabled) -> + [ + {verify, verify_none} + ] ++ get_sni_options(Host, SniEnabled). -sni_options(Host, SslOptions) when SslOptions#ssl_options.sni_enabled -> +%% 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)}]; -sni_options(_Host, _SslOptions) -> []. +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])), @@ -66,7 +67,6 @@ connect(Config) -> user = User, password = Password, ssl = Ssl, - ssl_options = SslOptions, connection_parameters = ConnectionParameters, pool_size = PoolSize, queue_target = QueueTarget, @@ -77,7 +77,7 @@ connect(Config) -> rows_as_map = RowsAsMap, default_timeout = DefaultTimeout } = Config, - {SslActivated, ReturnedSslOptions} = default_ssl_options(Host, Ssl, SslOptions), + {SslActivated, ReturnedSslOptions} = default_ssl_options(Host, Ssl), Options1 = #{ host => Host, port => Port, From 5f78560f04852e55b63b44af0291ae4ba845b448 Mon Sep 17 00:00:00 2001 From: sammyolo Date: Sat, 25 Jan 2025 18:21:13 +0100 Subject: [PATCH 5/6] update: Readme to reflect new ssl options --- README.md | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 639363d..713b694 100644 --- a/README.md +++ b/README.md @@ -166,37 +166,27 @@ 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: -```gleam -import pog +- `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) -pub fn connect() { - pog.default_config() - |> pog.ssl(pog.SslVerified) - |> pog.connect -} -``` +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. -### SSL Options -`pog` also provides additional SSL configuration options through SslOptions. Currently, you can configure: -- `sni_enabled`: Enable or disable Server Name Indication (SNI). By default, this is set to True and uses the connection hostname. ```gleam import pog pub fn connect() { pog.default_config() - |> pog.ssl(pog.SslVerified) - |> pog.ssl_options(pog.SslOptions(sni_enabled: True)) + |> pog.ssl(pog.SslVerified(sni_enabled: True)) |> pog.connect } ``` -When SNI is enabled, it helps ensure proper SSL certificate verification by sending the server name during the SSL handshake. This is particularly important when connecting to databases that use virtual hosting or when the database certificate includes multiple domain names. -The default configuration (sni_enabled: True) is recommended for most use cases as it provides the most secure and reliable SSL connection setup. + +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? From 75bb9ce183c56071654bc049b55fae72d173e50c Mon Sep 17 00:00:00 2001 From: sammyolo Date: Sat, 25 Jan 2025 18:25:40 +0100 Subject: [PATCH 6/6] Remove SslOptions type --- src/pog.gleam | 11 ----------- src/pog_ffi.erl | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/pog.gleam b/src/pog.gleam index 9b4c3e4..24e4b94 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -82,17 +82,6 @@ pub type Ssl { SslDisabled } -pub type SslOptions { - /// Additional SSL configuration options for fine-tuning the SSL connection. - /// Currently supports Server Name Indication (SNI) configuration: - /// - `sni_enabled`: When set to `True` (default), enables SNI using the connection - /// hostname. SNI helps ensure proper SSL certificate verification by sending - /// the server name during the SSL handshake. This is particularly important - /// when connecting to databases that use virtual hosting or when the database - /// certificate includes multiple domain names. - SslOptions(sni_enabled: Bool) -} - /// Database server hostname. /// /// (default: 127.0.0.1) diff --git a/src/pog_ffi.erl b/src/pog_ffi.erl index eb9e614..597d197 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -77,14 +77,14 @@ connect(Config) -> rows_as_map = RowsAsMap, default_timeout = DefaultTimeout } = Config, - {SslActivated, ReturnedSslOptions} = default_ssl_options(Host, Ssl), + {SslActivated, SslOptions} = default_ssl_options(Host, Ssl), Options1 = #{ host => Host, port => Port, database => Database, user => User, ssl => SslActivated, - ssl_options => ReturnedSslOptions, + ssl_options => SslOptions, connection_parameters => ConnectionParameters, pool_size => PoolSize, queue_target => QueueTarget,