From 80d78d84f4dc12f45b892075c8e9dce627e21aa1 Mon Sep 17 00:00:00 2001 From: Ufuk Celebi <1756620+uce@users.noreply.github.com> Date: Mon, 17 May 2021 22:46:38 +0200 Subject: [PATCH 1/2] config: add sslmode `verify-ca` and `verify-full` When a connection is established, the added modes are treated in the same way as the existing `require` mode as they both require a TLS connection. --- postgres/src/config.rs | 3 ++- tokio-postgres/src/config.rs | 9 ++++++++- tokio-postgres/src/connect_tls.rs | 11 ++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/postgres/src/config.rs b/postgres/src/config.rs index c8dffa330..cc48027a0 100644 --- a/postgres/src/config.rs +++ b/postgres/src/config.rs @@ -34,7 +34,8 @@ use tokio_postgres::{Error, Socket}; /// * `options` - Command line options used to configure the server. /// * `application_name` - Sets the `application_name` parameter on the server. /// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used -/// if available, but not used otherwise. If set to `require`, TLS will be forced to be used. Defaults to `prefer`. +/// if available, but not used otherwise. If set to `require`, `verify-ca`, or `verify-full`, TLS will be forced to +/// be used. Defaults to `prefer`. /// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the /// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts /// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting diff --git a/tokio-postgres/src/config.rs b/tokio-postgres/src/config.rs index 3e648a3d4..74946987d 100644 --- a/tokio-postgres/src/config.rs +++ b/tokio-postgres/src/config.rs @@ -42,6 +42,10 @@ pub enum SslMode { Prefer, /// Require the use of TLS. Require, + /// Require the use of TLS. + VerifyCa, + /// Require the use of TLS. + VerifyFull, } /// Channel binding configuration. @@ -95,7 +99,8 @@ pub enum Host { /// * `options` - Command line options used to configure the server. /// * `application_name` - Sets the `application_name` parameter on the server. /// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used -/// if available, but not used otherwise. If set to `require`, TLS will be forced to be used. Defaults to `prefer`. +/// if available, but not used otherwise. If set to `require`, `verify-ca`, or `verify-full`, TLS will be forced to +/// be used. Defaults to `prefer`. /// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the /// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts /// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting @@ -432,6 +437,8 @@ impl Config { "disable" => SslMode::Disable, "prefer" => SslMode::Prefer, "require" => SslMode::Require, + "verify-ca" => SslMode::VerifyCa, + "verify-full" => SslMode::VerifyFull, _ => return Err(Error::config_parse(Box::new(InvalidValue("sslmode")))), }; self.ssl_mode(mode); diff --git a/tokio-postgres/src/connect_tls.rs b/tokio-postgres/src/connect_tls.rs index 5ef21ac5c..25e913d1f 100644 --- a/tokio-postgres/src/connect_tls.rs +++ b/tokio-postgres/src/connect_tls.rs @@ -21,7 +21,7 @@ where SslMode::Prefer if !tls.can_connect(ForcePrivateApi) => { return Ok(MaybeTlsStream::Raw(stream)) } - SslMode::Prefer | SslMode::Require => {} + SslMode::Prefer | SslMode::Require | SslMode::VerifyCa | SslMode::VerifyFull => {} } let mut buf = BytesMut::new(); @@ -32,10 +32,11 @@ where stream.read_exact(&mut buf).await.map_err(Error::io)?; if buf[0] != b'S' { - if SslMode::Require == mode { - return Err(Error::tls("server does not support TLS".into())); - } else { - return Ok(MaybeTlsStream::Raw(stream)); + match mode { + SslMode::Require | SslMode::VerifyCa | SslMode::VerifyFull => { + return Err(Error::tls("server does not support TLS".into())) + } + SslMode::Disable | SslMode::Prefer => return Ok(MaybeTlsStream::Raw(stream)), } } From 71e51f31db09a877c3d31e040142c4f09b8abe08 Mon Sep 17 00:00:00 2001 From: Ufuk Celebi <1756620+uce@users.noreply.github.com> Date: Wed, 19 May 2021 22:21:19 +0200 Subject: [PATCH 2/2] config: add ssl config params Adds additional SSL config params: - sslcert - sslkey - sslrootcert More details at https://www.postgresql.org/docs/9.5/libpq-connect.html#LIBPQ-CONNSTRING. --- postgres/src/config.rs | 44 ++++++++++++++++++++++- tokio-postgres/src/config.rs | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/postgres/src/config.rs b/postgres/src/config.rs index cc48027a0..e41852bb6 100644 --- a/postgres/src/config.rs +++ b/postgres/src/config.rs @@ -5,11 +5,11 @@ use crate::connection::Connection; use crate::Client; use log::info; -use std::fmt; use std::path::Path; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; +use std::{fmt, path::PathBuf}; use tokio::runtime; #[doc(inline)] pub use tokio_postgres::config::{ChannelBinding, Host, SslMode, TargetSessionAttrs}; @@ -33,9 +33,12 @@ use tokio_postgres::{Error, Socket}; /// * `dbname` - The name of the database to connect to. Defaults to the username. /// * `options` - Command line options used to configure the server. /// * `application_name` - Sets the `application_name` parameter on the server. +/// * `sslcert` - Location of the client SSL certificate file. +/// * `sslkey` - Location for the secret key file used for the client certificate. /// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used /// if available, but not used otherwise. If set to `require`, `verify-ca`, or `verify-full`, TLS will be forced to /// be used. Defaults to `prefer`. +/// * `sslrootcert` - Location of SSL certificate authority (CA) certificate. /// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the /// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts /// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting @@ -184,6 +187,32 @@ impl Config { self.config.get_application_name() } + /// Sets the location of the client SSL certificate file. + /// + /// Defaults to `None`. + pub fn ssl_cert(&mut self, ssl_cert: &str) -> &mut Config { + self.config.ssl_cert(ssl_cert); + self + } + + /// Gets the location of the client SSL certificate file. + pub fn get_ssl_cert(&self) -> Option { + self.config.get_ssl_cert() + } + + /// Sets the location of the secret key file used for the client certificate. + /// + /// Defaults to `None`. + pub fn ssl_key(&mut self, ssl_key: &str) -> &mut Config { + self.config.ssl_key(ssl_key); + self + } + + /// Gets the location of the secret key file used for the client certificate. + pub fn get_ssl_key(&self) -> Option { + self.config.get_ssl_key() + } + /// Sets the SSL configuration. /// /// Defaults to `prefer`. @@ -197,6 +226,19 @@ impl Config { self.config.get_ssl_mode() } + /// Sets the location of SSL certificate authority (CA) certificate. + /// + /// Defaults to `None`. + pub fn ssl_root_cert(&mut self, ssl_root_cert: &str) -> &mut Config { + self.config.ssl_root_cert(ssl_root_cert); + self + } + + /// Gets the location of SSL certificate authority (CA) certificate. + pub fn get_ssl_root_cert(&self) -> Option { + self.config.get_ssl_root_cert() + } + /// Adds a host to the configuration. /// /// Multiple hosts can be specified by calling this method multiple times, and each will be tried in order. On Unix diff --git a/tokio-postgres/src/config.rs b/tokio-postgres/src/config.rs index 74946987d..4c47ac292 100644 --- a/tokio-postgres/src/config.rs +++ b/tokio-postgres/src/config.rs @@ -98,9 +98,12 @@ pub enum Host { /// * `dbname` - The name of the database to connect to. Defaults to the username. /// * `options` - Command line options used to configure the server. /// * `application_name` - Sets the `application_name` parameter on the server. +/// * `sslcert` - Location of the client SSL certificate file. +/// * `sslkey` - Location for the secret key file used for the client certificate. /// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used /// if available, but not used otherwise. If set to `require`, `verify-ca`, or `verify-full`, TLS will be forced to /// be used. Defaults to `prefer`. +/// * `sslrootcert` - Location of SSL certificate authority (CA) certificate. /// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the /// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts /// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting @@ -166,7 +169,10 @@ pub struct Config { pub(crate) dbname: Option, pub(crate) options: Option, pub(crate) application_name: Option, + pub(crate) ssl_cert: Option, + pub(crate) ssl_key: Option, pub(crate) ssl_mode: SslMode, + pub(crate) ssl_root_cert: Option, pub(crate) host: Vec, pub(crate) port: Vec, pub(crate) connect_timeout: Option, @@ -192,7 +198,10 @@ impl Config { dbname: None, options: None, application_name: None, + ssl_cert: None, + ssl_key: None, ssl_mode: SslMode::Prefer, + ssl_root_cert: None, host: vec![], port: vec![], connect_timeout: None, @@ -271,6 +280,32 @@ impl Config { self.application_name.as_deref() } + /// Sets the location of the client SSL certificate file. + /// + /// Defaults to `None`. + pub fn ssl_cert(&mut self, ssl_cert: &str) -> &mut Config { + self.ssl_cert = Some(PathBuf::from(ssl_cert)); + self + } + + /// Gets the location of the client SSL certificate file. + pub fn get_ssl_cert(&self) -> Option { + self.ssl_cert.clone() + } + + /// Sets the location of the secret key file used for the client certificate. + /// + /// Defaults to `None`. + pub fn ssl_key(&mut self, ssl_key: &str) -> &mut Config { + self.ssl_key = Some(PathBuf::from(ssl_key)); + self + } + + /// Gets the location of the secret key file used for the client certificate. + pub fn get_ssl_key(&self) -> Option { + self.ssl_key.clone() + } + /// Sets the SSL configuration. /// /// Defaults to `prefer`. @@ -284,6 +319,19 @@ impl Config { self.ssl_mode } + /// Sets the location of SSL certificate authority (CA) certificate. + /// + /// Defaults to `None`. + pub fn ssl_root_cert(&mut self, ssl_root_cert: &str) -> &mut Config { + self.ssl_root_cert = Some(PathBuf::from(ssl_root_cert)); + self + } + + /// Gets the location of SSL certificate authority (CA) certificate. + pub fn get_ssl_root_cert(&self) -> Option { + self.ssl_root_cert.clone() + } + /// Adds a host to the configuration. /// /// Multiple hosts can be specified by calling this method multiple times, and each will be tried in order. On Unix @@ -432,6 +480,18 @@ impl Config { "application_name" => { self.application_name(&value); } + "sslcert" => { + if std::fs::metadata(&value).is_err() { + return Err(Error::config_parse(Box::new(InvalidValue("sslcert")))); + } + self.ssl_cert(&value); + } + "sslkey" => { + if std::fs::metadata(&value).is_err() { + return Err(Error::config_parse(Box::new(InvalidValue("sslkey")))); + } + self.ssl_key(&value); + } "sslmode" => { let mode = match value { "disable" => SslMode::Disable, @@ -443,6 +503,12 @@ impl Config { }; self.ssl_mode(mode); } + "sslrootcert" => { + if std::fs::metadata(&value).is_err() { + return Err(Error::config_parse(Box::new(InvalidValue("sslrootcert")))); + } + self.ssl_root_cert(&value); + } "host" => { for host in value.split(',') { self.host(host); @@ -581,7 +647,10 @@ impl fmt::Debug for Config { .field("dbname", &self.dbname) .field("options", &self.options) .field("application_name", &self.application_name) + .field("ssl_cert", &self.ssl_cert) + .field("ssl_key", &self.ssl_key) .field("ssl_mode", &self.ssl_mode) + .field("ssl_root_cert", &self.ssl_root_cert) .field("host", &self.host) .field("port", &self.port) .field("connect_timeout", &self.connect_timeout)