mirror of https://github.com/Icinga/icinga2.git
Merge pull request #9392 from Icinga/bugfix/icingadb-env-id-init
Icinga DB: initialize environment ID during config validation
This commit is contained in:
commit
ca394efe83
|
@ -25,7 +25,7 @@ using namespace icinga;
|
|||
using Prio = RedisConnection::QueryPriority;
|
||||
|
||||
String IcingaDB::m_EnvironmentId;
|
||||
std::once_flag IcingaDB::m_EnvironmentIdOnce;
|
||||
std::mutex IcingaDB::m_EnvironmentIdInitMutex;
|
||||
|
||||
REGISTER_TYPE(IcingaDB);
|
||||
|
||||
|
@ -74,6 +74,13 @@ void IcingaDB::Validate(int types, const ValidationUtils& utils)
|
|||
if (GetEnableTls() && GetCertPath().IsEmpty() != GetKeyPath().IsEmpty()) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(), "Validation failed: Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given."));
|
||||
}
|
||||
|
||||
try {
|
||||
InitEnvironmentId();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(),
|
||||
String("Validation failed: ") + e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,39 +90,8 @@ void IcingaDB::Start(bool runtimeCreated)
|
|||
{
|
||||
ObjectImpl<IcingaDB>::Start(runtimeCreated);
|
||||
|
||||
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();
|
||||
});
|
||||
VERIFY(!m_EnvironmentId.IsEmpty());
|
||||
PersistEnvironmentId();
|
||||
|
||||
Log(LogInformation, "IcingaDB")
|
||||
<< "'" << GetName() << "' started.";
|
||||
|
@ -288,3 +264,62 @@ bool IcingaDB::DumpedGlobals::IsNew(const String& id)
|
|||
std::lock_guard<std::mutex> l (m_Mutex);
|
||||
return m_Ids.emplace(id).second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the m_EnvironmentId attribute or throws an exception on failure to do so. Can be called concurrently.
|
||||
*/
|
||||
void IcingaDB::InitEnvironmentId()
|
||||
{
|
||||
// Initialize m_EnvironmentId once across all IcingaDB objects. In theory, this could be done using
|
||||
// std::call_once, however, due to a bug in libstdc++ (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66146),
|
||||
// this can result in a deadlock when an exception is thrown (which is explicitly allowed by the standard).
|
||||
std::unique_lock<std::mutex> lock (m_EnvironmentIdInitMutex);
|
||||
|
||||
if (m_EnvironmentId.IsEmpty()) {
|
||||
String path = Configuration::DataDir + "/icingadb.env";
|
||||
String envId;
|
||||
|
||||
if (Utility::PathExists(path)) {
|
||||
envId = Utility::LoadJsonFile(path);
|
||||
|
||||
if (envId.GetLength() != 2*SHA_DIGEST_LENGTH) {
|
||||
throw std::runtime_error("environment ID stored at " + path + " is corrupt: wrong length.");
|
||||
}
|
||||
|
||||
for (unsigned char c : envId) {
|
||||
if (!std::isxdigit(c)) {
|
||||
throw std::runtime_error("environment ID stored at " + path + " is corrupt: invalid 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()));
|
||||
}
|
||||
|
||||
envId = BinaryToHex(digest, n);
|
||||
}
|
||||
|
||||
m_EnvironmentId = envId.ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the environment ID is persisted on disk or throws an exception on failure to do so.
|
||||
* Can be called concurrently.
|
||||
*/
|
||||
void IcingaDB::PersistEnvironmentId()
|
||||
{
|
||||
String path = Configuration::DataDir + "/icingadb.env";
|
||||
|
||||
std::unique_lock<std::mutex> lock (m_EnvironmentIdInitMutex);
|
||||
|
||||
if (!Utility::PathExists(path)) {
|
||||
Utility::SaveJsonFile(path, 0600, m_EnvironmentId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,6 +179,9 @@ private:
|
|||
|
||||
static std::vector<Type::Ptr> GetTypes();
|
||||
|
||||
static void InitEnvironmentId();
|
||||
static void PersistEnvironmentId();
|
||||
|
||||
Timer::Ptr m_StatsTimer;
|
||||
WorkQueue m_WorkQueue{0, 1, LogNotice};
|
||||
|
||||
|
@ -199,8 +202,11 @@ private:
|
|||
DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage;
|
||||
} m_DumpedGlobals;
|
||||
|
||||
// m_EnvironmentId is shared across all IcingaDB objects (typically there is at most one, but it is perfectly fine
|
||||
// to have multiple ones). It is initialized once (synchronized using m_EnvironmentIdInitMutex). After successful
|
||||
// initialization, the value is read-only and can be accessed without further synchronization.
|
||||
static String m_EnvironmentId;
|
||||
static std::once_flag m_EnvironmentIdOnce;
|
||||
static std::mutex m_EnvironmentIdInitMutex;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue