]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3569] add support for legal log syslog
authorRazvan Becheriu <razvan@isc.org>
Fri, 28 Mar 2025 22:44:08 +0000 (00:44 +0200)
committerRazvan Becheriu <razvan@isc.org>
Mon, 31 Mar 2025 15:49:45 +0000 (18:49 +0300)
14 files changed:
doc/sphinx/arm/hooks-legal-log.rst
src/hooks/dhcp/forensic_log/Makefile.am
src/hooks/dhcp/forensic_log/legal_log_messages.cc
src/hooks/dhcp/forensic_log/legal_log_messages.h
src/hooks/dhcp/forensic_log/legal_log_messages.mes
src/hooks/dhcp/forensic_log/legal_syslog.cc [new file with mode: 0644]
src/hooks/dhcp/forensic_log/legal_syslog.h [new file with mode: 0644]
src/hooks/dhcp/forensic_log/libdhcp_legal_log.dox
src/hooks/dhcp/forensic_log/load_unload.cc
src/hooks/dhcp/forensic_log/meson.build
src/hooks/dhcp/forensic_log/rotating_file.h
src/lib/dhcpsrv/legal_log_mgr.cc
src/lib/dhcpsrv/legal_log_mgr.h
src/lib/dhcpsrv/legal_log_mgr_factory.cc

index dcb49aabcd1fef32be0f5c4a33de648cd9f13adc..8b9b1da6f6315369398f9599e0898ca03ba052b5 100644 (file)
@@ -1053,7 +1053,7 @@ table is part of the Kea database schemas.
 
 Configuration parameters are extended by standard lease database
 parameters as defined in :ref:`database-configuration4`. The ``type``
-parameter should be ``mysql``, ``postgresql`` or ``logfile``; when
+parameter should be ``mysql``, ``postgresql``, ``logfile`` or ``syslog``; when
 it is absent or set to ``logfile``, files are used.
 
 No specific tools are provided to operate the database, but standard
@@ -1091,3 +1091,19 @@ system allows nanny scripts to detect the problem.
 If ``retry-on-startup`` is set to ``true``, the server starts reconnection
 attempts even at server startup or on reconfigure events, and honors the
 action specified in the ``on-fail`` parameter.
+
+.. _forensic-log-syslog:
+
+Syslog Backend
+~~~~~~~~~~~~~~
+
+Log entries can be inserted into syslog by setting the ``type`` to ``syslog``.
+When syslog type is configured, the ``pattern`` parameter specifies the details that
+are used for logging. For more details see :ref:`logging`. If not configured, it defaults
+to:
+
+::
+
+    "%-5p [%c.%t] %m\n"
+
+The ``facility`` parameter specifies the syslog facility and it defaults to ``local0``.
index c5a747a3c8799cd77156e68780c60cd4095c0022..ff11cdd5cfee75e4e212dca39305d9578e520430 100644 (file)
@@ -32,6 +32,7 @@ liblegl_la_SOURCES += lease4_callouts.cc
 liblegl_la_SOURCES += lease6_callouts.cc
 liblegl_la_SOURCES += legal_log_log.cc legal_log_log.h
 liblegl_la_SOURCES += legal_log_messages.cc legal_log_messages.h
+liblegl_la_SOURCES += legal_syslog.cc legal_syslog.h
 liblegl_la_SOURCES += rotating_file.cc rotating_file.h
 liblegl_la_SOURCES += subnets_user_context.h
 liblegl_la_SOURCES += version.cc
index c32dcd4264e6daadb371e6ad337a5a05cbc95251..b75505eba5b322362d25f75b88b9702b7b545c05 100644 (file)
@@ -17,6 +17,8 @@ extern const isc::log::MessageID LEGAL_LOG_STORE_CLOSE_ERROR = "LEGAL_LOG_STORE_
 extern const isc::log::MessageID LEGAL_LOG_STORE_OPEN = "LEGAL_LOG_STORE_OPEN";
 extern const isc::log::MessageID LEGAL_LOG_STORE_OPENED = "LEGAL_LOG_STORE_OPENED";
 extern const isc::log::MessageID LEGAL_LOG_UNLOAD_ERROR = "LEGAL_LOG_UNLOAD_ERROR";
