Log(LogWarning, "ApiListener")
<< "Certificate validation failed for endpoint '" << hostname
<< "': " << tlsStream->GetVerifyError();
- return;
}
}
Log(LogInformation, "ApiListener")
<< "Requesting new certificate for this Icinga instance from endpoint '" << endpoint->GetName() << "'.";
- SendCertificateRequest(aclient);
+ aclient->SendCertificateRequest();
}
/* Make sure that the config updates are synced
<< "Finished syncing endpoint '" << endpoint->GetName() << "' in zone '" << eZone->GetName() << "'.";
}
-void ApiListener::SendCertificateRequest(const JsonRpcConnection::Ptr& aclient)
-{
- Dictionary::Ptr message = new Dictionary();
- message->Set("jsonrpc", "2.0");
- message->Set("method", "pki::RequestCertificate");
-
- Dictionary::Ptr params = new Dictionary();
-
- message->Set("params", params);
-
- JsonRpc::SendMessage(aclient->GetStream(), message);
-}
-
void ApiListener::ApiTimerHandler(void)
{
double now = Utility::GetTime();
#include "remote/jsonrpcconnection.hpp"
#include "remote/apilistener.hpp"
#include "remote/apifunction.hpp"
+#include "remote/jsonrpc.hpp"
#include "base/configtype.hpp"
#include "base/objectlock.hpp"
#include "base/utility.hpp"
#include "base/exception.hpp"
#include "base/convert.hpp"
#include <boost/thread/once.hpp>
+#include <fstream>
using namespace icinga;
ApiListener::Ptr listener = ApiListener::GetInstance();
- String cacertfile = listener->GetCaPath();
- boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+ boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetCaPath());
result->Set("ca", CertificateToString(cacert));
if (Utility::PathExists(requestPath)) {
subject = X509_get_subject_name(cert.get());
newcert = CreateCertIcingaCA(pubkey, 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
+ * we're using for cluster connections (there's no point in sending a client
+ * a certificate it wouldn't be able to use to connect to us anyway) */
+ if (!VerifyCertificate(cacert, newcert)) {
+ Log(LogWarning, "JsonRpcConnection")
+ << "The CA in '" << listener->GetCaPath() << "' does not match the CA which Icinga uses "
+ << "for its own cluster connections. This is most likely a configuration problem.";
+ goto delayed_request;
+ }
+
result->Set("cert", CertificateToString(newcert));
result->Set("status_code", 0);
return result;
}
+void JsonRpcConnection::SendCertificateRequest(void)
+{
+ 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)
+ params->Set("ticket", listener->GetClientTicket());
+
+ message->Set("params", params);
+
+ RegisterCallback(id, boost::bind(&JsonRpcConnection::CertificateRequestResponseHandler, this, _1));
+
+ JsonRpc::SendMessage(GetStream(), message);
+}
+
+void JsonRpcConnection::CertificateRequestResponseHandler(const Dictionary::Ptr& message)
+{
+ Log(LogWarning, "JsonRpcConnection")
+ << message->ToString();
+
+ 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;
+ }
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ String caPath = listener->GetCaPath();
+
+ std::fstream cafp;
+ String tempCaPath = Utility::CreateTempFile(caPath + ".XXXXXX", 0644, cafp);
+ cafp << ca;
+ cafp.close();
+
+ if (rename(tempCaPath.CStr(), caPath.CStr()) < 0) {
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("rename")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(tempCaPath));
+ }
+
+ String certPath = listener->GetCertPath();
+
+ std::fstream certfp;
+ String tempCertPath = Utility::CreateTempFile(certPath + ".XXXXXX", 0644, certfp);
+ certfp << cert;
+ certfp.close();
+
+ if (rename(tempCertPath.CStr(), certPath.CStr()) < 0) {
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("rename")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(tempCertPath));
+ }
+
+ /* Update ApiListener's SSL_CTX */
+}
origin->FromZone = Zone::GetByName(message->Get("originZone"));
}
- String method = message->Get("method");
+ Value vmethod;
+
+ if (!message->Get("method", &vmethod)) {
+ Value vid;
+
+ if (!message->Get("id", &vid))
+ return;
+
+ String id = vid;
+
+ auto it = m_ApiCallbacks.find(id);
+
+ if (it == m_ApiCallbacks.end())
+ return;
+
+ ApiCallbackInfo aci = it->second;
+ m_ApiCallbacks.erase(it);
+
+ aci.Callback(message);
+
+ return;
+ }
+
+ String method = vmethod;
Log(LogNotice, "JsonRpcConnection")
<< "Received '" << method << "' message from '" << m_Identity << "'";
return rate / count;
}
+void JsonRpcConnection::RegisterCallback(const String& id, const boost::function<void (const Dictionary::Ptr&)>& callback)
+{
+ ApiCallbackInfo aci;
+ aci.Timestamp = Utility::GetTime();
+ aci.Callback = callback;
+
+ m_ApiCallbacks[id] = aci;
+}
class MessageOrigin;
+struct ApiCallbackInfo
+{
+ double Timestamp;
+ boost::function<void (const Dictionary::Ptr&)> Callback;
+};
+
/**
* An API client connection.
*
static int GetWorkQueueLength(void);
static double GetWorkQueueRate(void);
+ void SendCertificateRequest(void);
+
private:
int m_ID;
String m_Identity;
double m_NextHeartbeat;
double m_HeartbeatTimeout;
boost::mutex m_DataHandlerMutex;
+ std::map<String, ApiCallbackInfo> m_ApiCallbacks;
StreamReadContext m_Context;
static void StaticInitialize(void);
static void TimeoutTimerHandler(void);
void CheckLiveness(void);
+
+ void RegisterCallback(const String& id, const boost::function<void (const Dictionary::Ptr&)>& callback);
+
+ void CertificateRequestResponseHandler(const Dictionary::Ptr& message);
};
}