]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#396,!205] Implemented audit for options.
authorMarcin Siodelski <marcin@isc.org>
Mon, 21 Jan 2019 13:30:53 +0000 (14:30 +0100)
committerMarcin Siodelski <marcin@isc.org>
Wed, 30 Jan 2019 09:18:59 +0000 (10:18 +0100)
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/dhcpdb_drop.mysql

index 69f65af6357ff09a0d1192abc940e81a76ddefa7..864d20b44cb389e752aa61b85c3228082943f65e 100644 (file)
@@ -24,6 +24,7 @@
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
 #include <mysql.h>
 #include <mysqld_error.h>
 #include <array>
@@ -51,7 +52,7 @@ public:
     /// reading the database are less than those of statements modifying the
     /// database.
     enum StatementIndex {
-        SET_AUDIT_LOG_MESSAGE,
+        INIT_AUDIT_REVISION,
         GET_GLOBAL_PARAMETER4,
         GET_ALL_GLOBAL_PARAMETERS4,
         GET_MODIFIED_GLOBAL_PARAMETERS4,
@@ -215,9 +216,11 @@ public:
 
         MySqlTransaction transaction(conn_);
 
-        // Set log message to be used to create the audit revision.
-        conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
-                          { MySqlBinding::createString("this is a log message") });
+        // The last parameter indicates that the parameter is not bound to any
+        // other object (e.g. addition of a subnet), so an audit entry should
+        // be created for the addition of the parameter.
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", true);
 
         // Try to update the existing row.
         if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_GLOBAL_PARAMETER4,
@@ -749,9 +752,14 @@ public:
         MySqlTransaction transaction(conn_);
 
         try {
-            // Set log message to be used to create the audit revision.
-            conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
-                              { MySqlBinding::createString("this is a log message") });
+
+            // The change will involve multiple statements. The audit entry should
+            // be created for the parent object and should not be created for the
+            // DHCP options. The boolean value set to false indicates that the
+            // MySQL triggers should not create audit revision for the DHCP
+            // options associated with the subnet.
+            initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                              "this is log message", false);
 
             // Try to insert subnet. If this duplicates primary key, i.e. this
             // subnet already exists it will throw DuplicateEntry exception in
@@ -795,7 +803,8 @@ public:
             for (auto desc = options->begin(); desc != options->end(); ++desc) {
                 OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc));
                 desc_copy->space_name_ = option_space;
-                createUpdateOption4(server_selector, subnet->getID(), desc_copy);
+                createUpdateOption4(server_selector, subnet->getID(), desc_copy,
+                                    false);
             }
         }
 
@@ -1109,10 +1118,13 @@ public:
         MySqlTransaction transaction(conn_);
 
         try {
-
-            // Set log message to be used to create the audit revision.
-            conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
-                              { MySqlBinding::createString("this is a log message") });
+            // The change will involve multiple statements. The audit entry should
+            // be created for the parent object and should not be created for the
+            // DHCP options. The boolean value set to false indicates that the
+            // MySQL triggers should not create audit revision for the DHCP
+            // options associated with the shared network.
+            initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                              "this is log message", false);
 
             // Try to insert shared network. The shared network name must be unique,
             // so if inserting fails with DuplicateEntry exception we'll need to
@@ -1150,7 +1162,7 @@ public:
                 OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc));
                 desc_copy->space_name_ = option_space;
                 createUpdateOption4(server_selector, shared_network->getName(),
-                                    desc_copy);
+                                    desc_copy, false);
             }
         }
 
@@ -1218,6 +1230,13 @@ public:
         OptionDescriptorPtr existing_option = getOption4(server_selector,
                                                          option->option_->getType(),
                                                          option->space_name_);
+
+        // The last parameter indicates that the option is not bound to any
+        // other object (e.g. addition of a subnet), so an audit entry should
+        // be created for the addition of the option.
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", true);
+
         if (existing_option) {
             in_bindings.push_back(MySqlBinding::createString(tag));
             in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(option->option_->getType()));
