-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
/// reading the database are less than those of statements modifying the
/// database.
enum StatementIndex {
+ SET_AUDIT_LOG_MESSAGE,
GET_GLOBAL_PARAMETER4,
GET_ALL_GLOBAL_PARAMETERS4,
GET_MODIFIED_GLOBAL_PARAMETERS4,
GET_OPTION4_SUBNET_ID_CODE_SPACE,
GET_OPTION4_POOL_ID_CODE_SPACE,
GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
+ GET_AUDIT_ENTRIES4_TIME,
INSERT_GLOBAL_PARAMETER4,
INSERT_GLOBAL_PARAMETER4_SERVER,
INSERT_SUBNET4,
explicit MySqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap&
parameters);
+ /// @brief Sends query to insert an audit entry.
+ ///
+ /// @param in_bindings Collection of bindings representing an option.
+ void insertAuditEntry4(const MySqlBindingCollection& in_bindings) {
+ // Fetch unique identifier of the inserted option.
+ uint64_t id = mysql_insert_id(conn_.mysql_);
+
+ // Create bindings needed to insert association of that option with
+ // a server into the dhcp4_options_server table.
+ }
+
/// @brief Sends query to retrieve multiple global parameters.
///
/// @param index Index of the query to be used.
StampedValuePtr stamped_value(new StampedValue(out_bindings[1]->getString(),
out_bindings[2]->getString()));
stamped_value->setModificationTime(out_bindings[3]->getTimestamp());
- parameters.push_back(stamped_value);
+ parameters.insert(stamped_value);
}
});
}
MySqlTransaction transaction(conn_);
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::SET_AUDIT_LOG_MESSAGE,
+ { MySqlBinding::createString("this is a log message") });
+
// Try to update the existing row.
if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_GLOBAL_PARAMETER4,
in_bindings) == 0) {
// No such parameter found, so let's insert it. We have to adjust the
// bindings collection to match the prepared statement for insert.
in_bindings.pop_back();
+ in_bindings.pop_back();
conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4,
in_bindings);
// Insert association.
conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4_SERVER,
in_server_bindings);
+
}
transaction.commit();
/// @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 = ?"
+ },
+
// Select global parameter by name.
{ MySqlConfigBackendDHCPv4Impl::GET_GLOBAL_PARAMETER4,
MYSQL_GET_GLOBAL_PARAMETER(dhcp4, AND g.name = ?)
AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?)
},
+ // Retrieves the most recent audit entries.
+ { MySqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
+ MYSQL_GET_AUDIT_ENTRIES_TIME(dhcp4)
+ },
+
// Insert global parameter.
{ MySqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4,
MYSQL_INSERT_GLOBAL_PARAMETER(dhcp4)
return (parameters);
}
+AuditEntryCollection
+MySqlConfigBackendDHCPv4::
+getRecentAuditEntries4(const db::ServerSelector&,
+ const boost::posix_time::ptime& modification_time) const {
+ AuditEntryCollection audit_entries;
+ impl_->getRecentAuditEntries(MySqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
+ modification_time, audit_entries);
+
+ return (audit_entries);
+}
+
void
MySqlConfigBackendDHCPv4::createUpdateSubnet4(const ServerSelector& server_selector,
const Subnet4Ptr& subnet) {
getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
const boost::posix_time::ptime& modification_time) const;
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
/// @brief Creates or updates a subnet.
///
/// @param server_selector Server selector.
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
}
}
+void
+MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
+ const boost::posix_time::ptime& modification_time,
+ AuditEntryCollection& audit_entries) {
+ // Create the output bindings for receiving the data.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint64_t>(),
+ MySqlBinding::createString(AUDIT_ENTRY_OBJECT_TYPE_BUF_LENGTH),
+ MySqlBinding::createInteger<uint64_t>(),
+ MySqlBinding::createInteger<uint8_t>(),
+ MySqlBinding::createTimestamp(),
+ MySqlBinding::createString(AUDIT_ENTRY_LOG_MESSAGE_BUF_LENGTH)
+ };
+
+ // There is only one input binding, modification time.
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createTimestamp(modification_time)
+ };
+
+ // Execute select.
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&audit_entries] (MySqlBindingCollection& out_bindings) {
+ // Convert the numeric modification type into modification type enum.
+ AuditEntry::ModificationType mod_type =
+ static_cast<AuditEntry::ModificationType>(out_bindings[3]->getInteger<uint8_t>());
+
+ // Create new audit entry and add it to the collection of received
+ // entries.
+ AuditEntryPtr audit_entry(new AuditEntry(out_bindings[1]->getString(),
+ out_bindings[2]->getInteger<uint64_t>(),
+ mod_type,
+ out_bindings[4]->getTimestamp(),
+ out_bindings[5]->getStringOrDefault("")));
+ audit_entries.insert(audit_entry);
+ });
+}
+
uint64_t
MySqlConfigBackendImpl::deleteFromTable(const int index) {
MySqlBindingCollection in_bindings;
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
#ifndef MYSQL_CONFIG_BACKEND_IMPL_H
#define MYSQL_CONFIG_BACKEND_IMPL_H
+#include <database/audit_entry.h>
#include <database/database_connection.h>
#include <database/server_selector.h>
#include <dhcp/option.h>
return (s.str());
}
+ /// @brief Sends query to the database to retrieve most recent audit entries.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param [out] audit_entries Reference to the container where fetched audit
+ /// entries will be inserted.
+ void getRecentAuditEntries(const int index,
+ const boost::posix_time::ptime& modification_time,
+ db::AuditEntryCollection& audit_entries);
+
/// @brief Sends query to delete rows from a table.
///
/// @param index Index of the statement to be executed.
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
" ORDER BY o.option_id"
#endif
+#ifndef MYSQL_GET_AUDIT_ENTRIES_TIME
+#define MYSQL_GET_AUDIT_ENTRIES_TIME(table_prefix) \
+ "SELECT" \
+ " a.id," \
+ " a.object_type," \
+ " a.object_id," \
+ " a.modification_type," \
+ " r.modification_ts," \
+ " r.log_message " \
+ "FROM " #table_prefix "_audit AS a " \
+ "LEFT JOIN " #table_prefix "_audit_revision AS r " \
+ " ON a.revision_id = r.id " \
+ "WHERE (r.modification_ts > ?) " \
+ "ORDER BY r.modification_ts"
+#endif
+
#ifndef MYSQL_INSERT_GLOBAL_PARAMETER
#define MYSQL_INSERT_GLOBAL_PARAMETER(table_prefix) \
"INSERT INTO " #table_prefix "_global_parameter(" \
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
- boost::posix_time::hours(1);
// Yesterday.
timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+ // Two days ago.
+ timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
// Tomorrow.
timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
}
cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
global_parameter);
+ AuditEntryCollection audit_entries =
+ cbptr_->getRecentAuditEntries4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ ASSERT_EQ(1, audit_entries.size());
+
+ auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeTag>();
+ auto audit_entry = mod_time_idx.begin();
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::CREATE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
+
// Verify returned parameter and the modification time.
StampedValuePtr returned_global_parameter =
cbptr_->getGlobalParameter4(ServerSelector::ALL(), "global");
EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
global_parameter->getModificationTime());
+ // There should now be two audit entries.
+ audit_entries = cbptr_->getRecentAuditEntries4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ ASSERT_EQ(2, audit_entries.size());
+
+ mod_time_idx = audit_entries.get<AuditEntryModificationTimeTag>();
+ audit_entry = mod_time_idx.begin();
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::CREATE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
+
+ ++audit_entry;
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::UPDATE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
+
// Should not delete parameter specified for all servers if explicit
// server name is provided.
EXPECT_EQ(0, cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server1"),
returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
"global");
EXPECT_FALSE(returned_global_parameter);
+
+ // There should now be three audit entries.
+ audit_entries = cbptr_->getRecentAuditEntries4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ ASSERT_EQ(3, audit_entries.size());
+
+ mod_time_idx = audit_entries.get<AuditEntryModificationTimeTag>();
+ audit_entry = mod_time_idx.begin();
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::CREATE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
+
+ ++audit_entry;
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::UPDATE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
+
+ ++audit_entry;
+ EXPECT_EQ("dhcp4_global_parameter", (*audit_entry)->getObjectType());
+ EXPECT_EQ(AuditEntry::ModificationType::DELETE, (*audit_entry)->getModificationType());
+ EXPECT_EQ("this is a log message", (*audit_entry)->getLogMessage());
}
// This test verifies that all global parameters can be retrieved and deleted.
auto parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ALL());
ASSERT_EQ(3, parameters.size());
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
// Verify their values.
- EXPECT_EQ("value1", parameters[0]->getValue());
- EXPECT_EQ(65, parameters[1]->getSignedIntegerValue());
- EXPECT_EQ("value3", parameters[2]->getValue());
+ EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue());
+ EXPECT_EQ(65, (*parameters_index.find("name2"))->getSignedIntegerValue());
+ EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue());
// Should be able to fetct these parameters when explicitly providing
// the server tag.
auto parameters = cbptr_->getModifiedGlobalParameters4(ServerSelector::ALL(),
timestamps_["today"]);
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
// It should be the one modified "tomorrow".
- ASSERT_EQ(1, parameters.size());
- ASSERT_TRUE(parameters[0]);
- EXPECT_EQ("value3", parameters[0]->getValue());
+ ASSERT_EQ(1, parameters_index.size());
+
+ auto parameter = parameters_index.find("name3");
+ ASSERT_FALSE(parameter == parameters_index.end());
+
+ ASSERT_TRUE(*parameter);
+ EXPECT_EQ("value3", (*parameter)->getValue());
// Should be able to fetct these parameters when explicitly providing
// the server tag.
-# Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2019 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
# This line concludes database upgrade to version 7.0.
+-- -----------------------------------------------------
+-- Table `dhcp4_audit_revision`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_audit_revision (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_audit_revision_by_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Drop columns from the dhcp4_audit table which now
+-- belong to the dhcp4_audit_revision.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ DROP COLUMN modification_ts,
+ DROP COLUMN log_message;
+
+-- -----------------------------------------------------
+-- Add column revision_id and the foreign key with a
+-- refrence to the dhcp4_audit_revision table.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ ADD COLUMN revision_id BIGINT(20) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp4_audit
+ ADD CONSTRAINT fk_dhcp4_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp4_audit_revision (id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit_revision table. This procedure should
+-- be called from the triggers of the tables where
+-- the config modifications are applied. If the
+-- corresponding revision doesn't exist, it will be
+-- created by this procedure.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditRevisionDHCP4()
+BEGIN
+ IF @audit_revision_id IS NULL THEN
+ INSERT INTO dhcp4_audit_revision (modification_ts, log_message)
+ VALUES (NOW(), @audit_log_message);
+ SET @audit_revision_id = LAST_INSERT_ID();
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The @audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+-- ----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditEntryDHCP4(IN object_type_val VARCHAR(256),
+ IN object_id_val BIGINT(20) UNSIGNED,
+ IN modification_type_val TINYINT(1))
+BEGIN
+ 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 $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Triggers used to create entries in the audit
+-- tables upon insertion, update or deletion of the
+-- configuration entries.
+-- -----------------------------------------------------
+
+# Create dhcp4_global_parameter insert trigger
+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 ;
+
+# Create dhcp4_global_parameter update trigger
+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 ;
+
+# Create dhcp4_global_parameter delete trigger
+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 ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '0';
+
+# This line concludes database upgrade to version 8.0.
+
+
# Notes:
#
# Indexes
-# Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2016-2019 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
DROP TABLE IF EXISTS lease6_stat;
DROP TABLE IF EXISTS logs;
DROP TABLE IF EXISTS dhcp4_audit;
+DROP TABLE IF EXISTS dhcp4_audit_revision;
DROP TABLE IF EXISTS dhcp4_global_parameter;
DROP TABLE IF EXISTS dhcp4_global_parameter_server;
DROP TABLE IF EXISTS dhcp4_option_def;
DROP TABLE IF EXISTS dhcp6_subnet;
DROP TABLE IF EXISTS dhcp6_subnet_server;
DROP TABLE IF EXISTS modification;
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_AINS;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_ADEL;