From: Gunnar Beutner Date: Tue, 5 Sep 2017 12:16:26 +0000 (+0200) Subject: Update output format for the new CLI commands X-Git-Tag: v2.8.0~87^2~23 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6a533796e551a10b7ce1fad243cf8c958c56164e;p=icinga2 Update output format for the new CLI commands refs #5450 --- diff --git a/lib/cli/calistcommand.cpp b/lib/cli/calistcommand.cpp index 98012af9e..5cab64b6e 100644 --- a/lib/cli/calistcommand.cpp +++ b/lib/cli/calistcommand.cpp @@ -21,8 +21,10 @@ #include "base/logger.hpp" #include "base/application.hpp" #include "base/tlsutility.hpp" +#include "base/json.hpp" using namespace icinga; +namespace po = boost::program_options; REGISTER_CLICOMMAND("ca/list", CAListCommand); @@ -36,34 +38,57 @@ String CAListCommand::GetShortDescription(void) const return "lists all certificate signing requests"; } -void CAListCommand::PrintRequest(const String& requestFile) +void CAListCommand::InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const +{ + visibleDesc.add_options() + ("json", "encode output as JSON") + ; +} +static void CollectRequestHandler(const Dictionary::Ptr& requests, const String& requestFile) { Dictionary::Ptr request = Utility::LoadJsonFile(requestFile); if (!request) return; + Dictionary::Ptr result = new Dictionary(); + String fingerprint = Utility::BaseName(requestFile); fingerprint = fingerprint.SubStr(0, fingerprint.GetLength() - 5); - std::cout << "***\n"; - std::cout << "Fingerprint: " << fingerprint << "\n"; - String certRequestText = request->Get("cert_request"); + result->Set("cert_request", certRequestText); + + Value vcertResponseText; + + if (request->Get("cert_response", &vcertResponseText)) { + String certResponseText = vcertResponseText; + result->Set("cert_response", certResponseText); + } boost::shared_ptr certRequest = StringToCertificate(certRequestText); - String cn = GetCertificateCN(certRequest); + time_t now; + time(&now); + ASN1_TIME *tm = ASN1_TIME_adj(NULL, now, 0, 0); - String certResponseText = request->Get("cert_response"); + int day, sec; + ASN1_TIME_diff(&day, &sec, tm, X509_get_notBefore(certRequest.get())); - if (!certResponseText.IsEmpty()) { - boost::shared_ptr certResponse = StringToCertificate(certResponseText); - } + result->Set("timestamp", static_cast(now) + day * 24 * 60 * 60 + sec); + + BIO *out = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(out, X509_get_subject_name(certRequest.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB); - std::cout << "CN: " << cn << "\n"; - std::cout << "Certificate (request): " << certRequestText << "\n"; - std::cout << "Certificate (response): " << certResponseText << "\n"; + char *data; + long length; + length = BIO_get_mem_data(out, &data); + + result->Set("subject", String(data, data + length)); + BIO_free(out); + + requests->Set(fingerprint, result); } /** @@ -73,7 +98,34 @@ void CAListCommand::PrintRequest(const String& requestFile) */ int CAListCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const { - Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/*.json", &CAListCommand::PrintRequest, GlobFile); + Dictionary::Ptr requests = new Dictionary(); + + String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests"; + + if (Utility::PathExists(requestDir)) + Utility::Glob(requestDir + "/*.json", boost::bind(&CollectRequestHandler, requests, _1), GlobFile); + + if (vm.count("json")) + std::cout << JsonEncode(requests); + else { + ObjectLock olock(requests); + + std::cout << "Fingerprint | Timestamp | Signed | Subject\n"; + std::cout << "-----------------------------------------------------------------|---------------------|--------|--------\n"; + + for (auto& kv : requests) { + Dictionary::Ptr request = kv.second; + + std::cout << kv.first + << " | " + << Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", request->Get("timestamp")) + << " | " + << (request->Contains("cert_response") ? "*" : " ") << " " + << " | " + << request->Get("subject") + << "\n"; + } + } return 0; } diff --git a/lib/cli/calistcommand.hpp b/lib/cli/calistcommand.hpp index 80ef37872..8694fd16c 100644 --- a/lib/cli/calistcommand.hpp +++ b/lib/cli/calistcommand.hpp @@ -37,6 +37,8 @@ public: virtual String GetDescription(void) const override; virtual String GetShortDescription(void) const override; + virtual void InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const override; virtual int Run(const boost::program_options::variables_map& vm, const std::vector& ap) const override; private: diff --git a/lib/cli/casigncommand.cpp b/lib/cli/casigncommand.cpp index 247afebb3..6ca2ff55d 100644 --- a/lib/cli/casigncommand.cpp +++ b/lib/cli/casigncommand.cpp @@ -55,6 +55,12 @@ int CASignCommand::Run(const boost::program_options::variables_map& vm, const st { String requestFile = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests/" + ap[0] + ".json"; + if (!Utility::PathExists(requestFile)) { + Log(LogCritical, "cli") + << "No request exists for fingerprint '" << ap[0] << "'."; + return 1; + } + Dictionary::Ptr request = Utility::LoadJsonFile(requestFile); if (!request) @@ -64,11 +70,35 @@ int CASignCommand::Run(const boost::program_options::variables_map& vm, const st boost::shared_ptr certRequest = StringToCertificate(certRequestText); + if (!certRequest) { + Log(LogCritical, "cli", "Certificate request is invalid. Could not parse X.509 certificate for the 'cert_request' attribute."); + return 1; + } + boost::shared_ptr certResponse = CreateCertIcingaCA(certRequest); + BIO *out = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(out, X509_get_subject_name(certRequest.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB); + + char *data; + long length; + length = BIO_get_mem_data(out, &data); + + String subject = String(data, data + length); + BIO_free(out); + + if (!certResponse) { + Log(LogCritical, "cli") + << "Could not sign certificate for '" << subject << "'."; + return 1; + } + request->Set("cert_response", CertificateToString(certResponse)); Utility::SaveJsonFile(requestFile, 0600, request); + Log(LogInformation, "cli") + << "Signed certificate for '" << subject << "'."; + return 0; } diff --git a/lib/cli/pkiutility.cpp b/lib/cli/pkiutility.cpp index ed1f0e938..fffb51bfd 100644 --- a/lib/cli/pkiutility.cpp +++ b/lib/cli/pkiutility.cpp @@ -367,6 +367,9 @@ String PkiUtility::GetCertificateInformation(const boost::shared_ptr& cert std::stringstream info; info << String(data, data + length); + + BIO_free(out); + for (unsigned int i = 0; i < diglen; i++) { info << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << static_cast(md[i]) << ' '; diff --git a/lib/remote/jsonrpcconnection-pki.cpp b/lib/remote/jsonrpcconnection-pki.cpp index 471c5e3bf..6687cea4e 100644 --- a/lib/remote/jsonrpcconnection-pki.cpp +++ b/lib/remote/jsonrpcconnection-pki.cpp @@ -54,6 +54,28 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona else cert = StringToCertificate(certText); + ApiListener::Ptr listener = ApiListener::GetInstance(); + boost::shared_ptr cacert = GetX509Certificate(listener->GetCaPath()); + + bool signedByCA = VerifyCertificate(cacert, cert); + + if (signedByCA) { + time_t now; + time(&now); + + /* auto-renew all certificates which were created before 2017 to force an update of the CA, + * because Icinga versions older than 2.4 sometimes create certificates with an invalid + * serial number. */ + time_t forceRenewalEnd = 1483228800; /* January 1st, 2017 */ + time_t renewalStart = now + 30 * 24 * 60 * 60; + + if (X509_cmp_time(X509_get_notBefore(cert.get()), &forceRenewalEnd) != -1 && X509_cmp_time(X509_get_notAfter(cert.get()), &renewalStart) != -1) { + result->Set("status_code", 1); + result->Set("error", "The certificate cannot be renewed yet."); + return result; + } + } + unsigned int n; unsigned char digest[EVP_MAX_MD_SIZE]; @@ -72,9 +94,6 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona String requestDir = Application::GetLocalStateDir() + "/lib/icinga2/pki-requests"; String requestPath = requestDir + "/" + certFingerprint + ".json"; - ApiListener::Ptr listener = ApiListener::GetInstance(); - - boost::shared_ptr cacert = GetX509Certificate(listener->GetCaPath()); result->Set("ca", CertificateToString(cacert)); if (Utility::PathExists(requestPath)) { @@ -104,7 +123,7 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona if (!Utility::PathExists(GetIcingaCADir() + "/ca.key")) goto delayed_request; - if (!VerifyCertificate(cacert, cert)) { + if (!signedByCA) { String salt = listener->GetTicketSalt(); String ticket = params->Get("ticket"); @@ -119,19 +138,8 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona result->Set("error", "Invalid ticket."); return result; } - } else { - time_t renewalStart; - time(&renewalStart); - renewalStart += 30 * 24 * 60 * 60; - - if (X509_cmp_time(X509_get_notAfter(cert.get()), &renewalStart)) { - result->Set("status_code", 1); - result->Set("error", "The certificate cannot be renewed yet."); - return result; - } } - pubkey = boost::shared_ptr(X509_get_pubkey(cert.get()), EVP_PKEY_free); subject = X509_get_subject_name(cert.get());