@@ -1238,9 +1257,12 @@ public:
     /// @param server_selector Server selector.
     /// @param subnet_id Identifier of the subnet the option belongs to.
     /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param distinct_transaction Boolean value indicating whether setting
+    /// the option value should be enclosed in a separate transaction.
     void createUpdateOption4(const ServerSelector& server_selector,
                              const SubnetID& subnet_id,
-                             const OptionDescriptorPtr& option) {
+                             const OptionDescriptorPtr& option,
+                             const bool distinct_transaction = false) {
 
         if (server_selector.amUnassigned()) {
             isc_throw(NotImplemented, "managing configuration for no particular server"
@@ -1266,11 +1288,21 @@ public:
         };
 
 
-        MySqlTransaction transaction(conn_);
+        boost::scoped_ptr<MySqlTransaction> transaction;
+        // Only start new transaction if specified to do so. This function may
+        // be called from within an existing transaction in which case we
+        // don't start the new one.
+        if (distinct_transaction) {
+            transaction.reset(new MySqlTransaction(conn_));
+        }
 
         OptionDescriptorPtr existing_option = getOption4(server_selector, subnet_id,
                                                          option->option_->getType(),
                                                          option->space_name_);
+
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", distinct_transaction);
+
         if (existing_option) {
             in_bindings.push_back(MySqlBinding::createString(tag));
             in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(static_cast<uint32_t>(subnet_id)));
@@ -1283,7 +1315,9 @@ public:
             insertOption4(server_selector, in_bindings);
         }
 
-        transaction.commit();
+        if (transaction) {
+            transaction->commit();
+        }
     }
 
     /// @brief Sends query to insert or update DHCP option in a pool.
@@ -1305,7 +1339,7 @@ public:
                       << pool_end_address);
         }
 
-        createUpdateOption4(server_selector, pool_id, option);
+        createUpdateOption4(server_selector, pool_id, option, false);
     }
 
 
@@ -1314,9 +1348,12 @@ public:
     /// @param selector Server selector.
     /// @param pool_id Identifier of the pool the option belongs to.
     /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param distinct_transaction Boolean value indicating whether setting