+extern const isc::log::MessageID LEGAL_SYSLOG_LOG = "LEGAL_SYSLOG_LOG";
+extern const isc::log::MessageID LEGAL_SYSLOG_STORE_OPEN = "LEGAL_SYSLOG_STORE_OPEN";
 
 namespace {
 
@@ -34,6 +36,8 @@ const char* values[] = {
     "LEGAL_LOG_STORE_OPEN", "opening Legal Log file: %1",
     "LEGAL_LOG_STORE_OPENED", "Legal store opened: %1",
     "LEGAL_LOG_UNLOAD_ERROR", "An error occurred unloading the library: %1",
+    "LEGAL_SYSLOG_LOG", "%1",
+    "LEGAL_SYSLOG_STORE_OPEN", "opening Legal Syslog: %1",
     NULL
 };
 
index ebdb7611483b511af4a7348da16557b3cb070d4b..ca50d8e1b17d44f2918b26e12a68f38a13b21cbb 100644 (file)
@@ -18,5 +18,7 @@ extern const isc::log::MessageID LEGAL_LOG_STORE_CLOSE_ERROR;
 extern const isc::log::MessageID LEGAL_LOG_STORE_OPEN;
 extern const isc::log::MessageID LEGAL_LOG_STORE_OPENED;
 extern const isc::log::MessageID LEGAL_LOG_UNLOAD_ERROR;
+extern const isc::log::MessageID LEGAL_SYSLOG_LOG;
+extern const isc::log::MessageID LEGAL_SYSLOG_STORE_OPEN;
 
 #endif // LEGAL_LOG_MESSAGES_H
index 764d92123f5340128724a189f348ffa0b6a4b1a1..7d6320f88711f4bd33007ff7630880448cb66218 100644 (file)
@@ -62,6 +62,14 @@ This informational message is logged when a DHCP server (either V4 or
 V6) is about to open a legal log file. The parameters of
 the backend are logged.
 
+% LEGAL_SYSLOG_STORE_OPEN opening Legal Syslog: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a legal syslog store. The parameters of
+the backend are logged.
+
+% LEGAL_SYSLOG_LOG %1
+This informational message contains the message being logged to syslog.
+
 % LEGAL_LOG_STORE_OPENED Legal store opened: %1
 This is an informational message issued when the Legal Log library
 has successfully opened the legal store.
diff --git a/src/hooks/dhcp/forensic_log/legal_syslog.cc b/src/hooks/dhcp/forensic_log/legal_syslog.cc
new file mode 100644 (file)
index 0000000..16efa17
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (C) 2016-2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcpsrv/cfgmgr.h>
+#include <legal_log_log.h>
+#include <legal_syslog.h>
+#include <log/logger_manager.h>
+#include <log/message_initializer.h>
+#include <log/macros.h>
+#include <process/logging_info.h>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::log;
+using namespace isc::process;
+using namespace std;
+
+namespace isc {
+namespace legal_log {
+
+LegalSyslog::LegalSyslog(const DatabaseConnection::ParameterMap& parameters)
+    : LegalLogMgr(parameters) {
+    LoggingInfo info;
+    // Remove default destinations as we are going to replace them.
+    info.clearDestinations();
+    info.name_ = "legal-log-syslog-";
+    info.name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    logger_.reset(new Logger(info.name_.c_str()));
+    LoggingDestination dest;
+    dest.output_ = "syslog:";
+    if (parameters.find("facility") != parameters.end()) {
+        dest.output_ += parameters.at("facility");
+    } else {
+        dest.output_ += "LOCAL0";
+    }
+    if (parameters.find("pattern") != parameters.end()) {
+        dest.pattern_ = parameters.at("pattern");
+    }
+    info.destinations_.push_back(dest);
+    CfgMgr::instance().getStagingCfg()->addLoggingInfo(info);
+}
+
+void
+LegalSyslog::open() {
+}
+
+void
+LegalSyslog::close() {
+}
+
+void
+LegalSyslog::writeln(const string& text, const string&) {
+    LOG_INFO(*logger_, LEGAL_SYSLOG_LOG)
+        .arg(text);
+}
+
+string
+LegalSyslog::getType() const {
+    return ("syslog");
+}
+
+LegalLogMgrPtr
+LegalSyslog::factory(const DatabaseConnection::ParameterMap& parameters) {
+    LOG_INFO(legal_log_logger, LEGAL_SYSLOG_STORE_OPEN)
+        .arg(DatabaseConnection::redactedAccessString(parameters));
+    return (LegalLogMgrPtr(new LegalSyslog(parameters)));
+}
+
+} // namespace legal_log
+} // namespace isc
diff --git a/src/hooks/dhcp/forensic_log/legal_syslog.h b/src/hooks/dhcp/forensic_log/legal_syslog.h
new file mode 100644 (file)
index 0000000..781619c
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LEGAL_SYSLOG_H
+#define LEGAL_SYSLOG_H
+
+#include <dhcpsrv/legal_log_mgr_factory.h>
+
+namespace isc {
+namespace legal_log {
+
+class LegalSyslog : public isc::dhcp::LegalLogMgr {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the manager configuration.
+    LegalSyslog(const isc::db::DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Destructor.
+    ///
+    /// The destructor does call the close method.
+    virtual ~LegalSyslog() = default;
+
+    /// @brief Opens the store.
+    virtual void open();
+
+    /// @brief Closes the store.
+    virtual void close();
+
+    /// @brief Appends a string to the store with a timestamp and address.
+    ///
+    /// @param text String to append
+    /// @param addr Address or prefix
+    /// @throw LegalLogMgrError if the write fails
+    virtual void writeln(const std::string& text, const std::string& addr);
+
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "logfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const;
+
+private:
+    /// @brief Logger used to write to syslog.
+    std::shared_ptr<isc::log::Logger> logger_;
+
+public:
+    /// @brief Factory class method.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    ///
+    /// @return The Rotating File Store Backend.
+    static isc::dhcp::LegalLogMgrPtr
+    factory(const isc::db::DatabaseConnection::ParameterMap& parameters);
+};
+
+} // namespace legal_log
+} // namespace isc
+
+#endif
index e9744e4973255a015cb092ef9070c3cf8c69275e..bf5f892b549bc84600f5f84394859a930964bbf9 100644 (file)
@@ -497,6 +497,14 @@ Design notes:
  - the address (or prefix) column can be null / empty.
   This allows to insert no address related log entries.
 
+## Syslog
+
+Log entries can be inserted into syslog by setting the "type" to "syslog".
+When syslog type is configured, the "pattern" parameter specifies the details that
+are used for logging. If not configured, it defaults to: "%-5p [%c.%t] %m\n".
+
+The "facility" parameter specifies the syslog facility and it defaults to "local0".
+
 @section libdhcp_legal_logMTCompatibility Multi-Threading Compatibility
 
 The libdhcp_legal_log hooks library is compatible with multi-threading.
index d4f2315da77537e31481c302d3acc3ba620321a4..ec377d05f039d949ea1207f27e6ef4c049e80e28 100644 (file)
@@ -16,6 +16,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <process/daemon.h>
 #include <legal_log_log.h>
+#include <legal_syslog.h>
 #include <dhcpsrv/legal_log_mgr.h>
 #include <rotating_file.h>
 
@@ -63,6 +64,7 @@ int load(LibraryHandle& handle) {
         }
 
         LegalLogMgrFactory::registerBackendFactory("logfile", RotatingFile::factory);
+        LegalLogMgrFactory::registerBackendFactory("syslog", LegalSyslog::factory);
 
         // Get and decode parameters.
         ConstElementPtr const& parameters(handle.getParameters());
@@ -105,6 +107,7 @@ int unload() {
         LegalLogMgrFactory::delAllBackends();
 
         LegalLogMgrFactory::unregisterBackendFactory("logfile");
+        LegalLogMgrFactory::unregisterBackendFactory("syslog");
     } catch (const std::exception& ex) {
         // On the off chance something goes awry, catch it and log it.
         // @todo Not sure if we should return a non-zero result or not.
index 90d50460b9b75a09ee61fe7b5968d7d75e8007c2..6f50cc12ad69ca06d8307dbce0b262de9a0c4953 100644 (file)
@@ -5,6 +5,7 @@ dhcp_forensic_log_lib = shared_library(
     'lease6_callouts.cc',
     'legal_log_log.cc',
     'legal_log_messages.cc',
+    'legal_syslog.cc',
     'load_unload.cc',
     'rotating_file.cc',
     'version.cc',
index 190de41edd7698f0fba3c6a626aefb461574d2de..866e0ee5e7dbdc88654268999a7b63e8c1df360e 100644 (file)
@@ -67,13 +67,8 @@ public:
     /// 'count' number of days, months or years (when the write function call
     /// detects that the day, month or year has changed).
     ///
-    /// @param path Directory in which file(s) will be created.
-    /// @param base_name Base file name to use when creating files.
-    /// @param unit The time unit used to rotate the file.
-    /// @param count The number of time units used to rotate the file (0 means
-    /// disabled).
-    /// @param prerotate The script to be run before closing the old file.
-    /// @param postrotate The script to be run after opening the new file.
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the manager configuration.
     ///
     /// @throw LegalLogMgrError if given file name is empty.
     RotatingFile(const isc::db::DatabaseConnection::ParameterMap& parameters);
index cbf14e18fa549308374dceaf442a2e50f7ba5e39..e6905a64681ee46dc7679d24a88196a01f96baa5 100644 (file)
@@ -38,6 +38,8 @@ LegalLogMgr::parseConfig(const ConstElementPtr& parameters, DatabaseConnection::
     if (!parameters || !parameters->get("type") ||
         parameters->get("type")->stringValue() == "logfile") {
         parseFile(parameters, map);
+    } else if (parameters->get("type")->stringValue() == "syslog") {
+        parseSyslog(parameters, map);
     } else {
         parseDatabase(parameters, map);
     }
@@ -128,6 +130,27 @@ LegalLogMgr::parseDatabase(const ConstElementPtr& parameters, DatabaseConnection
     map = db_parameters;
 }
 
+void
+LegalLogMgr::parseSyslog(const ConstElementPtr& parameters, DatabaseConnection::ParameterMap& map) {
+    // Should never happen with the code flow at the time of writing, but
+    // let's get this check out of the way.
+    if (!parameters) {
+        isc_throw(BadValue, "no parameters specified for the hook library");
+    }
+
+    DatabaseConnection::ParameterMap syslog_parameters;
+
+    // Strings
+    for (char const* const& key : { "type", "pattern", "facility" }) {
+        ConstElementPtr const value(parameters->get(key));
+        if (value) {
+            syslog_parameters.emplace(key, value->stringValue());
+        }
+    }
+
+    map = syslog_parameters;
+}
+
 void
 LegalLogMgr::parseFile(const ConstElementPtr& parameters, DatabaseConnection::ParameterMap& map) {
     DatabaseConnection::ParameterMap file_parameters;
index 70acb1cee551bbabafc0bfefaef653e6f646a32a..d2c5bd654f97db2edc2b2af564f456a8ab7204e5 100644 (file)
@@ -39,6 +39,9 @@ typedef boost::shared_ptr<LegalLogMgr> LegalLogMgrPtr;
 class LegalLogMgr {
 public:
     /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the manager configuration.
     LegalLogMgr(const isc::db::DatabaseConnection::ParameterMap parameters) :
         timestamp_format_("%Y-%m-%d %H:%M:%S %Z"),
         parameters_(parameters) {
@@ -78,6 +81,15 @@ public:
     /// @param [out] map The parameter map.
     static void parseFile(const isc::data::ConstElementPtr& parameters, isc::db::DatabaseConnection::ParameterMap& map);
 
+    /// @brief Parse syslog specification.
+    ///
+    /// Parse the configuration and check that the various keywords are
+    /// consistent.
+    ///
+    /// @param parameters The library parameters.
+    /// @param [out] map The parameter map.
+    static void parseSyslog(const isc::data::ConstElementPtr& parameters, isc::db::DatabaseConnection::ParameterMap& map);
+
     /// @brief Parse extra parameters which are not related to backend
     /// connection.
     ///
index 20ff27f6d923b43ee4ec44a8187eed13feb73caa..881d717a7b7607f1988d7bea4996b1ef61c52a23 100644 (file)
@@ -97,7 +97,7 @@ LegalLogMgrFactory::addBackend(DatabaseConnection::ParameterMap& parameters, Man
     // Call the factory and push the pointer on sources.
     auto backend = index->second.first(parameters);
     if (!backend) {
-        isc_throw(Unexpected, "Forensic log database " << db_type <<
+        isc_throw(Unexpected, "Forensic log backend " << db_type <<
                   " factory returned NULL");
     }