From 2728603c29ed9fb46416ed54c6012def644b056c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 20 Jul 2021 12:31:22 +0200 Subject: [PATCH 1/5] Rename SetupSslContext() to InitSslContext() --- lib/base/tlsutility.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 40e2d1767..6de5df544 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -70,7 +70,7 @@ void InitializeOpenSSL() l_SSLInitialized = true; } -static void SetupSslContext(const Shared::Ptr& context, const String& pubkey, const String& privkey, const String& cakey) +static void InitSslContext(const Shared::Ptr& context, const String& pubkey, const String& privkey, const String& cakey) { char errbuf[256]; @@ -181,7 +181,7 @@ Shared::Ptr MakeAsioSslContext(const String& pubkey, auto context (Shared::Make(ssl::context::tls)); - SetupSslContext(context, pubkey, privkey, cakey); + InitSslContext(context, pubkey, privkey, cakey); return context; } From fbcaf82e3edaf61fac99bb93e7fafcaba5a1ae8b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 20 Jul 2021 13:44:06 +0200 Subject: [PATCH 2/5] InitSslContext(): fall back to default root CAs --- lib/base/tlsutility.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 6de5df544..40f7b57da 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -137,7 +137,16 @@ static void InitSslContext(const Shared::Ptr& context } } - if (!cakey.IsEmpty()) { + if (cakey.IsEmpty()) { + if (!SSL_CTX_set_default_verify_paths(sslContext)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error loading system's root CAs: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_set_default_verify_paths") + << errinfo_openssl_error(ERR_peek_error()); + } + } else { if (!SSL_CTX_load_verify_locations(sslContext, cakey.CStr(), nullptr)) { ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); Log(LogCritical, "SSL") From 80a1128ec7e212133f69f17d080bdeea23aabe6f Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 16 Jul 2021 18:31:52 +0200 Subject: [PATCH 3/5] Introduce SetupSslContext() --- lib/base/tlsutility.cpp | 43 ++++++++++++++++++++++++++++++++++++++ lib/base/tlsutility.hpp | 4 ++++ lib/remote/apilistener.cpp | 39 +--------------------------------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 40f7b57da..1357ed617 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -258,6 +258,49 @@ int ResolveTlsProtocolVersion(const std::string& version) { } } +Shared::Ptr SetupSslContext(String certPath, String keyPath, + String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di) +{ + namespace ssl = boost::asio::ssl; + + Shared::Ptr context; + + try { + context = MakeAsioSslContext(certPath, keyPath, caPath); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '" + + certPath + "' key path: '" + keyPath + "' ca path: '" + caPath + "'.", di)); + } + + if (!crlPath.IsEmpty()) { + try { + AddCRLToSSLContext(context, crlPath); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot add certificate revocation list to SSL context for crl path: '" + + crlPath + "'.", di)); + } + } + + if (!cipherList.IsEmpty()) { + try { + SetCipherListToSSLContext(context, cipherList); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot set cipher list to SSL context for cipher list: '" + + cipherList + "'.", di)); + } + } + + if (!protocolmin.IsEmpty()){ + try { + SetTlsProtocolminToSSLContext(context, protocolmin); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + protocolmin + "'.", di)); + } + } + + return std::move(context); +} + /** * Set the minimum TLS protocol version to the specified SSL context. * diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index 2493ff279..ce50fb427 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -4,6 +4,7 @@ #define TLSUTILITY_H #include "base/i2-base.hpp" +#include "base/debuginfo.hpp" #include "base/object.hpp" #include "base/shared.hpp" #include "base/array.hpp" @@ -35,6 +36,9 @@ void SetCipherListToSSLContext(const Shared::Ptr& con void SetTlsProtocolminToSSLContext(const Shared::Ptr& context, const String& tlsProtocolmin); int ResolveTlsProtocolVersion(const std::string& version); +Shared::Ptr SetupSslContext(String certPath, String keyPath, + String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di); + String GetCertificateCN(const std::shared_ptr& certificate); std::shared_ptr GetX509Certificate(const String& pemfile); int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false); diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index fcf902cfa..45dc69131 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -181,44 +181,7 @@ void ApiListener::OnConfigLoaded() void ApiListener::UpdateSSLContext() { - namespace ssl = boost::asio::ssl; - - Shared::Ptr context; - - try { - context = MakeAsioSslContext(GetDefaultCertPath(), GetDefaultKeyPath(), GetDefaultCaPath()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '" - + GetDefaultCertPath() + "' key path: '" + GetDefaultKeyPath() + "' ca path: '" + GetDefaultCaPath() + "'.", GetDebugInfo())); - } - - if (!GetCrlPath().IsEmpty()) { - try { - AddCRLToSSLContext(context, GetCrlPath()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ScriptError("Cannot add certificate revocation list to SSL context for crl path: '" - + GetCrlPath() + "'.", GetDebugInfo())); - } - } - - if (!GetCipherList().IsEmpty()) { - try { - SetCipherListToSSLContext(context, GetCipherList()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ScriptError("Cannot set cipher list to SSL context for cipher list: '" - + GetCipherList() + "'.", GetDebugInfo())); - } - } - - if (!GetTlsProtocolmin().IsEmpty()){ - try { - SetTlsProtocolminToSSLContext(context, GetTlsProtocolmin()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + GetTlsProtocolmin() + "'.", GetDebugInfo())); - } - } - - m_SSLContext = context; + m_SSLContext = SetupSslContext(GetDefaultCertPath(), GetDefaultKeyPath(), GetDefaultCaPath(), GetCrlPath(), GetCipherList(), GetTlsProtocolmin(), GetDebugInfo()); for (const Endpoint::Ptr& endpoint : ConfigType::GetObjectsByType()) { for (const JsonRpcConnection::Ptr& client : endpoint->GetClients()) { From 7f7637c9b8d3dbb348c0f049b587fc5f10296356 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 16 Jul 2021 18:32:26 +0200 Subject: [PATCH 4/5] Introduce DEFAULT_TLS_CIPHERS and DEFAULT_TLS_PROTOCOLMIN --- lib/base/tlsutility.hpp | 4 ++++ lib/remote/apilistener.ti | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index ce50fb427..0bc1a8332 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -25,6 +25,10 @@ namespace icinga { +const char * const DEFAULT_TLS_CIPHERS = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384:AES128-GCM-SHA256"; + +const char * const DEFAULT_TLS_PROTOCOLMIN = "TLSv1.2"; + void InitializeOpenSSL(); String GetOpenSSLVersion(); diff --git a/lib/remote/apilistener.ti b/lib/remote/apilistener.ti index fa0ad395b..d62402b6e 100644 --- a/lib/remote/apilistener.ti +++ b/lib/remote/apilistener.ti @@ -3,6 +3,7 @@ #include "remote/i2-remote.hpp" #include "base/configobject.hpp" #include "base/application.hpp" +#include "base/tlsutility.hpp" library remote; @@ -18,10 +19,10 @@ class ApiListener : ConfigObject [config, deprecated] String ca_path; [config] String crl_path; [config] String cipher_list { - default {{{ return "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384:AES128-GCM-SHA256"; }}} + default {{{ return DEFAULT_TLS_CIPHERS; }}} }; [config] String tls_protocolmin { - default {{{ return "TLSv1.2"; }}} + default {{{ return DEFAULT_TLS_PROTOCOLMIN; }}} }; [config] String bind_host { From 37e53eaa68784a72cad78e9fa8263a9947c16343 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 Jul 2021 14:34:07 +0200 Subject: [PATCH 5/5] Icinga DB: support TLS --- doc/09-object-types.md | 8 +++ lib/base/tlsutility.cpp | 2 +- lib/icingadb/icingadb.cpp | 31 ++++++++++- lib/icingadb/icingadb.hpp | 4 ++ lib/icingadb/icingadb.ti | 20 +++++++ lib/icingadb/redisconnection.cpp | 92 ++++++++++++++++++++++++++------ lib/icingadb/redisconnection.hpp | 26 +++++++-- 7 files changed, 161 insertions(+), 22 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 19e796aff..4ee242019 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1410,6 +1410,14 @@ Configuration Attributes: port | Number | **Optional.** Redis port for IcingaDB. Defaults to `6380`. path | String | **Optional.** Redix unix socket path. Can be used instead of `host` and `port` attributes. password | String | **Optional.** Redis auth password for IcingaDB. + enable\_tls | Boolean | **Optional.** Whether to use TLS. + cert\_path | String | **Optional.** Path to the certificate. + key\_path | String | **Optional.** Path to the private key. + ca\_path | String | **Optional.** Path to the CA certificate to use instead of the system's root CAs. + crl\_path | String | **Optional.** Path to the CRL file. + cipher\_list | String | **Optional.** Cipher list that is allowed. For a list of available ciphers run `openssl ciphers`. Defaults to `ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384:AES128-GCM-SHA256`. + tls\_protocolmin | String | **Optional.** Minimum TLS protocol version. Defaults to `TLSv1.2`. + insecure\_noverify | Boolean | **Optional.** Whether not to verify the peer. ### IdoMySqlConnection diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 1357ed617..02d677ee0 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -144,7 +144,7 @@ static void InitSslContext(const Shared::Ptr& context << "Error loading system's root CAs: " << ERR_peek_error() << ", \"" << errbuf << "\""; BOOST_THROW_EXCEPTION(openssl_error() << boost::errinfo_api_function("SSL_CTX_set_default_verify_paths") - << errinfo_openssl_error(ERR_peek_error()); + << errinfo_openssl_error(ERR_peek_error())); } } else { if (!SSL_CTX_load_verify_locations(sslContext, cakey.CStr(), nullptr)) { diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 7b05364d1..0fac021fc 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -33,6 +33,18 @@ IcingaDB::IcingaDB() m_PrefixConfigCheckSum = "icinga:checksum:"; } +void IcingaDB::Validate(int types, const ValidationUtils& utils) +{ + ObjectImpl::Validate(types, utils); + + if (!(types & FAConfig)) + return; + + if (GetEnableTls() && GetCertPath().IsEmpty() != GetKeyPath().IsEmpty()) { + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "Validation failed: Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given.")); + } +} + /** * Starts the component. */ @@ -52,7 +64,9 @@ void IcingaDB::Start(bool runtimeCreated) m_WorkQueue.SetExceptionCallback([this](boost::exception_ptr exp) { ExceptionHandler(std::move(exp)); }); - m_Rcon = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex()); + m_Rcon = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), + GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), + GetTlsProtocolmin(), GetCipherList(), GetDebugInfo()); m_Rcon->SetConnectedCallback([this](boost::asio::yield_context& yc) { m_WorkQueue.Enqueue([this]() { OnConnectedHandler(); }); }); @@ -63,7 +77,9 @@ void IcingaDB::Start(bool runtimeCreated) if (!ctype) continue; - RedisConnection::Ptr rCon (new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), m_Rcon)); + RedisConnection::Ptr rCon (new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), + GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), + GetTlsProtocolmin(), GetCipherList(), GetDebugInfo(), m_Rcon)); rCon->Start(); m_Rcons[ctype] = std::move(rCon); } @@ -140,6 +156,17 @@ void IcingaDB::Stop(bool runtimeRemoved) ObjectImpl::Stop(runtimeRemoved); } +void IcingaDB::ValidateTlsProtocolmin(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateTlsProtocolmin(lvalue, utils); + + try { + ResolveTlsProtocolVersion(lvalue()); + } catch (const std::exception& ex) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "tls_protocolmin" }, ex.what())); + } +} + void IcingaDB::AssertOnWorkQueue() { ASSERT(m_WorkQueue.IsWorkerThread()); diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index 964b837b9..07ed75904 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -35,9 +35,13 @@ public: static void ConfigStaticInitialize(); + void Validate(int types, const ValidationUtils& utils) override; virtual void Start(bool runtimeCreated) override; virtual void Stop(bool runtimeRemoved) override; +protected: + void ValidateTlsProtocolmin(const Lazy& lvalue, const ValidationUtils& utils) override; + private: class DumpedGlobals { diff --git a/lib/icingadb/icingadb.ti b/lib/icingadb/icingadb.ti index ebc69e8a6..35299573b 100644 --- a/lib/icingadb/icingadb.ti +++ b/lib/icingadb/icingadb.ti @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/configobject.hpp" +#include "base/tlsutility.hpp" library icingadb; @@ -20,6 +21,25 @@ class IcingaDB : ConfigObject [config] String path; [config, no_user_view, no_user_modify] String password; [config] int db_index; + + [config] bool enable_tls { + default {{{ return false; }}} + }; + + [config] bool insecure_noverify { + default {{{ return false; }}} + }; + + [config] String cert_path; + [config] String key_path; + [config] String ca_path; + [config] String crl_path; + [config] String cipher_list { + default {{{ return DEFAULT_TLS_CIPHERS; }}} + }; + [config] String tls_protocolmin { + default {{{ return DEFAULT_TLS_PROTOCOLMIN; }}} + }; }; } diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index ee1ede89d..9ccdf5d7f 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -4,11 +4,13 @@ #include "base/array.hpp" #include "base/convert.hpp" #include "base/defer.hpp" +#include "base/exception.hpp" #include "base/io-engine.hpp" #include "base/logger.hpp" #include "base/objectlock.hpp" #include "base/string.hpp" #include "base/tcpsocket.hpp" +#include "base/tlsutility.hpp" #include "base/utility.hpp" #include #include @@ -19,23 +21,39 @@ #include #include #include +#include +#include #include using namespace icinga; namespace asio = boost::asio; -RedisConnection::RedisConnection(const String& host, const int port, const String& path, - const String& password, const int db, const RedisConnection::Ptr& parent) : - RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, password, db, parent) +RedisConnection::RedisConnection(const String& host, int port, const String& path, const String& password, int db, + bool useTls, bool insecure, const String& certPath, const String& keyPath, const String& caPath, const String& crlPath, + const String& tlsProtocolmin, const String& cipherList, DebugInfo di, const RedisConnection::Ptr& parent) + : RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, password, db, + useTls, insecure, certPath, keyPath, caPath, crlPath, tlsProtocolmin, cipherList, std::move(di), parent) { } -RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, - String password, int db, const RedisConnection::Ptr& parent) - : m_Host(std::move(host)), m_Port(port), m_Path(std::move(path)), m_Password(std::move(password)), m_DbIndex(db), - m_Connecting(false), m_Connected(false), m_Started(false), m_Strand(io), - m_QueuedWrites(io), m_QueuedReads(io), m_LogStatsTimer(io), m_Parent(parent) +RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, + int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, + String tlsProtocolmin, String cipherList, DebugInfo di, const RedisConnection::Ptr& parent) + : m_Host(std::move(host)), m_Port(port), m_Path(std::move(path)), m_Password(std::move(password)), + m_DbIndex(db), m_CertPath(std::move(certPath)), m_KeyPath(std::move(keyPath)), m_Insecure(insecure), + m_CaPath(std::move(caPath)), m_CrlPath(std::move(crlPath)), m_TlsProtocolmin(std::move(tlsProtocolmin)), + m_CipherList(std::move(cipherList)), m_DebugInfo(std::move(di)), m_Connecting(false), m_Connected(false), + m_Started(false), m_Strand(io), m_QueuedWrites(io), m_QueuedReads(io), m_LogStatsTimer(io), m_Parent(parent) { + if (useTls && m_Path.IsEmpty()) { + UpdateTLSContext(); + } +} + +void RedisConnection::UpdateTLSContext() +{ + m_TLSContext = SetupSslContext(m_CertPath, m_KeyPath, m_CaPath, + m_CrlPath, m_CipherList, m_TlsProtocolmin, m_DebugInfo); } void RedisConnection::Start() @@ -245,12 +263,48 @@ void RedisConnection::Connect(asio::yield_context& yc) for (;;) { try { if (m_Path.IsEmpty()) { - Log(m_Parent ? LogNotice : LogInformation, "IcingaDB") - << "Trying to connect to Redis server (async) on host '" << m_Host << ":" << m_Port << "'"; + if (m_TLSContext) { + Log(m_Parent ? LogNotice : LogInformation, "IcingaDB") + << "Trying to connect to Redis server (async, TLS) on host '" << m_Host << ":" << m_Port << "'"; - auto conn (Shared::Make(m_Strand.context())); - icinga::Connect(conn->next_layer(), m_Host, Convert::ToString(m_Port), yc); - m_TcpConn = std::move(conn); + auto conn (Shared::Make(m_Strand.context(), *m_TLSContext, m_Host)); + auto& tlsConn (conn->next_layer()); + + if (!m_Insecure) { + auto native (tlsConn.native_handle()); + + X509_VERIFY_PARAM_set1_host(SSL_get0_param(native), m_Host.CStr(), 0); + SSL_set_verify(native, SSL_VERIFY_PEER, NULL); + } + + icinga::Connect(conn->lowest_layer(), m_Host, Convert::ToString(m_Port), yc); + tlsConn.async_handshake(tlsConn.client, yc); + + if (!m_Insecure) { + std::shared_ptr cert (tlsConn.GetPeerCertificate()); + + if (!cert) { + BOOST_THROW_EXCEPTION(std::runtime_error( + "Redis didn't present any TLS certificate." + )); + } + + if (!tlsConn.IsVerifyOK()) { + BOOST_THROW_EXCEPTION(std::runtime_error( + "TLS certificate validation failed: " + std::string(tlsConn.GetVerifyError()) + )); + } + } + + m_TlsConn = std::move(conn); + } else { + Log(m_Parent ? LogNotice : LogInformation, "IcingaDB") + << "Trying to connect to Redis server (async) on host '" << m_Host << ":" << m_Port << "'"; + + auto conn (Shared::Make(m_Strand.context())); + icinga::Connect(conn->next_layer(), m_Host, Convert::ToString(m_Port), yc); + m_TcpConn = std::move(conn); + } } else { Log(LogInformation, "IcingaDB") << "Trying to connect to Redis server (async) on unix socket path '" << m_Path << "'"; @@ -560,7 +614,11 @@ void RedisConnection::WriteItem(boost::asio::yield_context& yc, RedisConnection: RedisConnection::Reply RedisConnection::ReadOne(boost::asio::yield_context& yc) { if (m_Path.IsEmpty()) { - return ReadOne(m_TcpConn, yc); + if (m_TLSContext) { + return ReadOne(m_TlsConn, yc); + } else { + return ReadOne(m_TcpConn, yc); + } } else { return ReadOne(m_UnixConn, yc); } @@ -574,7 +632,11 @@ RedisConnection::Reply RedisConnection::ReadOne(boost::asio::yield_context& yc) void RedisConnection::WriteOne(RedisConnection::Query& query, asio::yield_context& yc) { if (m_Path.IsEmpty()) { - WriteOne(m_TcpConn, query, yc); + if (m_TLSContext) { + WriteOne(m_TlsConn, query, yc); + } else { + WriteOne(m_TcpConn, query, yc); + } } else { WriteOne(m_UnixConn, query, yc); } diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index 9c7aaa880..57d6b63c3 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -10,6 +10,7 @@ #include "base/ringbuffer.hpp" #include "base/shared.hpp" #include "base/string.hpp" +#include "base/tlsstream.hpp" #include "base/value.hpp" #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -69,8 +71,11 @@ namespace icinga SyncConnection = 255 }; - RedisConnection(const String& host, const int port, const String& path, - const String& password = "", const int db = 0, const Ptr& parent = nullptr); + RedisConnection(const String& host, int port, const String& path, const String& password, int db, + bool useTls, bool insecure, const String& certPath, const String& keyPath, const String& caPath, const String& crlPath, + const String& tlsProtocolmin, const String& cipherList, DebugInfo di, const Ptr& parent = nullptr); + + void UpdateTLSContext(); void Start(); @@ -134,6 +139,8 @@ namespace icinga typedef boost::asio::buffered_stream TcpConn; typedef boost::asio::buffered_stream UnixConn; + Shared::Ptr m_TLSContext; + template static Value ReadRESP(AsyncReadStream& stream, boost::asio::yield_context& yc); @@ -143,8 +150,9 @@ namespace icinga template static void WriteRESP(AsyncWriteStream& stream, const Query& query, boost::asio::yield_context& yc); - RedisConnection(boost::asio::io_context& io, String host, int port, String path, - String password, int db, const Ptr& parent); + RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, + int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, + String tlsProtocolmin, String cipherList, DebugInfo di, const Ptr& parent); void Connect(boost::asio::yield_context& yc); void ReadLoop(boost::asio::yield_context& yc); @@ -169,9 +177,19 @@ namespace icinga String m_Password; int m_DbIndex; + String m_CertPath; + String m_KeyPath; + bool m_Insecure; + String m_CaPath; + String m_CrlPath; + String m_TlsProtocolmin; + String m_CipherList; + DebugInfo m_DebugInfo; + boost::asio::io_context::strand m_Strand; Shared::Ptr m_TcpConn; Shared::Ptr m_UnixConn; + Shared::Ptr m_TlsConn; Atomic m_Connecting, m_Connected, m_Started; struct {