+    /// the option value should be enclosed in a separate transaction.
     void createUpdateOption4(const ServerSelector& server_selector,
                              const uint64_t pool_id,
-                             const OptionDescriptorPtr& option) {
+                             const OptionDescriptorPtr& option,
+                             const bool distinct_transaction = false) {
 
         if (server_selector.amUnassigned()) {
             isc_throw(NotImplemented, "managing configuration for no particular server"
@@ -1341,10 +1378,21 @@ public:
             MySqlBinding::createTimestamp(option->getModificationTime())
         };
 
-        MySqlTransaction transaction(conn_);
+        boost::scoped_ptr<MySqlTransaction> transaction;
+        // Only start new transaction if specified to do so. This function may
+        // be called from within an existing transaction in which case we
+        // don't start the new one.
+        if (distinct_transaction) {
+            transaction.reset(new MySqlTransaction(conn_));
+        }
+
         OptionDescriptorPtr existing_option = getOption4(server_selector, pool_id,
                                                          option->option_->getType(),
                                                          option->space_name_);
+
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", distinct_transaction);
+
         if (existing_option) {
             in_bindings.push_back(MySqlBinding::createString(tag));
             in_bindings.push_back(MySqlBinding::createInteger<uint64_t>(pool_id));
@@ -1357,7 +1405,9 @@ public:
             insertOption4(server_selector, in_bindings);
         }
 
-        transaction.commit();
+        if (transaction) {
+            transaction->commit();
+        }
     }
 
     /// @brief Sends query to insert or update DHCP option in a shared network.
@@ -1366,9 +1416,12 @@ public:
     /// @param shared_network_name Name of the shared network the option
     /// belongs to.
     /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param distinct_transaction Boolean value indicating whether setting
+    /// the option value should be enclosed in a separate transaction.)
     void createUpdateOption4(const ServerSelector& server_selector,
                              const std::string& shared_network_name,
-                             const OptionDescriptorPtr& option) {
+                             const OptionDescriptorPtr& option,
+                             const bool distinct_transaction = false) {
 
         if (server_selector.amUnassigned()) {
             isc_throw(NotImplemented, "managing configuration for no particular server"
@@ -1393,11 +1446,21 @@ public:
             MySqlBinding::createTimestamp(option->getModificationTime())
         };
 
-        MySqlTransaction transaction(conn_);
+        boost::scoped_ptr<MySqlTransaction> transaction;
+        // Only start new transaction if specified to do so. This function may
+        // be called from within an existing transaction in which case we
+        // don't start the new one.
+        if (distinct_transaction) {
+            transaction.reset(new MySqlTransaction(conn_));
+        }
 
         OptionDescriptorPtr existing_option = getOption4(server_selector, shared_network_name,
                                                          option->option_->getType(),
                                                          option->space_name_);
+
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", distinct_transaction);
+
         if (existing_option) {
             in_bindings.push_back(MySqlBinding::createString(tag));
             in_bindings.push_back(MySqlBinding::createString(shared_network_name));
@@ -1410,7 +1473,9 @@ public:
             insertOption4(server_selector, in_bindings);
         }
 
-        transaction.commit();
+        if (transaction) {
+            transaction->commit();
+        }
     }
 
     /// @brief Sends query to retrieve single option definition by code and
@@ -1712,10 +1777,8 @@ public:
                                                                 option_def->getCode(),
                                                                 option_def->getOptionSpaceName());
 
-        // Set log message to be used to create the audit revision.
-        conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
-                          { MySqlBinding::createString("this is a log message") });
-
+        initAuditRevision(MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+                          "this is log message", true);
 
         if (existing_definition) {
             // Need to add three more bindings for WHERE clause.
@@ -1960,8 +2023,8 @@ TaggedStatementArray;
 /// @brief Prepared MySQL statements used by the backend to insert and
 /// retrieve data from the database.
 TaggedStatementArray tagged_statements = { {
-    { MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
-      "SET @audit_log_message = ?"
+    { MySqlConfigBackendDHCPv4Impl::INIT_AUDIT_REVISION,
+      "CALL initAuditRevision(?, ?)"
     },
 
     // Select global parameter by name.
@@ -2521,14 +2584,14 @@ void
 MySqlConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
                                               const std::string& shared_network_name,
                                               const OptionDescriptorPtr& option) {
-    impl_->createUpdateOption4(server_selector, shared_network_name, option);
+    impl_->createUpdateOption4(server_selector, shared_network_name, option, true);
 }
 
 void
 MySqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& server_selector,
                                               const SubnetID& subnet_id,
                                               const OptionDescriptorPtr& option) {
-    impl_->createUpdateOption4(server_selector, subnet_id, option);
+    impl_->createUpdateOption4(server_selector, subnet_id, option, true);
 }
 
 void
index 0087412aab3c9b4554fa7f0a19b8ea1d5f996812..89bb93d26dff5addb200bfbf59947f7adaf3cb8b 100644 (file)
@@ -67,6 +67,17 @@ MySqlConfigBackendImpl::~MySqlConfigBackendImpl() {
     }
 }
 
+void
+MySqlConfigBackendImpl::initAuditRevision(const int index,
+                                          const std::string& log_message,
+                                          const bool distinct_transaction) {
+    MySqlBindingCollection in_bindings = {
+        MySqlBinding::createString("this is a log message"),
+        MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(distinct_transaction))
+    };
+    conn_.insertQuery(index, in_bindings);
+}
+
 void
 MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
                                               const boost::posix_time::ptime& modification_time,
