From: Michael Friedrich Date: Mon, 20 Mar 2017 08:56:54 +0000 (+0100) Subject: Start working on checksum config dump X-Git-Tag: v2.7.0~175 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0b466aabc0332234c1564bceaa846b3132ef1ac2;p=icinga2 Start working on checksum config dump refs #4991 --- diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index c9eedd230..877a29d1f 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -591,6 +591,46 @@ String PBKDF2_SHA1(const String& password, const String& salt, int iterations) return output; } +String SHA1(const String& s, bool binary) +{ + char errbuf[120]; + SHA_CTX context; + unsigned char digest[SHA_DIGEST_LENGTH]; + + if (!SHA1_Init(&context)) { + Log(LogCritical, "SSL") + << "Error on SHA Init: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Init") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Update(&context, (unsigned char*)s.CStr(), s.GetLength())) { + Log(LogCritical, "SSL") + << "Error on SHA Update: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Update") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Final(digest, &context)) { + Log(LogCritical, "SSL") + << "Error on SHA Final: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Final") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (binary) + return String(reinterpret_cast(digest)); + + char output[SHA_DIGEST_LENGTH*2+1]; + for (int i = 0; i < 20; i++) + sprintf(output + 2 * i, "%02x", digest[i]); + + return output; +} + String SHA256(const String& s) { char errbuf[120]; diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index 4db9e7ae0..59af5c236 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -50,7 +50,7 @@ String I2_BASE_API GetIcingaCADir(void); String I2_BASE_API CertificateToString(const boost::shared_ptr& cert); boost::shared_ptr I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject); String I2_BASE_API PBKDF2_SHA1(const String& password, const String& salt, int iterations); -String I2_BASE_API SHA1(const String& s); +String I2_BASE_API SHA1(const String& s, bool binary = false); String I2_BASE_API SHA256(const String& s); String I2_BASE_API RandomString(int length); diff --git a/lib/redis/CMakeLists.txt b/lib/redis/CMakeLists.txt index 936f29160..9d940a1ff 100644 --- a/lib/redis/CMakeLists.txt +++ b/lib/redis/CMakeLists.txt @@ -18,7 +18,7 @@ mkclass_target(rediswriter.ti rediswriter.tcpp rediswriter.thpp) set(redis_SOURCES - rediswriter.cpp rediswriter-config.cpp rediswriter.thpp + rediswriter.cpp rediswriter-config.cpp rediswriter-utility.cpp rediswriter.thpp ) if(ICINGA2_UNITY_BUILD) diff --git a/lib/redis/rediswriter-config.cpp b/lib/redis/rediswriter-config.cpp index cf0851315..af96bf800 100644 --- a/lib/redis/rediswriter-config.cpp +++ b/lib/redis/rediswriter-config.cpp @@ -19,9 +19,12 @@ #include "redis/rediswriter.hpp" #include "icinga/customvarobject.hpp" +#include "icinga/host.hpp" +#include "icinga/service.hpp" #include "base/json.hpp" #include "base/logger.hpp" #include "base/serializer.hpp" +#include "base/tlsutility.hpp" #include "base/initialize.hpp" using namespace icinga; @@ -52,12 +55,10 @@ void RedisWriter::ConfigStaticInitialize(void) ConfigObject::OnVersionChanged.connect(boost::bind(&RedisWriter::VersionChangedHandler, _1)); } -//TODO: OnActiveChanged handling. void RedisWriter::UpdateAllConfigObjects(void) { AssertOnWorkQueue(); - //TODO: Just use config types for (const Type::Ptr& type : Type::GetAllTypes()) { if (!ConfigObject::TypeInstance->IsAssignableFrom(type)) continue; @@ -108,25 +109,62 @@ void RedisWriter::SendConfigUpdate(const ConfigObject::Ptr& object, const String //TODO: checksum String objectName = object->GetName(); - redisReply *reply = reinterpret_cast(redisCommand(m_Context, "HSET icinga:config:%s %s %s", typeName.CStr(), objectName.CStr(), jsonBody.CStr())); + redisReply *reply1 = reinterpret_cast(redisCommand(m_Context, "HSET icinga:config:%s %s %s", typeName.CStr(), objectName.CStr(), jsonBody.CStr())); - if (!reply) { + if (!reply1) { redisFree(m_Context); m_Context = NULL; return; } - if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_ERROR) { + if (reply1->type == REDIS_REPLY_STATUS || reply1->type == REDIS_REPLY_ERROR) { Log(LogInformation, "RedisWriter") - << "HSET icinga:config:" << typeName << " " << objectName << " " << jsonBody << ": " << reply->str; + << "HSET icinga:config:" << typeName << " " << objectName << " " << jsonBody << ": " << reply1->str; } - if (reply->type == REDIS_REPLY_ERROR) { - freeReplyObject(reply); + if (reply1->type == REDIS_REPLY_ERROR) { + freeReplyObject(reply1); return; } - freeReplyObject(reply); + freeReplyObject(reply1); + + + /* check sums */ + /* hset icinga:config:Host:checksums localhost { "name_checksum": "...", "properties_checksum": "...", "groups_checksum": "...", "vars_checksum": null } */ + Dictionary::Ptr checkSum = new Dictionary(); + + checkSum->Set("name_checksum", CalculateCheckSumString(object->GetName())); + + if (object->GetReflectionType() == Host::TypeInstance) { + Host::Ptr host = static_pointer_cast(object); + checkSum->Set("groups_checksum", CalculateCheckSumGroups(host->GetGroups())); + } else if (object->GetReflectionType() == Service::TypeInstance) { + Service::Ptr service = static_pointer_cast(object); + checkSum->Set("groups_checksum", CalculateCheckSumGroups(service->GetGroups())); + } + + String checkSumBody = JsonEncode(checkSum); + + redisReply *reply2 = reinterpret_cast(redisCommand(m_Context, "HSET icinga:config:%s:checksum %s %s", typeName.CStr(), objectName.CStr(), checkSumBody.CStr())); + + if (!reply2) { + redisFree(m_Context); + m_Context = NULL; + return; + } + + if (reply2->type == REDIS_REPLY_STATUS || reply2->type == REDIS_REPLY_ERROR) { + Log(LogInformation, "RedisWriter") + << "HSET icinga:config:" << typeName << " " << objectName << " " << jsonBody << ": " << reply2->str; + } + + if (reply2->type == REDIS_REPLY_ERROR) { + freeReplyObject(reply2); + return; + } + + freeReplyObject(reply2); } void RedisWriter::SendStatusUpdate(const ConfigObject::Ptr& object, const String& typeName) @@ -162,41 +200,6 @@ void RedisWriter::SendStatusUpdate(const ConfigObject::Ptr& object, const String freeReplyObject(reply); } -Dictionary::Ptr RedisWriter::SerializeObjectAttrs(const Object::Ptr& object, int fieldType) -{ - Type::Ptr type = object->GetReflectionType(); - - std::vector fids; - - for (int fid = 0; fid < type->GetFieldCount(); fid++) { - fids.push_back(fid); - } - - Dictionary::Ptr resultAttrs = new Dictionary(); - - for (int& fid : fids) { - Field field = type->GetFieldInfo(fid); - - if ((field.Attributes & fieldType) == 0) - continue; - - Value val = object->GetField(fid); - - /* hide attributes which shouldn't be user-visible */ - if (field.Attributes & FANoUserView) - continue; - - /* hide internal navigation fields */ - if (field.Attributes & FANavigation && !(field.Attributes & (FAConfig | FAState))) - continue; - - Value sval = Serialize(val); - resultAttrs->Set(field.Name, sval); - } - - return resultAttrs; -} - void RedisWriter::StateChangedHandler(const ConfigObject::Ptr& object) { Type::Ptr type = object->GetReflectionType(); diff --git a/lib/redis/rediswriter-utility.cpp b/lib/redis/rediswriter-utility.cpp new file mode 100644 index 000000000..59d7880a7 --- /dev/null +++ b/lib/redis/rediswriter-utility.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "redis/rediswriter.hpp" +#include "icinga/customvarobject.hpp" +#include "base/json.hpp" +#include "base/logger.hpp" +#include "base/serializer.hpp" +#include "base/tlsutility.hpp" +#include "base/initialize.hpp" + +using namespace icinga; + +String RedisWriter::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 RedisWriter::CalculateCheckSumString(const String& str) +{ + return SHA1(str, true); +} + +String RedisWriter::CalculateCheckSumGroups(const Array::Ptr& groups) +{ + String output; + + bool first = true; + + for (const String& group : groups) { + if (first) + first = false; + else + output += ";"; + + output += SHA1(group, true); //binary checksum required here + } + + return SHA1(output, false); +} + +String RedisWriter::CalculateCheckSumAttrs(const Dictionary::Ptr& attrs) +{ + String output; + + //TODO: Implement + for (const Dictionary::Pair& kv: attrs) { + if (kv.second.IsNumber()) { + //use a precision of 6 for floating point numbers + } + } + + return output; +} + +Dictionary::Ptr RedisWriter::SerializeObjectAttrs(const Object::Ptr& object, int fieldType) +{ + Type::Ptr type = object->GetReflectionType(); + + std::vector fids; + + for (int fid = 0; fid < type->GetFieldCount(); fid++) { + fids.push_back(fid); + } + + Dictionary::Ptr resultAttrs = new Dictionary(); + + for (int& fid : fids) { + Field field = type->GetFieldInfo(fid); + + if ((field.Attributes & fieldType) == 0) + continue; + + Value val = object->GetField(fid); + + /* hide attributes which shouldn't be user-visible */ + if (field.Attributes & FANoUserView) + continue; + + /* hide internal navigation fields */ + if (field.Attributes & FANavigation && !(field.Attributes & (FAConfig | FAState))) + continue; + + Value sval = Serialize(val); + resultAttrs->Set(field.Name, sval); + } + + return resultAttrs; +} + diff --git a/lib/redis/rediswriter.hpp b/lib/redis/rediswriter.hpp index 1269f29dd..44a48034a 100644 --- a/lib/redis/rediswriter.hpp +++ b/lib/redis/rediswriter.hpp @@ -59,10 +59,16 @@ private: void UpdateSubscriptionsTimerHandler(void); void UpdateSubscriptions(void); - /* config dump */ + /* config & status dump */ void UpdateAllConfigObjects(void); void SendConfigUpdate(const ConfigObject::Ptr& object, const String& typeName); void SendStatusUpdate(const ConfigObject::Ptr& object, const String& typeName); + + /* utilities */ + static String FormatCheckSumBinary(const String& str); + static String CalculateCheckSumString(const String& str); + static String CalculateCheckSumGroups(const Array::Ptr& groups); + static String CalculateCheckSumAttrs(const Dictionary::Ptr& attrs); static Dictionary::Ptr SerializeObjectAttrs(const Object::Ptr& object, int fieldType); static void StateChangedHandler(const ConfigObject::Ptr& object);