]> granicus.if.org Git - icinga2/commitdiff
Update output format for the new CLI commands
authorGunnar Beutner <gunnar.beutner@icinga.com>
Tue, 5 Sep 2017 12:16:26 +0000 (14:16 +0200)
committerGunnar Beutner <gunnar.beutner@icinga.com>
Tue, 12 Sep 2017 10:52:49 +0000 (12:52 +0200)
refs #5450

lib/cli/calistcommand.cpp
lib/cli/calistcommand.hpp
lib/cli/casigncommand.cpp
lib/cli/pkiutility.cpp
lib/remote/jsonrpcconnection-pki.cpp

index 98012af9e9c9a2019af36a3b08b2de60203d9553..5cab64b6e54dd8261d74408228f1d8ecdb81eacd 100644 (file)
 #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<X509> 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<X509> certResponse = StringToCertificate(certResponseText);
-       }
+       result->Set("timestamp",  static_cast<double>(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<std::string>& 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;
 }
index 80ef378728a32eeaa2c1d4c8e3dafe00e9161baf..8694fd16c8892b46bbcc253df329c659593dda96 100644 (file)
@@ -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<std::string>& ap) const override;
 
 private:
index 247afebb3b6eb321b6470f8cdc05db4226d9c243..6ca2ff55d9ae361d056be4499eb68df39ba7272c 100644 (file)
@@ -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<X509> 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<X509> 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;
 }
index ed1f0e938b7069e9db83e5f7e0e7bbd0ce9fe690..fffb51bfdca80bf01e7ef453877add4890952b7a 100644 (file)
@@ -367,6 +367,9 @@ String PkiUtility::GetCertificateInformation(const boost::shared_ptr<X509>& 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<int>(md[i]) << ' ';
index 471c5e3bfbbcfb487b8f3ee3bdce5f218124c7ff..6687cea4e68a9640bff50a845210879f8f6a092b 100644 (file)
@@ -54,6 +54,28 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
        else
                cert = StringToCertificate(certText);
 
+       ApiListener::Ptr listener = ApiListener::GetInstance();
+       boost::shared_ptr<X509> 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<X509> 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<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
        subject = X509_get_subject_name(cert.get());