index 6b6f75ceee22f6e5b5b5ce83833f17d3dc3d3aed..f8908ed11e94fa2e719747730eb07eae9ecde651 100644 (file)
@@ -98,6 +98,32 @@ public:
         return (s.str());
     }
 
+    /// @brief Invokes the corresponding stored procedure in MySQL.
+    ///
+    /// The @c initAuditRevision stored procedure initializes several
+    /// session variables used when creating new audit revision in the
+    /// database. That includes setting a log message for the revision,
+    /// setting the boolean value indicating if the audit entries should
+    /// be created for DHCP options (that should only be the case when
+    /// the options are not added as part of the subnet, shared network
+    /// etc.). Finally, it resets the session variables used internally
+    /// by the database to corrdinate between the triggers.
+    ///
+    /// @param index query index.
+    /// @param log_message log message to be used for the audit revision.
+    /// @param distinct_transaction boolean value indicating if a single
+    /// change will be applied in the transaction (distinct transaction)
+    /// or a chain of transactions. The example of the former is when
+    /// an option is added to the existing subnet. The example of the
+    /// latter is when the subnet along with the options is added. This
+    /// consists of two changes (adding the subnet and options) as part
+    /// of the single transaction. The MySQL database needs to
+    /// distinguish between these two cases to bind audit revisions
+    /// to the appropriate objects.
+    void initAuditRevision(const int index,
+                           const std::string& log_message,
+                           const bool distinct_transaction);
+
     /// @brief Sends query to the database to retrieve most recent audit entries.
     ///
     /// @param index Index of the query to be used.
index 52ac6b848e431d4b803bd6bc2950d95d7ac98a17..1177672d197cf8ab23f3891ed6c17c82283c43fc 100644 (file)
@@ -1129,6 +1129,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name);
     EXPECT_TRUE(returned_opt_boot_file_name->equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("CREATE audit entry for an option");
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "this is a log message");
+    }
+
     // Modify option and update it in the database.
     opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
     cbptr_->createUpdateOption4(ServerSelector::ALL(),
@@ -1142,6 +1149,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name);
     EXPECT_TRUE(returned_opt_boot_file_name->equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("UPDATE audit entry for an option");
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
+
     // Deleting an option with explicitly specified server tag should fail.
     EXPECT_EQ(0, cbptr_->deleteOption4(ServerSelector::ONE("server1"),
                                        opt_boot_file_name->option_->getType(),
@@ -1155,6 +1169,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) {
     EXPECT_FALSE(cbptr_->getOption4(ServerSelector::ALL(),
                                     opt_boot_file_name->option_->getType(),
                                     opt_boot_file_name->space_name_));
+
+    {
+        SCOPED_TRACE("DELETE audit entry for an option");
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::DELETE,
+                          "this is a log message");
+    }
 }
 
 // This test verifies that all global options can be retrieved.
@@ -1243,6 +1264,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
                                                     subnet->getID());
     ASSERT_TRUE(returned_subnet);
 
+    {
+        SCOPED_TRACE("CREATE audit entry for a new subnet");
+        testNewAuditEntry("dhcp4_subnet",
+                          AuditEntry::ModificationType::CREATE,
+                          "this is a log message");
+    }
+
     OptionDescriptorPtr opt_boot_file_name = test_options_[0];
     cbptr_->createUpdateOption4(ServerSelector::ALL(), subnet->getID(),
                                 opt_boot_file_name);
@@ -1256,6 +1284,17 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name.option_);
     EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("UPDATE audit entry for an added subnet option");
+        // Instead of adding an audit entry for an option we add an audit
+        // entry for the entire subnet so as the server refreshes the
+        // subnet with the new option. Note that the server doesn't
+        // have means to retrieve only the newly added option.
+        testNewAuditEntry("dhcp4_subnet",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
+
     opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
     cbptr_->createUpdateOption4(ServerSelector::ALL(), subnet->getID(),
                                 opt_boot_file_name);
@@ -1268,6 +1307,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name.option_);
     EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("UPDATE audit entry for an updated subnet option");
