mirror of https://github.com/Icinga/icinga2.git
parent
1d75a15d8e
commit
510e2d622a
|
@ -590,6 +590,21 @@ String CertificateToString(const boost::shared_ptr<X509>& cert)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<X509> StringToCertificate(const String& cert)
|
||||||
|
{
|
||||||
|
BIO *bio = BIO_new(BIO_s_mem());
|
||||||
|
BIO_write(bio, (const void *)cert.CStr(), cert.GetLength());
|
||||||
|
|
||||||
|
X509 *rawCert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
BIO_free(bio);
|
||||||
|
|
||||||
|
if (!rawCert)
|
||||||
|
BOOST_THROW_EXCEPTION(std::invalid_argument("The specified X509 certificate is invalid."));
|
||||||
|
|
||||||
|
return boost::shared_ptr<X509>(rawCert, X509_free);
|
||||||
|
}
|
||||||
|
|
||||||
String PBKDF2_SHA1(const String& password, const String& salt, int iterations)
|
String PBKDF2_SHA1(const String& password, const String& salt, int iterations)
|
||||||
{
|
{
|
||||||
unsigned char digest[SHA_DIGEST_LENGTH];
|
unsigned char digest[SHA_DIGEST_LENGTH];
|
||||||
|
|
|
@ -48,6 +48,7 @@ int I2_BASE_API MakeX509CSR(const String& cn, const String& keyfile, const Strin
|
||||||
boost::shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
|
boost::shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
|
||||||
String I2_BASE_API GetIcingaCADir(void);
|
String I2_BASE_API GetIcingaCADir(void);
|
||||||
String I2_BASE_API CertificateToString(const boost::shared_ptr<X509>& cert);
|
String I2_BASE_API CertificateToString(const boost::shared_ptr<X509>& cert);
|
||||||
|
boost::shared_ptr<X509> I2_BASE_API StringToCertificate(const String& cert);
|
||||||
boost::shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
|
boost::shared_ptr<X509> 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 PBKDF2_SHA1(const String& password, const String& salt, int iterations);
|
||||||
String I2_BASE_API SHA1(const String& s, bool binary = false);
|
String I2_BASE_API SHA1(const String& s, bool binary = false);
|
||||||
|
|
|
@ -95,17 +95,16 @@ int PKIRequestCommand::Run(const boost::program_options::variables_map& vm, cons
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vm.count("ticket")) {
|
|
||||||
Log(LogCritical, "cli", "Ticket (--ticket) must be specified.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String port = "5665";
|
String port = "5665";
|
||||||
|
String ticket;
|
||||||
|
|
||||||
if (vm.count("port"))
|
if (vm.count("port"))
|
||||||
port = vm["port"].as<std::string>();
|
port = vm["port"].as<std::string>();
|
||||||
|
|
||||||
|
if (vm.count("ticket"))
|
||||||
|
ticket = vm["ticket"].as<std::string>();
|
||||||
|
|
||||||
return PkiUtility::RequestCertificate(vm["host"].as<std::string>(), port, vm["key"].as<std::string>(),
|
return PkiUtility::RequestCertificate(vm["host"].as<std::string>(), port, vm["key"].as<std::string>(),
|
||||||
vm["cert"].as<std::string>(), vm["ca"].as<std::string>(), GetX509Certificate(vm["trustedcert"].as<std::string>()),
|
vm["cert"].as<std::string>(), vm["ca"].as<std::string>(), GetX509Certificate(vm["trustedcert"].as<std::string>()),
|
||||||
vm["ticket"].as<std::string>());
|
ticket);
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,39 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
|
||||||
Dictionary::Ptr result = response->Get("result");
|
Dictionary::Ptr result = response->Get("result");
|
||||||
|
|
||||||
if (result->Contains("error")) {
|
if (result->Contains("error")) {
|
||||||
Log(LogCritical, "cli", result->Get("error"));
|
LogSeverity severity;
|
||||||
|
|
||||||
|
if (result->Get("status_code") == 1)
|
||||||
|
severity = LogCritical;
|
||||||
|
else {
|
||||||
|
severity = LogInformation;
|
||||||
|
Log(severity, "cli", "!!!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(severity, "cli")
|
||||||
|
<< "!!! " << result->Get("error");
|
||||||
|
|
||||||
|
if (result->Get("status_code") == 1)
|
||||||
|
return 1;
|
||||||
|
else {
|
||||||
|
Log(severity, "cli", "!!!!!!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringToCertificate(result->Get("cert"));
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogCritical, "cli")
|
||||||
|
<< "Could not write certificate file: " << DiagnosticInformation(ex, false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringToCertificate(result->Get("ca"));
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogCritical, "cli")
|
||||||
|
<< "Could not write CA file: " << DiagnosticInformation(ex, false);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ public:
|
||||||
static int GenTicket(const String& cn, const String& salt, std::ostream& ticketfp);
|
static int GenTicket(const String& cn, const String& salt, std::ostream& ticketfp);
|
||||||
static int RequestCertificate(const String& host, const String& port, const String& keyfile,
|
static int RequestCertificate(const String& host, const String& port, const String& keyfile,
|
||||||
const String& certfile, const String& cafile, const boost::shared_ptr<X509>& trustedcert,
|
const String& certfile, const String& cafile, const boost::shared_ptr<X509>& trustedcert,
|
||||||
const String& ticket);
|
const String& ticket = String());
|
||||||
static String GetCertificateInformation(const boost::shared_ptr<X509>& certificate);
|
static String GetCertificateInformation(const boost::shared_ptr<X509>& certificate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -28,7 +28,7 @@ set(remote_SOURCES
|
||||||
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
|
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
|
||||||
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
|
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
|
||||||
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
|
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
|
||||||
httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
|
httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp
|
||||||
messageorigin.cpp modifyobjecthandler.cpp statushandler.cpp objectqueryhandler.cpp templatequeryhandler.cpp
|
messageorigin.cpp modifyobjecthandler.cpp statushandler.cpp objectqueryhandler.cpp templatequeryhandler.cpp
|
||||||
typequeryhandler.cpp url.cpp variablequeryhandler.cpp zone.cpp zone.thpp
|
typequeryhandler.cpp url.cpp variablequeryhandler.cpp zone.cpp zone.thpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* 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 "remote/jsonrpcconnection.hpp"
|
||||||
|
#include "remote/apilistener.hpp"
|
||||||
|
#include "remote/apifunction.hpp"
|
||||||
|
#include "base/configtype.hpp"
|
||||||
|
#include "base/objectlock.hpp"
|
||||||
|
#include "base/utility.hpp"
|
||||||
|
#include "base/logger.hpp"
|
||||||
|
#include "base/exception.hpp"
|
||||||
|
#include "base/convert.hpp"
|
||||||
|
#include <boost/thread/once.hpp>
|
||||||
|
|
||||||
|
using namespace icinga;
|
||||||
|
|
||||||
|
static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||||
|
REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
|
||||||
|
|
||||||
|
Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
||||||
|
{
|
||||||
|
if (!params)
|
||||||
|
return Empty;
|
||||||
|
|
||||||
|
String certText = params->Get("cert_request");
|
||||||
|
|
||||||
|
boost::shared_ptr<X509> cert;
|
||||||
|
|
||||||
|
Dictionary::Ptr result = new Dictionary();
|
||||||
|
|
||||||
|
if (certText.IsEmpty())
|
||||||
|
cert = origin->FromClient->GetStream()->GetPeerCertificate();
|
||||||
|
else
|
||||||
|
cert = StringToCertificate(certText);
|
||||||
|
|
||||||
|
unsigned int n;
|
||||||
|
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||||
|
|
||||||
|
if (!X509_digest(cert.get(), EVP_sha256(), digest, &n)) {
|
||||||
|
result->Set("status_code", 1);
|
||||||
|
result->Set("error", "Could not calculate fingerprint for the X509 certificate.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char certFingerprint[EVP_MAX_MD_SIZE*2+1];
|
||||||
|
for (unsigned int i = 0; i < n; i++)
|
||||||
|
sprintf(certFingerprint + 2 * i, "%02x", digest[i]);
|
||||||
|
|
||||||
|
String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests";
|
||||||
|
String requestPath = requestDir + "/" + certFingerprint + ".json";
|
||||||
|
|
||||||
|
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||||
|
|
||||||
|
String cacertfile = listener->GetCaPath();
|
||||||
|
boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
|
||||||
|
result->Set("ca", CertificateToString(cacert));
|
||||||
|
|
||||||
|
if (Utility::PathExists(requestPath)) {
|
||||||
|
Dictionary::Ptr request = Utility::LoadJsonFile(requestPath);
|
||||||
|
|
||||||
|
String certResponse = request->Get("cert_response");
|
||||||
|
|
||||||
|
if (!certResponse.IsEmpty()) {
|
||||||
|
result->Set("cert", certResponse);
|
||||||
|
result->Set("status_code", 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<X509> newcert;
|
||||||
|
EVP_PKEY *pubkey;
|
||||||
|
X509_NAME *subject;
|
||||||
|
|
||||||
|
if (!Utility::PathExists(GetIcingaCADir() + "/ca.key"))
|
||||||
|
goto delayed_request;
|
||||||
|
|
||||||
|
if (!origin->FromClient->IsAuthenticated()) {
|
||||||
|
String salt = listener->GetTicketSalt();
|
||||||
|
|
||||||
|
if (salt.IsEmpty())
|
||||||
|
goto delayed_request;
|
||||||
|
|
||||||
|
String ticket = params->Get("ticket");
|
||||||
|
String realTicket = PBKDF2_SHA1(origin->FromClient->GetIdentity(), salt, 50000);
|
||||||
|
|
||||||
|
if (ticket != realTicket) {
|
||||||
|
result->Set("status_code", 1);
|
||||||
|
result->Set("error", "Invalid ticket.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey = X509_get_pubkey(cert.get());
|
||||||
|
subject = X509_get_subject_name(cert.get());
|
||||||
|
|
||||||
|
newcert = CreateCertIcingaCA(pubkey, subject);
|
||||||
|
result->Set("cert", CertificateToString(newcert));
|
||||||
|
|
||||||
|
result->Set("status_code", 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
delayed_request:
|
||||||
|
Utility::MkDirP(requestDir, 0700);
|
||||||
|
|
||||||
|
Dictionary::Ptr request = new Dictionary();
|
||||||
|
request->Set("cert_request", CertificateToString(cert));
|
||||||
|
request->Set("ticket", params->Get("ticket"));
|
||||||
|
|
||||||
|
Utility::SaveJsonFile(requestPath, 0600, request);
|
||||||
|
|
||||||
|
result->Set("status_code", 2);
|
||||||
|
result->Set("error", "Certificate request is pending. Waiting for approval from the parent Icinga instance.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -33,8 +33,6 @@ using namespace icinga;
|
||||||
|
|
||||||
static Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
static Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||||
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
|
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
|
||||||
static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
|
||||||
REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
|
|
||||||
|
|
||||||
static boost::once_flag l_JsonRpcConnectionOnceFlag = BOOST_ONCE_INIT;
|
static boost::once_flag l_JsonRpcConnectionOnceFlag = BOOST_ONCE_INIT;
|
||||||
static Timer::Ptr l_JsonRpcConnectionTimeoutTimer;
|
static Timer::Ptr l_JsonRpcConnectionTimeoutTimer;
|
||||||
|
@ -276,46 +274,6 @@ Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::
|
||||||
return Empty;
|
return Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
|
||||||
{
|
|
||||||
if (!params)
|
|
||||||
return Empty;
|
|
||||||
|
|
||||||
Dictionary::Ptr result = new Dictionary();
|
|
||||||
|
|
||||||
if (!origin->FromClient->IsAuthenticated()) {
|
|
||||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
|
||||||
String salt = listener->GetTicketSalt();
|
|
||||||
|
|
||||||
if (salt.IsEmpty()) {
|
|
||||||
result->Set("error", "Ticket salt is not configured.");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ticket = params->Get("ticket");
|
|
||||||
String realTicket = PBKDF2_SHA1(origin->FromClient->GetIdentity(), salt, 50000);
|
|
||||||
|
|
||||||
if (ticket != realTicket) {
|
|
||||||
result->Set("error", "Invalid ticket.");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::shared_ptr<X509> cert = origin->FromClient->GetStream()->GetPeerCertificate();
|
|
||||||
|
|
||||||
EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
|
|
||||||
X509_NAME *subject = X509_get_subject_name(cert.get());
|
|
||||||
|
|
||||||
boost::shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
|
|
||||||
result->Set("cert", CertificateToString(newcert));
|
|
||||||
|
|
||||||
String cacertfile = GetIcingaCADir() + "/ca.crt";
|
|
||||||
boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
|
|
||||||
result->Set("ca", CertificateToString(cacert));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JsonRpcConnection::CheckLiveness(void)
|
void JsonRpcConnection::CheckLiveness(void)
|
||||||
{
|
{
|
||||||
if (m_Seen < Utility::GetTime() - 60 && (!m_Endpoint || !m_Endpoint->GetSyncing())) {
|
if (m_Seen < Utility::GetTime() - 60 && (!m_Endpoint || !m_Endpoint->GetSyncing())) {
|
||||||
|
|
Loading…
Reference in New Issue