From 98f60fd78ebee9a657eef035e03d6b7a1fd921b8 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 19 Sep 2024 16:10:07 +0200 Subject: [PATCH] Icinga DB: Support Redis username authentication The Redis ACL system was introduced with Redis 6.0. It introduced users with precisely granular permissions. This change allows Icinga 2 to use the Icinga DB feature against a Redis with an ACL user. This was reflected in the documentation, next to the already implemented, but undocumented Redis database. Closes #9536. --- doc/09-object-types.md | 1 + lib/icingadb/icingadb.cpp | 9 +++++++-- lib/icingadb/icingadb.ti | 1 + lib/icingadb/redisconnection.cpp | 8 ++++---- lib/icingadb/redisconnection.hpp | 9 ++++++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 44c52da13..c2d5e5eff 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1387,6 +1387,7 @@ Configuration Attributes: host | String | **Optional.** Redis host. Defaults to `127.0.0.1`. port | Number | **Optional.** Redis port. Defaults to `6380` since the Redis server provided by the `icingadb-redis` package listens on that port. path | String | **Optional.** Redis unix socket path. Can be used instead of `host` and `port` attributes. + username | String | **Optional.** Redis auth username. Only possible if Redis ACLs are used. Requires `password` to be set as well. password | String | **Optional.** Redis auth password. db\_index | Number | **Optional.** Redis logical database by its number. Defaults to `0`. enable\_tls | Boolean | **Optional.** Whether to use TLS. diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 6d5ded9cf..80e297e4a 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -47,6 +47,11 @@ void IcingaDB::Validate(int types, const ValidationUtils& utils) if (!(types & FAConfig)) return; + if (!GetUsername().IsEmpty() && GetPassword().IsEmpty()) { + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), + "Redis password must be set, if username is provided.")); + } + 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.")); } @@ -77,7 +82,7 @@ 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(), GetUsername(), GetPassword(), GetDbIndex(), GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo()); m_RconLocked.store(m_Rcon); @@ -87,7 +92,7 @@ void IcingaDB::Start(bool runtimeCreated) if (!ctype) continue; - RedisConnection::Ptr con = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), + RedisConnection::Ptr con = new RedisConnection(GetHost(), GetPort(), GetPath(), GetUsername(), GetPassword(), GetDbIndex(), GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo(), m_Rcon); diff --git a/lib/icingadb/icingadb.ti b/lib/icingadb/icingadb.ti index 2cc54ccc1..c4037b14d 100644 --- a/lib/icingadb/icingadb.ti +++ b/lib/icingadb/icingadb.ti @@ -19,6 +19,7 @@ class IcingaDB : ConfigObject default {{{ return 6380; }}} }; [config, no_user_modify] String path; + [config, no_user_modify] String username; [config, no_user_view, no_user_modify] String password; [config, no_user_modify] int db_index; diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index 798a8279b..c187d7f1e 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -30,18 +30,18 @@ namespace asio = boost::asio; boost::regex RedisConnection::m_ErrAuth ("\\AERR AUTH "); -RedisConnection::RedisConnection(const String& host, int port, const String& path, const String& password, int db, +RedisConnection::RedisConnection(const String& host, int port, const String& path, const String& username, 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, double connectTimeout, DebugInfo di, const RedisConnection::Ptr& parent) - : RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, password, db, + : RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, username, password, db, useTls, insecure, certPath, keyPath, caPath, crlPath, tlsProtocolmin, cipherList, connectTimeout, std::move(di), parent) { } -RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, +RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, String username, String password, int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, String tlsProtocolmin, String cipherList, double connectTimeout, 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_Host(std::move(host)), m_Port(port), m_Path(std::move(path)), m_Username(std::move(username)), 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_ConnectTimeout(connectTimeout), m_DebugInfo(std::move(di)), m_Connecting(false), m_Connected(false), diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index f346ba285..fecd236f9 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -84,7 +84,7 @@ namespace icinga : Config(config), State(state), History(history) { } }; - RedisConnection(const String& host, int port, const String& path, const String& password, int db, + RedisConnection(const String& host, int port, const String& path, const String& username, 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, double connectTimeout, DebugInfo di, const Ptr& parent = nullptr); @@ -196,7 +196,7 @@ namespace icinga static boost::regex m_ErrAuth; - RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, + RedisConnection(boost::asio::io_context& io, String host, int port, String path, String username, String password, int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, String tlsProtocolmin, String cipherList, double connectTimeout, DebugInfo di, const Ptr& parent); @@ -227,6 +227,7 @@ namespace icinga String m_Path; String m_Host; int m_Port; + String m_Username; String m_Password; int m_DbIndex; @@ -457,7 +458,9 @@ void RedisConnection::Handshake(StreamPtr& strm, boost::asio::yield_context& yc) // Trigger NOAUTH WriteRESP(*strm, {"PING"}, yc); } else { - if (!m_Password.IsEmpty()) { + if (!m_Username.IsEmpty()) { + WriteRESP(*strm, {"AUTH", m_Username, m_Password}, yc); + } else if (!m_Password.IsEmpty()) { WriteRESP(*strm, {"AUTH", m_Password}, yc); }