+        testNewAuditEntry("dhcp4_subnet",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
+
     // Deleting an option with explicitly specified server tag should fail.
     EXPECT_EQ(0, cbptr_->deleteOption4(ServerSelector::ONE("server1"),
                                        subnet->getID(),
@@ -1284,6 +1330,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
     ASSERT_TRUE(returned_subnet);
 
     EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+    {
+        SCOPED_TRACE("UPDATE audit entry for a deleted subnet option");
+        testNewAuditEntry("dhcp4_subnet",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
 }
 
 // This test verifies that option can be inserted, updated and deleted
@@ -1381,6 +1434,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
                                   shared_network->getName());
     ASSERT_TRUE(returned_network);
 
+    {
+        SCOPED_TRACE("CREATE audit entry for the new shared network");
+        testNewAuditEntry("dhcp4_shared_network",
+                          AuditEntry::ModificationType::CREATE,
+                          "this is a log message");
+    }
+
     OptionDescriptorPtr opt_boot_file_name = test_options_[0];
     cbptr_->createUpdateOption4(ServerSelector::ALL(),
                                 shared_network->getName(),
@@ -1395,6 +1455,17 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name.option_);
     EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("UPDATE audit entry for the added shared network option");
+        // Instead of adding an audit entry for an option we add an audit
+        // entry for the entire shared network so as the server refreshes the
+        // shared network with the new option. Note that the server doesn't
+        // have means to retrieve only the newly added option.
+        testNewAuditEntry("dhcp4_shared_network",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
+
     opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
     cbptr_->createUpdateOption4(ServerSelector::ALL(),
                                 shared_network->getName(),
@@ -1408,6 +1479,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
     ASSERT_TRUE(returned_opt_boot_file_name.option_);
     EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
 
+    {
+        SCOPED_TRACE("UPDATE audit entry for the updated shared network option");
+        testNewAuditEntry("dhcp4_shared_network",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
+
     // Deleting an option with explicitly specified server tag should fail.
     EXPECT_EQ(0, cbptr_->deleteOption4(ServerSelector::ONE("server1"),
                                        shared_network->getName(),
@@ -1423,6 +1501,13 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
                                                  shared_network->getName());
     ASSERT_TRUE(returned_network);
     EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+    {
+        SCOPED_TRACE("UPDATE audit entry for the deleted shared network option");
+        testNewAuditEntry("dhcp4_shared_network",
+                          AuditEntry::ModificationType::UPDATE,
+                          "this is a log message");
+    }
 }
 
 
index a54f3ec153cfc08026193b62c8908e3a58d58774..a9d50999b0f7196d935000e0cebe4f6f02a8ef2f 100644 (file)
@@ -1365,6 +1365,21 @@ ALTER TABLE dhcp4_audit
         REFERENCES dhcp4_audit_revision (id)
         ON DELETE NO ACTION ON UPDATE CASCADE;
 
+-- -----------------------------------------------------
+-- Stored procedure which initializes the session
+-- variables for creation of the new audit revision.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS initAuditRevision;
+DELIMITER $$
+CREATE PROCEDURE initAuditRevision(IN log_message TEXT,
+                                   IN distinct_transaction TINYINT(1))
+BEGIN
+    SET @audit_log_message = log_message;
+    SET @distinct_transaction = distinct_transaction;
+    SET @audit_revision_id = NULL;
+END $$
+DELIMITER ;
+
 -- -----------------------------------------------------
 -- Stored procedure which creates a new entry in the
 -- dhcp4_audit_revision table. This procedure should
@@ -1377,9 +1392,12 @@ DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
 DELIMITER $$
 CREATE PROCEDURE createAuditRevisionDHCP4()
 BEGIN
+    DECLARE local_audit_log_message TEXT;
     IF @audit_revision_id IS NULL THEN
+       SET local_audit_log_message = @audit_log_message;
+       SET @audit_log_message = NULL;
        INSERT INTO dhcp4_audit_revision (modification_ts, log_message)
-           VALUES (NOW(), @audit_log_message);
+           VALUES (NOW(), local_audit_log_message);
        SET @audit_revision_id = LAST_INSERT_ID();
     END IF;
 END $$
@@ -1398,6 +1416,7 @@ CREATE PROCEDURE createAuditEntryDHCP4(IN object_type_val VARCHAR(256),
                                        IN object_id_val BIGINT(20) UNSIGNED,
                                        IN modification_type_val TINYINT(1))
 BEGIN
+    CALL createAuditRevisionDHCP4();
     INSERT INTO dhcp4_audit (object_type, object_id, modification_type, revision_id)
         VALUES (object_type_val, object_id_val, modification_type_val, @audit_revision_id);
 END $$
@@ -1414,7 +1433,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_global_parameter_AINS AFTER INSERT ON dhcp4_global_parameter
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 0);
     END $$
 DELIMITER ;
@@ -1424,7 +1442,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_global_parameter_AUPD AFTER UPDATE ON dhcp4_global_parameter
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 1);
     END $$
 DELIMITER ;
@@ -1434,7 +1451,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_global_parameter_ADEL AFTER DELETE ON dhcp4_global_parameter
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_global_parameter', OLD.id, 2);
     END $$
 DELIMITER ;
@@ -1444,7 +1460,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_subnet_AINS AFTER INSERT ON dhcp4_subnet
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 0);
     END $$
 DELIMITER ;
@@ -1454,7 +1469,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_subnet_AUPD AFTER UPDATE ON dhcp4_subnet
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 1);
     END $$
 DELIMITER ;
@@ -1464,7 +1478,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_subnet_ADEL AFTER DELETE ON dhcp4_subnet
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, 2);
     END $$
 DELIMITER ;
@@ -1474,7 +1487,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_shared_network_AINS AFTER INSERT ON dhcp4_shared_network
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 0);
     END $$
 DELIMITER ;
@@ -1484,7 +1496,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_shared_network_AUPD AFTER UPDATE ON dhcp4_shared_network
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 1);
     END $$
 DELIMITER ;
