]> granicus.if.org Git - icinga2/commitdiff
Implement support for pki::UpdateCertificate messages
authorGunnar Beutner <gunnar.beutner@icinga.com>
Mon, 4 Sep 2017 14:19:46 +0000 (16:19 +0200)
committerGunnar Beutner <gunnar.beutner@icinga.com>
Tue, 12 Sep 2017 10:52:49 +0000 (12:52 +0200)
refs #5450

lib/base/tlsutility.cpp
lib/cli/pkiutility.cpp
lib/remote/apilistener.cpp
lib/remote/jsonrpcconnection-pki.cpp
lib/remote/jsonrpcconnection.hpp

index 8876e7bb5ef520a273c50b9b639c578fd1ac5a03..8a7951192bf3b6347168655b64d779accde6de3d 100644 (file)
@@ -577,7 +577,8 @@ boost::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
 
 boost::shared_ptr<X509> CreateCertIcingaCA(const boost::shared_ptr<X509>& cert)
 {
-       return CreateCertIcingaCA(X509_get_pubkey(cert.get()), X509_get_subject_name(cert.get()));
+       boost::shared_ptr<EVP_PKEY> pkey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
+       return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get()));
 }
 
 String CertificateToString(const boost::shared_ptr<X509>& cert)
index 3dcae4cb2c188e63ba830b0cba447daf77af87ac..ed1f0e938b7069e9db83e5f7e0e7bbd0ce9fe690 100644 (file)
@@ -91,7 +91,8 @@ int PkiUtility::SignCsr(const String& csrfile, const String& certfile)
 
        BIO_free(csrbio);
 
-       boost::shared_ptr<X509> cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req));
+       boost::shared_ptr<EVP_PKEY> pubkey = boost::shared_ptr<EVP_PKEY>(X509_REQ_get_pubkey(req), EVP_PKEY_free);
+       boost::shared_ptr<X509> cert = CreateCertIcingaCA(pubkey.get(), X509_REQ_get_subject_name(req));
 
        X509_REQ_free(req);
 
index e7bee884c8212078a906b3ab4986dda7604fb7ae..6baabaff21434beee2c891023869075f54d174c2 100644 (file)
@@ -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
index 3110b53a4184a9481c6fe3f82de40a8965063fad..471c5e3bfbbcfb487b8f3ee3bdce5f218124c7ff 100644 (file)
 #include "base/exception.hpp"
 #include "base/convert.hpp"
 #include <boost/thread/once.hpp>
+#include <boost/regex.hpp>
 #include <fstream>
 
 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<X509> newcert;
-       EVP_PKEY *pubkey;
+       boost::shared_ptr<EVP_PKEY> 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<EVP_PKEY>(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);
+       ApiListener::Ptr listener = ApiListener::GetInstance();
 
-       Dictionary::Ptr params = new Dictionary();
+       if (!listener)
+               return;
 
-       ApiListener::Ptr listener = ApiListener::GetInstance();
+       Dictionary::Ptr params = new Dictionary();
+       message->Set("params", params);
 
-       if (listener) {
+       /* 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);
 
-       message->Set("params", params);
+               if (request->Contains("cert_response"))
+                       return;
 
-       RegisterCallback(id, boost::bind(&JsonRpcConnection::CertificateRequestResponseHandler, this, _1));
+               params->Set("cert_request", request->Get("cert_request"));
+               params->Set("ticket", request->Get("ticket"));
+       }
 
-       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)
 {
+       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).";
+
+               return Empty;
+       }
+
        Log(LogWarning, "JsonRpcConnection")
-           << message->ToString();
+           << params->ToString();
 
-       Dictionary::Ptr result = message->Get("result");
+       String ca = params->Get("ca");
+       String cert = params->Get("cert");
 
-       if (!result)
-               return;
+       ApiListener::Ptr listener = ApiListener::GetInstance();
 
-       String ca = result->Get("ca");
-       String cert = result->Get("cert");
-       int status = result->Get("status_code");
+       if (!listener)
+               return Empty;
 
-       /* TODO: make sure the cert's public key matches ours */
+       boost::shared_ptr<X509> oldCert = GetX509Certificate(listener->GetCertPath());
+       boost::shared_ptr<X509> newCert = StringToCertificate(cert);
 
-       if (status != 0) {
-               /* TODO: log error */
-               return;
-       }
+       Log(LogWarning, "JsonRpcConnection")
+           << "Received certificate update message for CN '" << GetCertificateCN(newCert) << "'";
 
-       ApiListener::Ptr listener = ApiListener::GetInstance();
+       /* check if this is a certificate update for a subordinate instance */
+       boost::shared_ptr<EVP_PKEY> oldKey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(oldCert.get()), EVP_PKEY_free);
+       boost::shared_ptr<EVP_PKEY> newKey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(newCert.get()), EVP_PKEY_free);
 
-       if (!listener)
-               return;
+       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;
 }
index 0ab1465af7be3d652df0296a0d90f0c12aa256a0..7a40f5f1572941385dd70b5e39ccd97c5745ce1d 100644 (file)
@@ -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<MessageOrigin>& origin, const String& path);
+       static void SendCertificateRequest(const JsonRpcConnection::Ptr& aclient, const intrusive_ptr<MessageOrigin>& origin, const String& path);
 
 private:
        int m_ID;