diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 8876e7bb5..8a7951192 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -577,7 +577,8 @@ boost::shared_ptr CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject) boost::shared_ptr CreateCertIcingaCA(const boost::shared_ptr& cert) { - return CreateCertIcingaCA(X509_get_pubkey(cert.get()), X509_get_subject_name(cert.get())); + boost::shared_ptr pkey = boost::shared_ptr(X509_get_pubkey(cert.get()), EVP_PKEY_free); + return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get())); } String CertificateToString(const boost::shared_ptr& cert) diff --git a/lib/cli/pkiutility.cpp b/lib/cli/pkiutility.cpp index 3dcae4cb2..ed1f0e938 100644 --- a/lib/cli/pkiutility.cpp +++ b/lib/cli/pkiutility.cpp @@ -91,7 +91,8 @@ int PkiUtility::SignCsr(const String& csrfile, const String& certfile) BIO_free(csrbio); - boost::shared_ptr cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req)); + boost::shared_ptr pubkey = boost::shared_ptr(X509_REQ_get_pubkey(req), EVP_PKEY_free); + boost::shared_ptr cert = CreateCertIcingaCA(pubkey.get(), X509_REQ_get_subject_name(req)); X509_REQ_free(req); diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index e7bee884c..6baabaff2 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -497,9 +497,10 @@ void ApiListener::SyncClient(const JsonRpcConnection::Ptr& aclient, const Endpoi Log(LogInformation, "ApiListener") << "Requesting new certificate for this Icinga instance from endpoint '" << endpoint->GetName() << "'."; - aclient->SendCertificateRequest(); + JsonRpcConnection::SendCertificateRequest(aclient, MessageOrigin::Ptr(), String()); - Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/*.json", boost::bind(&JsonRpcConnection::SyncCertificateRequest, aclient, MessageOrigin::Ptr(), _1), GlobFile); + if (Utility::PathExists(Application::GetLocalStateDir() + "/lib/icinga2/pki-requests")) + Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/*.json", boost::bind(&JsonRpcConnection::SendCertificateRequest, aclient, MessageOrigin::Ptr(), _1), GlobFile); } /* Make sure that the config updates are synced diff --git a/lib/remote/jsonrpcconnection-pki.cpp b/lib/remote/jsonrpcconnection-pki.cpp index 3110b53a4..471c5e3bf 100644 --- a/lib/remote/jsonrpcconnection-pki.cpp +++ b/lib/remote/jsonrpcconnection-pki.cpp @@ -28,12 +28,15 @@ #include "base/exception.hpp" #include "base/convert.hpp" #include +#include #include using namespace icinga; static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler); +static Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); +REGISTER_APIFUNCTION(UpdateCertificate, pki, &UpdateCertificateHandler); Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) { @@ -64,6 +67,8 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona for (unsigned int i = 0; i < n; i++) sprintf(certFingerprint + 2 * i, "%02x", digest[i]); + result->Set("fingerprint_request", certFingerprint); + String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests"; String requestPath = requestDir + "/" + certFingerprint + ".json"; @@ -81,13 +86,20 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona result->Set("cert", certResponse); result->Set("status_code", 0); + Dictionary::Ptr message = new Dictionary(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "pki::UpdateCertificate"); + message->Set("params", result); + JsonRpc::SendMessage(origin->FromClient->GetStream(), message); + return result; } } boost::shared_ptr newcert; - EVP_PKEY *pubkey; + boost::shared_ptr pubkey; X509_NAME *subject; + Dictionary::Ptr message; if (!Utility::PathExists(GetIcingaCADir() + "/ca.key")) goto delayed_request; @@ -120,10 +132,10 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona } - pubkey = X509_get_pubkey(cert.get()); + pubkey = boost::shared_ptr(X509_get_pubkey(cert.get()), EVP_PKEY_free); subject = X509_get_subject_name(cert.get()); - newcert = CreateCertIcingaCA(pubkey, subject); + newcert = CreateCertIcingaCA(pubkey.get(), subject); /* verify that the new cert matches the CA we're using for the ApiListener; * this ensures that the CA we have in /var/lib/icinga2/ca matches the one @@ -140,6 +152,12 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona result->Set("status_code", 0); + message = new Dictionary(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "pki::UpdateCertificate"); + message->Set("params", result); + JsonRpc::SendMessage(origin->FromClient->GetStream(), message); + return result; delayed_request: @@ -151,27 +169,29 @@ delayed_request: Utility::SaveJsonFile(requestPath, 0600, request); - JsonRpcConnection::SyncCertificateRequest(JsonRpcConnection::Ptr(), origin, requestPath); + JsonRpcConnection::SendCertificateRequest(JsonRpcConnection::Ptr(), origin, requestPath); result->Set("status_code", 2); result->Set("error", "Certificate request is pending. Waiting for approval from the parent Icinga instance."); return result; } -void JsonRpcConnection::SendCertificateRequest(void) +void JsonRpcConnection::SendCertificateRequest(const JsonRpcConnection::Ptr& aclient, const MessageOrigin::Ptr& origin, const String& path) { Dictionary::Ptr message = new Dictionary(); message->Set("jsonrpc", "2.0"); message->Set("method", "pki::RequestCertificate"); - String id = Utility::NewUniqueID(); - message->Set("id", id); - - Dictionary::Ptr params = new Dictionary(); - ApiListener::Ptr listener = ApiListener::GetInstance(); - if (listener) { + if (!listener) + return; + + Dictionary::Ptr params = new Dictionary(); + message->Set("params", params); + + /* path is empty if this is our own request */ + if (path.IsEmpty()) { String ticketPath = Application::GetLocalStateDir() + "/lib/icinga2/pki/ticket"; std::ifstream fp(ticketPath.CStr()); @@ -179,40 +199,80 @@ void JsonRpcConnection::SendCertificateRequest(void) fp.close(); params->Set("ticket", ticket); + } else { + Dictionary::Ptr request = Utility::LoadJsonFile(path); + + if (request->Contains("cert_response")) + return; + + params->Set("cert_request", request->Get("cert_request")); + params->Set("ticket", request->Get("ticket")); } - message->Set("params", params); - - RegisterCallback(id, boost::bind(&JsonRpcConnection::CertificateRequestResponseHandler, this, _1)); - - JsonRpc::SendMessage(GetStream(), message); + if (aclient) + JsonRpc::SendMessage(aclient->GetStream(), message); + else + listener->RelayMessage(origin, Zone::GetLocalZone(), message, false); } -void JsonRpcConnection::CertificateRequestResponseHandler(const Dictionary::Ptr& message) +Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) { - Log(LogWarning, "JsonRpcConnection") - << message->ToString(); + if (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone)) { + Log(LogWarning, "ClusterEvents") + << "Discarding 'update certificate' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed)."; - Dictionary::Ptr result = message->Get("result"); - - if (!result) - return; - - String ca = result->Get("ca"); - String cert = result->Get("cert"); - int status = result->Get("status_code"); - - /* TODO: make sure the cert's public key matches ours */ - - if (status != 0) { - /* TODO: log error */ - return; + return Empty; } + Log(LogWarning, "JsonRpcConnection") + << params->ToString(); + + String ca = params->Get("ca"); + String cert = params->Get("cert"); + ApiListener::Ptr listener = ApiListener::GetInstance(); if (!listener) - return; + return Empty; + + boost::shared_ptr oldCert = GetX509Certificate(listener->GetCertPath()); + boost::shared_ptr newCert = StringToCertificate(cert); + + Log(LogWarning, "JsonRpcConnection") + << "Received certificate update message for CN '" << GetCertificateCN(newCert) << "'"; + + /* check if this is a certificate update for a subordinate instance */ + boost::shared_ptr oldKey = boost::shared_ptr(X509_get_pubkey(oldCert.get()), EVP_PKEY_free); + boost::shared_ptr newKey = boost::shared_ptr(X509_get_pubkey(newCert.get()), EVP_PKEY_free); + + if (X509_NAME_cmp(X509_get_subject_name(oldCert.get()), X509_get_subject_name(newCert.get())) != 0 || + EVP_PKEY_cmp(oldKey.get(), newKey.get()) != 1) { + String certFingerprint = params->Get("fingerprint_request"); + + boost::regex expr("^[0-9a-f]+$"); + + if (!boost::regex_match(certFingerprint.GetData(), expr)) { + Log(LogWarning, "JsonRpcConnection") + << "Endpoint '" << origin->FromClient->GetIdentity() << "' sent an invalid certificate fingerprint: " << certFingerprint; + return Empty; + } + + String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests"; + String requestPath = requestDir + "/" + certFingerprint + ".json"; + + std::cout << requestPath << "\n"; + + if (Utility::PathExists(requestPath)) { + Log(LogWarning, "JsonRpcConnection") + << "Saved certificate update for CN '" << GetCertificateCN(newCert) << "'"; + + Dictionary::Ptr request = Utility::LoadJsonFile(requestPath); + request->Set("cert_response", cert); + Utility::SaveJsonFile(requestPath, 0644, request); + } + + return Empty; + } String caPath = listener->GetCaPath(); @@ -261,33 +321,6 @@ void JsonRpcConnection::CertificateRequestResponseHandler(const Dictionary::Ptr& Log(LogInformation, "JsonRpcConnection", "Updating the client certificate for the ApiListener object"); listener->UpdateSSLContext(); -} - -void JsonRpcConnection::SyncCertificateRequest(const JsonRpcConnection::Ptr& aclient, const MessageOrigin::Ptr& origin, const String& path) -{ - Dictionary::Ptr request = Utility::LoadJsonFile(path); - - if (request->Contains("cert_response")) - return; - - Dictionary::Ptr message = new Dictionary(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "pki::RequestCertificate"); - - Dictionary::Ptr params = new Dictionary(); - params->Set("cert_request", request->Get("cert_request")); - params->Set("ticket", request->Get("ticket")); - - message->Set("params", params); - - if (aclient) - JsonRpc::SendMessage(aclient->GetStream(), message); - else { - ApiListener::Ptr listener = ApiListener::GetInstance(); - - if (!listener) - return; - - listener->RelayMessage(origin, Zone::GetLocalZone(), message, false); - } + + return Empty; } diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index 0ab1465af..7a40f5f15 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -83,9 +83,7 @@ public: static int GetWorkQueueLength(void); static double GetWorkQueueRate(void); - void SendCertificateRequest(void); - - static void SyncCertificateRequest(const JsonRpcConnection::Ptr& aclient, const intrusive_ptr& origin, const String& path); + static void SendCertificateRequest(const JsonRpcConnection::Ptr& aclient, const intrusive_ptr& origin, const String& path); private: int m_ID;