@@ -1494,7 +1505,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_shared_network_ADEL AFTER DELETE ON dhcp4_shared_network
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 2);
     END $$
 DELIMITER ;
@@ -1504,7 +1514,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_option_def_AINS AFTER INSERT ON dhcp4_option_def
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 0);
     END $$
 DELIMITER ;
@@ -1514,7 +1523,6 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_option_def_AUPD AFTER UPDATE ON dhcp4_option_def
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 1);
     END $$
 DELIMITER ;
@@ -1524,11 +1532,110 @@ DELIMITER $$
 CREATE TRIGGER dhcp4_option_def_ADEL AFTER DELETE ON dhcp4_option_def
     FOR EACH ROW
     BEGIN
-        CALL createAuditRevisionDHCP4();
         CALL createAuditEntryDHCP4('dhcp4_option_def', OLD.id, 2);
     END $$
 DELIMITER ;
 
+-- -----------------------------------------------------
+-- Stored procedure which creates an audit entry for a
+-- DHCPv4 option. Depending on the scope of the option
+-- the audit entry can be created for various levels
+-- of configuration hierarchy. If this is a global
+-- option the audit entry is created for this option
+-- for CREATE, UPDATE or DELETE. If the option is being
+-- added for an owning option, e.g. for a subnet, the
+-- audit entry is created as an UPDATE to this object.
+-- From the Kea perspective such option addition will
+-- be seen as a subnet update and the server will fetch
+-- the whole subnet and merge it into its configuration.
+-- The audit entry is not created if it was already
+-- created as part of the current transaction.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: CREATE, UPDATE or DELETE
+-- - scope_id: identifier of the option scope, e.g.
+--   global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+--   belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+--   belongs to the shared network.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP4(IN modification_type TINYINT(1),
+                                        IN scope_id TINYINT(3) UNSIGNED,
+                                        IN option_id BIGINT(20) UNSIGNED,
+                                        IN subnet_id INT(10) UNSIGNED,
+                                        IN host_id INT(10) UNSIGNED,
+                                        IN network_name VARCHAR(128))
+BEGIN
+    # This variable will hold shared network id that we will retrieve
+    # by matching it name.
+    DECLARE snid VARCHAR(128);
+
+    # Distinct transaction flag is set when audit entry must be
+    # associated with the option. For example: adding a global
+    # option or adding an option to the existing subnet etc.
+    IF @distinct_transaction != 0 THEN
+        IF scope_id = 0 THEN
+            # If a global option is added or modified, create audit
+            # entry for the 'dhcp4_options' table.
+            CALL createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+        ELSEIF scope_id = 1 THEN
+            # If subnet specific option is added or modified, create
+            # audit entry for the entire subnet, which indicates that
+            # it should be treated as the subnet update.
+            CALL createAuditEntryDHCP4('dhcp4_subnet', subnet_id, 1);
+        ELSEIF scope_id = 3 THEN
+            # If host specific option is added or modified, create
+            # audit entry for the host, which indicates that it
+            # should be treated as the host update.
+            CALL createAuditEntryDHCP4('hosts', host_id, 1);
+        ELSEIF scope_id = 4 THEN
+            # If shared network specific option is added or modified,
+            # created audit entry for the shared network which
+            # indicates that it should be treated as the shared
+            # network update.
+           SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+           CALL createAuditEntryDHCP4('dhcp4_shared_network', snid, 1);
+        END IF;
+    END IF;
+END $$
+DELIMITER ;
+
+# Create dhcp4_options insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AINS AFTER INSERT ON dhcp4_options
+    FOR EACH ROW
+    BEGIN
+        CALL createOptionAuditDHCP4(0, NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+                                    NEW.host_id, NEW.shared_network_name);
+    END $$
+DELIMITER ;
+
+# Create dhcp4_options update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AUPD AFTER UPDATE ON dhcp4_options
+    FOR EACH ROW
+    BEGIN
+        CALL createOptionAuditDHCP4(1, NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+                                    NEW.host_id, NEW.shared_network_name);
+    END $$
+DELIMITER ;
+
+# Create dhcp4_options delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_ADEL AFTER DELETE ON dhcp4_options
+    FOR EACH ROW
+    BEGIN
+        CALL createOptionAuditDHCP4(2, OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+                                    OLD.host_id, OLD.shared_network_name);
+    END $$
+DELIMITER ;
+
 
 # Update the schema version number
 UPDATE schema_version
index 0a183d6573060068a4cbbfc3ba49ac042f11c98d..2a41667d9c69d543eafd994db3c6b3c817d08fd5 100644 (file)
@@ -59,6 +59,7 @@ DROP TABLE IF EXISTS dhcp6_shared_network_server;
 DROP TABLE IF EXISTS dhcp6_subnet;
 DROP TABLE IF EXISTS dhcp6_subnet_server;
 DROP TABLE IF EXISTS modification;
+DROP PROCEDURE IF EXISTS initAuditRevision;
 DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
 DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
 DROP TRIGGER IF EXISTS dhcp4_global_parameter_AINS;
@@ -73,3 +74,6 @@ DROP TRIGGER IF EXISTS dhcp4_shared_network_ADEL;
 DROP TRIGGER IF EXISTS dhcp4_option_def_AINS;
 DROP TRIGGER IF EXISTS dhcp4_option_def_AUPD;
 DROP TRIGGER IF EXISTS dhcp4_option_def_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_options_AINS;
+DROP TRIGGER IF EXISTS dhcp4_options_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_options_ADEL;