icinga2/lib/icingadb/icingadb-utility.cpp
Julian Brost 42fc3cdcfc IcingaDB: introduce a new environment ID derived from the CA public key
In order to avoid changes to the environment ID, it is now no longer derived
from the Environment constant but instead from the public key of the CA
certificate. This ensures that it is different between clusters by default, so
no additional changes have to be done to allow two clusters to use Icinga DB to
write into the same database.

To prevent the ID from changing when the CA certificate is replaced, it is also
persisted into the file /var/lib/icinga2/icingadb.env, so if that file exists,
it takes precedence over the CA certificate.
2021-11-11 16:58:16 +01:00

271 lines
6.2 KiB
C++

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "icingadb/icingadb.hpp"
#include "base/configtype.hpp"
#include "base/object-packer.hpp"
#include "base/logger.hpp"
#include "base/serializer.hpp"
#include "base/tlsutility.hpp"
#include "base/initialize.hpp"
#include "base/objectlock.hpp"
#include "base/array.hpp"
#include "base/scriptglobal.hpp"
#include "base/convert.hpp"
#include "base/json.hpp"
#include "icinga/customvarobject.hpp"
#include "icinga/checkcommand.hpp"
#include "icinga/notificationcommand.hpp"
#include "icinga/eventcommand.hpp"
#include "icinga/host.hpp"
#include <boost/algorithm/string.hpp>
#include <map>
#include <utility>
#include <vector>
using namespace icinga;
String IcingaDB::FormatCheckSumBinary(const String& str)
{
char output[20*2+1];
for (int i = 0; i < 20; i++)
sprintf(output + 2 * i, "%02x", str[i]);
return output;
}
String IcingaDB::FormatCommandLine(const Value& commandLine)
{
String result;
if (commandLine.IsObjectType<Array>()) {
Array::Ptr args = commandLine;
bool first = true;
ObjectLock olock(args);
for (const Value& arg : args) {
String token = "'" + Convert::ToString(arg) + "'";
if (first)
first = false;
else
result += String(1, ' ');
result += token;
}
} else if (!commandLine.IsEmpty()) {
result = commandLine;
boost::algorithm::replace_all(result, "\'", "\\'");
result = "'" + result + "'";
}
return result;
}
ArrayData IcingaDB::GetObjectIdentifiersWithoutEnv(const ConfigObject::Ptr& object)
{
Type::Ptr type = object->GetReflectionType();
if (type == CheckCommand::TypeInstance || type == NotificationCommand::TypeInstance || type == EventCommand::TypeInstance)
return {type->GetName(), object->GetName()};
else
return {object->GetName()};
}
String IcingaDB::GetObjectIdentifier(const ConfigObject::Ptr& object)
{
return HashValue(new Array(Prepend(m_EnvironmentId, GetObjectIdentifiersWithoutEnv(object))));
}
/**
* Calculates a deterministic history event ID like SHA1(env, eventType, x...[, nt][, eventTime])
*
* Where SHA1(env, x...) = GetObjectIdentifier(object)
*/
String IcingaDB::CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime, NotificationType nt)
{
Array::Ptr rawId = new Array(GetObjectIdentifiersWithoutEnv(object));
rawId->Insert(0, m_EnvironmentId);
rawId->Insert(1, eventType);
if (nt) {
rawId->Add(GetNotificationTypeByEnum(nt));
}
if (eventTime) {
rawId->Add(TimestampToMilliseconds(eventTime));
}
return HashValue(std::move(rawId));
}
static const std::set<String> metadataWhitelist ({"package", "source_location", "templates"});
/**
* Prepare object's custom vars for being written to Redis
*
* object.vars = {
* "disks": {
* "disk": {},
* "disk /": {
* "disk_partitions": "/"
* }
* }
* }
*
* return {
* SHA1(PackObject([
* EnvironmentId,
* "disks",
* {
* "disk": {},
* "disk /": {
* "disk_partitions": "/"
* }
* }
* ])): {
* "environment_id": EnvironmentId,
* "name_checksum": SHA1("disks"),
* "name": "disks",
* "value": {
* "disk": {},
* "disk /": {
* "disk_partitions": "/"
* }
* }
* }
* }
*
* @param object Config object with custom vars
*
* @return JSON-like data structure for Redis
*/
Dictionary::Ptr IcingaDB::SerializeVars(const CustomVarObject::Ptr& object)
{
Dictionary::Ptr vars = object->GetVars();
if (!vars)
return nullptr;
Dictionary::Ptr res = new Dictionary();
ObjectLock olock(vars);
for (auto& kv : vars) {
res->Set(
SHA1(PackObject((Array::Ptr)new Array({m_EnvironmentId, kv.first, kv.second}))),
(Dictionary::Ptr)new Dictionary({
{"environment_id", m_EnvironmentId},
{"name_checksum", SHA1(kv.first)},
{"name", kv.first},
{"value", JsonEncode(kv.second)},
})
);
}
return res;
}
const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type)
{
switch (type) {
case NotificationDowntimeStart:
return "downtime_start";
case NotificationDowntimeEnd:
return "downtime_end";
case NotificationDowntimeRemoved:
return "downtime_removed";
case NotificationCustom:
return "custom";
case NotificationAcknowledgement:
return "acknowledgement";
case NotificationProblem:
return "problem";
case NotificationRecovery:
return "recovery";
case NotificationFlappingStart:
return "flapping_start";
case NotificationFlappingEnd:
return "flapping_end";
}
VERIFY(!"Invalid notification type.");
}
static const std::set<String> propertiesBlacklistEmpty;
String IcingaDB::HashValue(const Value& value)
{
return HashValue(value, propertiesBlacklistEmpty);
}
String IcingaDB::HashValue(const Value& value, const std::set<String>& propertiesBlacklist, bool propertiesWhitelist)
{
Value temp;
bool mutabl;
Type::Ptr type = value.GetReflectionType();
if (ConfigObject::TypeInstance->IsAssignableFrom(type)) {
temp = Serialize(value, FAConfig);
mutabl = true;
} else {
temp = value;
mutabl = false;
}
if (propertiesBlacklist.size() && temp.IsObject()) {
Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>((Object::Ptr)temp);
if (dict) {
if (!mutabl)
dict = dict->ShallowClone();
ObjectLock olock(dict);
if (propertiesWhitelist) {
auto current = dict->Begin();
auto propertiesBlacklistEnd = propertiesBlacklist.end();
while (current != dict->End()) {
if (propertiesBlacklist.find(current->first) == propertiesBlacklistEnd) {
dict->Remove(current++);
} else {
++current;
}
}
} else {
for (auto& property : propertiesBlacklist)
dict->Remove(property);
}
if (!mutabl)
temp = dict;
}
}
return SHA1(PackObject(temp));
}
String IcingaDB::GetLowerCaseTypeNameDB(const ConfigObject::Ptr& obj)
{
return obj->GetReflectionType()->GetName().ToLower();
}
long long IcingaDB::TimestampToMilliseconds(double timestamp) {
return static_cast<long long>(timestamp * 1000);
}
String IcingaDB::IcingaToStreamValue(const Value& value)
{
switch (value.GetType()) {
case ValueBoolean:
return Convert::ToString(int(value));
case ValueString:
return Utility::ValidateUTF8(value);
case ValueNumber:
case ValueEmpty:
return Convert::ToString(value);
default:
return JsonEncode(value);
}
}