#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)
{
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";
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;
}
- 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
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:
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());
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();
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;
}