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.
This commit is contained in:
Julian Brost 2021-09-21 12:56:20 +02:00
parent 6cd3a483a0
commit 525dd50859
4 changed files with 69 additions and 44 deletions

View File

@ -607,7 +607,6 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
}
CustomVarObject::Ptr customVarObject = dynamic_pointer_cast<CustomVarObject>(object);
auto env (GetEnvironment());
if (customVarObject) {
auto vars(SerializeVars(customVarObject));
@ -630,7 +629,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
}
}
String id = HashValue(new Array(Prepend(env, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
typeCvs.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{objectKeyName, objectKey}, {"environment_id", m_EnvironmentId}, {"customvar_id", kv.first}});
@ -652,7 +651,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
if (!actionUrl.IsEmpty()) {
auto& actionUrls (hMSets[m_PrefixConfigObject + "action:url"]);
auto id (HashValue(new Array({env, actionUrl})));
auto id (HashValue(new Array({m_EnvironmentId, actionUrl})));
if (runtimeUpdate || m_DumpedGlobals.ActionUrl.IsNew(id)) {
actionUrls.emplace_back(std::move(id));
@ -667,7 +666,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
if (!notesUrl.IsEmpty()) {
auto& notesUrls (hMSets[m_PrefixConfigObject + "notes:url"]);
auto id (HashValue(new Array({env, notesUrl})));
auto id (HashValue(new Array({m_EnvironmentId, notesUrl})));
if (runtimeUpdate || m_DumpedGlobals.NotesUrl.IsNew(id)) {
notesUrls.emplace_back(std::move(id));
@ -682,7 +681,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
if (!iconImage.IsEmpty()) {
auto& iconImages (hMSets[m_PrefixConfigObject + "icon:image"]);
auto id (HashValue(new Array({env, iconImage})));
auto id (HashValue(new Array({m_EnvironmentId, iconImage})));
if (runtimeUpdate || m_DumpedGlobals.IconImage.IsNew(id)) {
iconImages.emplace_back(std::move(id));
@ -720,7 +719,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
for (auto& group : groups) {
auto groupObj ((*getGroup)(group));
String groupId = GetObjectIdentifier(groupObj);
String id = HashValue(new Array(Prepend(env, Prepend(GetObjectIdentifiersWithoutEnv(groupObj), GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(GetObjectIdentifiersWithoutEnv(groupObj), GetObjectIdentifiersWithoutEnv(object)))));
members.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{objectKeyName, objectKey}, {"environment_id", m_EnvironmentId}, {typeName + "group_id", groupId}});
members.emplace_back(JsonEncode(data));
@ -748,10 +747,10 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
rangeIds->Reserve(ranges->GetLength());
for (auto& kv : ranges) {
String rangeId = HashValue(new Array({env, kv.first, kv.second}));
String rangeId = HashValue(new Array({m_EnvironmentId, kv.first, kv.second}));
rangeIds->Add(rangeId);
String id = HashValue(new Array(Prepend(env, Prepend(kv.first, Prepend(kv.second, GetObjectIdentifiersWithoutEnv(object))))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(kv.first, Prepend(kv.second, GetObjectIdentifiersWithoutEnv(object))))));
typeRanges.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"environment_id", m_EnvironmentId}, {"timeperiod_id", objectKey}, {"range_key", kv.first}, {"range_value", kv.second}});
typeRanges.emplace_back(JsonEncode(data));
@ -781,7 +780,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
String includeId = GetObjectIdentifier(includeTp);
includeChecksums->Add(includeId);
String id = HashValue(new Array(Prepend(env, Prepend(GetObjectIdentifiersWithoutEnv(includeTp), GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(GetObjectIdentifiersWithoutEnv(includeTp), GetObjectIdentifiersWithoutEnv(object)))));
includs.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"environment_id", m_EnvironmentId}, {"timeperiod_id", objectKey}, {"include_id", includeId}});
includs.emplace_back(JsonEncode(data));
@ -811,7 +810,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
String excludeId = GetObjectIdentifier(excludeTp);
excludeChecksums->Add(excludeId);
String id = HashValue(new Array(Prepend(env, Prepend(GetObjectIdentifiersWithoutEnv(excludeTp), GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(GetObjectIdentifiersWithoutEnv(excludeTp), GetObjectIdentifiersWithoutEnv(object)))));
excluds.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"environment_id", m_EnvironmentId}, {"timeperiod_id", objectKey}, {"exclude_id", excludeId}});
excluds.emplace_back(JsonEncode(data));
@ -844,7 +843,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
for (auto& group : groups) {
auto groupObj ((*getGroup)(group));
String groupId = GetObjectIdentifier(groupObj);
String id = HashValue(new Array(Prepend(env, Prepend(GetObjectIdentifiersWithoutEnv(groupObj), GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(GetObjectIdentifiersWithoutEnv(groupObj), GetObjectIdentifiersWithoutEnv(object)))));
members.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"user_id", objectKey}, {"environment_id", m_EnvironmentId}, {"usergroup_id", groupId}});
members.emplace_back(JsonEncode(data));
@ -879,7 +878,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
for (auto& user : users) {
String userId = GetObjectIdentifier(user);
String id = HashValue(new Array(Prepend(env, Prepend(GetObjectIdentifiersWithoutEnv(user), GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(GetObjectIdentifiersWithoutEnv(user), GetObjectIdentifiersWithoutEnv(object)))));
usrs.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"user_id", userId}});
usrs.emplace_back(JsonEncode(data));
@ -902,7 +901,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
auto groupMembers = usergroup->GetMembers();
std::copy(groupMembers.begin(), groupMembers.end(), std::inserter(allUsers, allUsers.begin()));
String id = HashValue(new Array(Prepend(env, Prepend("usergroup", Prepend(GetObjectIdentifiersWithoutEnv(usergroup), GetObjectIdentifiersWithoutEnv(object))))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend("usergroup", Prepend(GetObjectIdentifiersWithoutEnv(usergroup), GetObjectIdentifiersWithoutEnv(object))))));
groups.emplace_back(id);
Dictionary::Ptr groupData = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"usergroup_id", usergroupId}});
groups.emplace_back(JsonEncode(groupData));
@ -921,7 +920,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
for (auto& user : allUsers) {
String userId = GetObjectIdentifier(user);
String id = HashValue(new Array(Prepend(env, Prepend("user", Prepend(GetObjectIdentifiersWithoutEnv(user), GetObjectIdentifiersWithoutEnv(object))))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend("user", Prepend(GetObjectIdentifiersWithoutEnv(user), GetObjectIdentifiersWithoutEnv(object))))));
notificationRecipients.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"user_id", userId}});
notificationRecipients.emplace_back(JsonEncode(data));
@ -975,7 +974,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
values->Set("argument_key", kv.first);
values->Set("environment_id", m_EnvironmentId);
String id = HashValue(new Array(Prepend(env, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
typeArgs.emplace_back(id);
typeArgs.emplace_back(JsonEncode(values));
@ -1024,7 +1023,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
values->Set("envvar_key", kv.first);
values->Set("environment_id", m_EnvironmentId);
String id = HashValue(new Array(Prepend(env, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
String id = HashValue(new Array(Prepend(m_EnvironmentId, Prepend(kv.first, GetObjectIdentifiersWithoutEnv(object)))));
typeVars.emplace_back(id);
typeVars.emplace_back(JsonEncode(values));
@ -1214,11 +1213,11 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a
String notesUrl = checkable->GetNotesUrl();
String iconImage = checkable->GetIconImage();
if (!actionUrl.IsEmpty())
attributes->Set("action_url_id", HashValue(new Array({GetEnvironment(), actionUrl})));
attributes->Set("action_url_id", HashValue(new Array({m_EnvironmentId, actionUrl})));
if (!notesUrl.IsEmpty())
attributes->Set("notes_url_id", HashValue(new Array({GetEnvironment(), notesUrl})));
attributes->Set("notes_url_id", HashValue(new Array({m_EnvironmentId, notesUrl})));
if (!iconImage.IsEmpty())
attributes->Set("icon_image_id", HashValue(new Array({GetEnvironment(), iconImage})));
attributes->Set("icon_image_id", HashValue(new Array({m_EnvironmentId, iconImage})));
Host::Ptr host;
@ -1544,7 +1543,7 @@ void IcingaDB::SendStateChange(const ConfigObject::Ptr& object, const CheckResul
auto eventTime (cr->GetExecutionEnd());
auto eventTs (TimestampToMilliseconds(eventTime));
Array::Ptr rawId = new Array(Prepend(GetEnvironment(), GetObjectIdentifiersWithoutEnv(object)));
Array::Ptr rawId = new Array(Prepend(m_EnvironmentId, GetObjectIdentifiersWithoutEnv(object)));
rawId->Add(eventTs);
std::vector<String> xAdd ({
@ -1624,7 +1623,7 @@ void IcingaDB::SendSentNotification(
auto usersAmount (users.size());
auto sendTs (TimestampToMilliseconds(sendTime));
Array::Ptr rawId = new Array(Prepend(GetEnvironment(), GetObjectIdentifiersWithoutEnv(notification)));
Array::Ptr rawId = new Array(Prepend(m_EnvironmentId, GetObjectIdentifiersWithoutEnv(notification)));
rawId->Add(GetNotificationTypeByEnum(type));
rawId->Add(sendTs);
@ -2031,7 +2030,7 @@ void IcingaDB::SendFlappingChange(const Checkable::Ptr& checkable, double change
xAdd.emplace_back("event_id");
xAdd.emplace_back(CalcEventID(checkable->IsFlapping() ? "flapping_start" : "flapping_end", checkable, startTime));
xAdd.emplace_back("id");
xAdd.emplace_back(HashValue(new Array({GetEnvironment(), checkable->GetName(), startTime})));
xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), startTime})));
m_Rcon->FireAndForgetQuery(std::move(xAdd), Prio::History);
}
@ -2112,7 +2111,7 @@ void IcingaDB::SendAcknowledgementSet(const Checkable::Ptr& checkable, const Str
xAdd.emplace_back("event_id");
xAdd.emplace_back(CalcEventID("ack_set", checkable, setTime));
xAdd.emplace_back("id");
xAdd.emplace_back(HashValue(new Array({GetEnvironment(), checkable->GetName(), setTime})));
xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTime})));
m_Rcon->FireAndForgetQuery(std::move(xAdd), Prio::History);
}
@ -2158,7 +2157,7 @@ void IcingaDB::SendAcknowledgementCleared(const Checkable::Ptr& checkable, const
xAdd.emplace_back("event_id");
xAdd.emplace_back(CalcEventID("ack_clear", checkable, setTime));
xAdd.emplace_back("id");
xAdd.emplace_back(HashValue(new Array({GetEnvironment(), checkable->GetName(), setTime})));
xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTime})));
if (!removedBy.IsEmpty()) {
xAdd.emplace_back("cleared_by");

View File

@ -60,11 +60,6 @@ String IcingaDB::FormatCommandLine(const Value& commandLine)
return result;
}
String IcingaDB::GetEnvironment()
{
return ConfigType::GetObjectsByType<IcingaApplication>()[0]->GetEnvironment();
}
ArrayData IcingaDB::GetObjectIdentifiersWithoutEnv(const ConfigObject::Ptr& object)
{
Type::Ptr type = object->GetReflectionType();
@ -77,7 +72,7 @@ ArrayData IcingaDB::GetObjectIdentifiersWithoutEnv(const ConfigObject::Ptr& obje
String IcingaDB::GetObjectIdentifier(const ConfigObject::Ptr& object)
{
return HashValue(new Array(Prepend(GetEnvironment(), GetObjectIdentifiersWithoutEnv(object))));
return HashValue(new Array(Prepend(m_EnvironmentId, GetObjectIdentifiersWithoutEnv(object))));
}
/**
@ -88,7 +83,7 @@ String IcingaDB::GetObjectIdentifier(const ConfigObject::Ptr& 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, GetEnvironment());
rawId->Insert(0, m_EnvironmentId);
rawId->Insert(1, eventType);
if (nt) {
@ -118,7 +113,7 @@ static const std::set<String> metadataWhitelist ({"package", "source_location",
*
* return {
* SHA1(PackObject([
* Environment,
* EnvironmentId,
* "disks",
* {
* "disk": {},
@ -127,7 +122,7 @@ static const std::set<String> metadataWhitelist ({"package", "source_location",
* }
* }
* ])): {
* "envId": SHA1(Environment),
* "environment_id": EnvironmentId,
* "name_checksum": SHA1("disks"),
* "name": "disks",
* "value": {
@ -151,16 +146,14 @@ Dictionary::Ptr IcingaDB::SerializeVars(const CustomVarObject::Ptr& object)
return nullptr;
Dictionary::Ptr res = new Dictionary();
auto env (GetEnvironment());
auto envChecksum (SHA1(env));
ObjectLock olock(vars);
for (auto& kv : vars) {
res->Set(
SHA1(PackObject((Array::Ptr)new Array({env, kv.first, kv.second}))),
SHA1(PackObject((Array::Ptr)new Array({m_EnvironmentId, kv.first, kv.second}))),
(Dictionary::Ptr)new Dictionary({
{"environment_id", envChecksum},
{"environment_id", m_EnvironmentId},
{"name_checksum", SHA1(kv.first)},
{"name", kv.first},
{"value", JsonEncode(kv.second)},

View File

@ -3,11 +3,16 @@
#include "icingadb/icingadb.hpp"
#include "icingadb/icingadb-ti.cpp"
#include "icingadb/redisconnection.hpp"
#include "remote/apilistener.hpp"
#include "remote/eventqueue.hpp"
#include "base/configuration.hpp"
#include "base/json.hpp"
#include "base/tlsutility.hpp"
#include "base/utility.hpp"
#include "icinga/checkable.hpp"
#include "icinga/host.hpp"
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <memory>
#include <utility>
@ -18,7 +23,7 @@ using namespace icinga;
using Prio = RedisConnection::QueryPriority;
String IcingaDB::m_EnvironmentId;
boost::once_flag IcingaDB::m_EnvironmentIdOnce = BOOST_ONCE_INIT;
std::once_flag IcingaDB::m_EnvironmentIdOnce;
REGISTER_TYPE(IcingaDB);
@ -52,9 +57,39 @@ void IcingaDB::Start(bool runtimeCreated)
{
ObjectImpl<IcingaDB>::Start(runtimeCreated);
boost::call_once([]() {
m_EnvironmentId = SHA1(GetEnvironment());
}, m_EnvironmentIdOnce);
std::call_once(m_EnvironmentIdOnce, []() {
String path = Configuration::DataDir + "/icingadb.env";
if (Utility::PathExists(path)) {
m_EnvironmentId = Utility::LoadJsonFile(path);
if (m_EnvironmentId.GetLength() != 2*SHA_DIGEST_LENGTH) {
throw std::runtime_error("Wrong length of stored Icinga DB environment");
}
for (unsigned char c : m_EnvironmentId) {
if (!std::isxdigit(c)) {
throw std::runtime_error("Stored Icinga DB environment is not a hex string");
}
}
} else {
std::shared_ptr<X509> cert = GetX509Certificate(ApiListener::GetDefaultCaPath());
unsigned int n;
unsigned char digest[EVP_MAX_MD_SIZE];
if (X509_pubkey_digest(cert.get(), EVP_sha1(), digest, &n) != 1) {
BOOST_THROW_EXCEPTION(openssl_error()
<< boost::errinfo_api_function("X509_pubkey_digest")
<< errinfo_openssl_error(ERR_peek_error()));
}
m_EnvironmentId = BinaryToHex(digest, n);
Utility::SaveJsonFile(path, 0600, m_EnvironmentId);
}
m_EnvironmentId = m_EnvironmentId.ToLower();
});
Log(LogInformation, "IcingaDB")
<< "'" << GetName() << "' started.";

View File

@ -12,7 +12,6 @@
#include "icinga/service.hpp"
#include "icinga/downtime.hpp"
#include "remote/messageorigin.hpp"
#include <boost/thread/once.hpp>
#include <atomic>
#include <memory>
#include <mutex>
@ -108,7 +107,6 @@ private:
static ArrayData GetObjectIdentifiersWithoutEnv(const ConfigObject::Ptr& object);
static String GetObjectIdentifier(const ConfigObject::Ptr& object);
static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0));
static String GetEnvironment();
static Dictionary::Ptr SerializeVars(const CustomVarObject::Ptr& object);
static const char* GetNotificationTypeByEnum(NotificationType type);
@ -180,7 +178,7 @@ private:
} m_DumpedGlobals;
static String m_EnvironmentId;
static boost::once_flag m_EnvironmentIdOnce;
static std::once_flag m_EnvironmentIdOnce;
};
}