mirror of https://github.com/Icinga/icinga2.git
Merge pull request #7843 from Icinga/feature/cli-pki-verify
CLI: Add `pki verify` command for better TLS certificate troubleshooting
This commit is contained in:
commit
06d0c3ea4e
|
@ -38,6 +38,7 @@ Supported commands:
|
|||
* pki save-cert (saves another Icinga 2 instance's certificate)
|
||||
* pki sign-csr (signs a CSR)
|
||||
* pki ticket (generates a ticket)
|
||||
* pki verify (verify TLS certificates: CN, signed by CA, is CA; Print certificate)
|
||||
* variable get (gets a variable)
|
||||
* variable list (lists all variables)
|
||||
|
||||
|
@ -570,7 +571,7 @@ You will need them in the [distributed monitoring chapter](06-distributed-monito
|
|||
|
||||
```
|
||||
# icinga2 pki --help
|
||||
icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0)
|
||||
icinga2 - The Icinga 2 network monitoring daemon (version: v2.12.0)
|
||||
|
||||
Usage:
|
||||
icinga2 <command> [<arguments>]
|
||||
|
@ -582,6 +583,7 @@ Supported commands:
|
|||
* pki save-cert (saves another Icinga 2 instance's certificate)
|
||||
* pki sign-csr (signs a CSR)
|
||||
* pki ticket (generates a ticket)
|
||||
* pki verify (verify TLS certificates: CN, signed by CA, is CA; Print certificate)
|
||||
|
||||
Global options:
|
||||
-h [ --help ] show this help message
|
||||
|
|
|
@ -1032,94 +1032,179 @@ Print the CA and client certificate and ensure that the following attributes are
|
|||
* v3 extensions must set the basic constraint for `CA:TRUE` (ca.crt) or `CA:FALSE` (client certificate).
|
||||
* Subject Alternative Name is set to the resolvable DNS name (required for REST API and browsers).
|
||||
|
||||
|
||||
Navigate into the local certificate store:
|
||||
|
||||
```
|
||||
$ cd /var/lib/icinga2/certs/
|
||||
```
|
||||
|
||||
Print the CA certificate:
|
||||
Make sure to verify the agents' certificate and its stored `ca.crt` in `/var/lib/icinga2/certs` and ensure that
|
||||
all instances (master, satellite, agent) are signed by the **same CA**.
|
||||
|
||||
Compare the `ca.crt` file from the agent node and compare it to your master's `ca.crt` file.
|
||||
|
||||
|
||||
Since 2.12, you can use the built-in CLI command `pki verify` to perform TLS certificate validation tasks.
|
||||
|
||||
> **Hint**
|
||||
>
|
||||
> The CLI command uses exit codes aligned to the [Plugin API specification](05-service-monitoring.md#service-monitoring-plugin-api).
|
||||
> Run the commands followed with `echo $?` to see the exit code.
|
||||
|
||||
These CLI commands can be used on Windows agents too without requiring the OpenSSL binary.
|
||||
|
||||
#### Print TLS Certificate <a id="troubleshooting-certificate-verification-print"></a>
|
||||
|
||||
Pass the certificate file to the `--cert` CLI command parameter to print its details.
|
||||
This prints a shorter version of `openssl x509 -in <file> -text`.
|
||||
|
||||
```
|
||||
$ openssl x509 -in ca.crt -text
|
||||
$ icinga2 pki verify --cert icinga2-agent2.localdomain.crt
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 1 (0x1)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=Icinga CA
|
||||
Validity
|
||||
Not Before: Feb 23 14:45:32 2016 GMT
|
||||
Not After : Feb 19 14:45:32 2031 GMT
|
||||
Subject: CN=Icinga CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
...
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
...
|
||||
information/cli: Printing certificate 'icinga2-agent2.localdomain.crt'
|
||||
|
||||
Version: 3
|
||||
Subject: CN = icinga2-agent2.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 14 11:29:36 2020 GMT
|
||||
Valid Until: Feb 10 11:29:36 2035 GMT
|
||||
Serial: 12:fe:a6:22:f5:e3:db:a2:95:8e:92:b2:af:1a:e3:01:44:c4:70:e0
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names: icinga2-agent2.localdomain
|
||||
Fingerprint: 40 98 A0 77 58 4F CA D1 05 AC 18 53 D7 52 8D D7 9C 7F 5A 23 B4 AF 63 A4 92 9D DC FF 89 EF F1 4C
|
||||
```
|
||||
|
||||
Print the client public certificate:
|
||||
You can also print the `ca.crt` certificate without any further checks using the `--cert` parameter.
|
||||
|
||||
#### Print and Verify CA Certificate <a id="troubleshooting-certificate-verification-print-verify-ca"></a>
|
||||
|
||||
The `--cacert` CLI parameter allows to check whether the given certificate file is a public CA certificate.
|
||||
|
||||
```
|
||||
$ openssl x509 -in icinga2-agent1.localdomain.crt -text
|
||||
$ icinga2 pki verify --cacert ca.crt
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
86:47:44:65:49:c6:65:6b:5e:6d:4f:a5:fe:6c:76:05:0b:1a:cf:34
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=Icinga CA
|
||||
Validity
|
||||
Not Before: Aug 20 16:20:05 2016 GMT
|
||||
Not After : Aug 17 16:20:05 2031 GMT
|
||||
Subject: CN=icinga2-agent1.localdomain
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
...
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:FALSE
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:icinga2-agent1.localdomain
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
...
|
||||
information/cli: Checking whether certificate 'ca.crt' is a valid CA certificate.
|
||||
|
||||
Version: 3
|
||||
Subject: CN = Icinga CA
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Jul 31 12:26:08 2019 GMT
|
||||
Valid Until: Jul 27 12:26:08 2034 GMT
|
||||
Serial: 89:fe:d6:12:66:25:3a:c5:07:c1:eb:d4:e6:f2:df:ca:13:6e:dc:e7
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names:
|
||||
Fingerprint: 9A 11 29 A8 A3 89 F8 56 30 1A E4 0A B2 6B 28 46 07 F0 14 17 BD 19 A4 FC BD 41 40 B5 1A 8F BF 20
|
||||
|
||||
information/cli: OK: CA certificate file 'ca.crt' was verified successfully.
|
||||
```
|
||||
|
||||
Make sure to verify the client's certificate and its received `ca.crt` in `/var/lib/icinga2/certs` and ensure that
|
||||
both instances are signed by the **same CA**.
|
||||
In case you pass a wrong certificate, an error is shown and the exit code is `2` (Critical).
|
||||
|
||||
```
|
||||
$ openssl verify -verbose -CAfile /var/lib/icinga2/certs/ca.crt /var/lib/icinga2/certs/icinga2-master1.localdomain.crt
|
||||
$ icinga2 pki verify --cacert icinga2-agent2.localdomain.crt
|
||||
|
||||
icinga2-master1.localdomain.crt: OK
|
||||
information/cli: Checking whether certificate 'icinga2-agent2.localdomain.crt' is a valid CA certificate.
|
||||
|
||||
Version: 3
|
||||
Subject: CN = icinga2-agent2.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 14 11:29:36 2020 GMT
|
||||
Valid Until: Feb 10 11:29:36 2035 GMT
|
||||
Serial: 12:fe:a6:22:f5:e3:db:a2:95:8e:92:b2:af:1a:e3:01:44:c4:70:e0
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names: icinga2-agent2.localdomain
|
||||
Fingerprint: 40 98 A0 77 58 4F CA D1 05 AC 18 53 D7 52 8D D7 9C 7F 5A 23 B4 AF 63 A4 92 9D DC FF 89 EF F1 4C
|
||||
|
||||
critical/cli: CRITICAL: The file 'icinga2-agent2.localdomain.crt' does not seem to be a CA certificate file.
|
||||
```
|
||||
|
||||
```
|
||||
$ openssl verify -verbose -CAfile /var/lib/icinga2/certs/ca.crt /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt
|
||||
#### Verify Certificate is signed by CA Certificate <a id="troubleshooting-certificate-verification-signed-by-ca"></a>
|
||||
|
||||
icinga2-agent1.localdomain.crt: OK
|
||||
```
|
||||
|
||||
Fetch the `ca.crt` file from the client node and compare it to your master's `ca.crt` file:
|
||||
Pass the certificate file to the `--cert` CLI parameter, and the `ca.crt` file to the `--cacert` parameter.
|
||||
Common troubleshooting scenarios involve self-signed certificates and untrusted agents resulting in disconnects.
|
||||
|
||||
```
|
||||
$ scp icinga2-agent1:/var/lib/icinga2/certs/ca.crt test-client-ca.crt
|
||||
$ diff -ur /var/lib/icinga2/certs/ca.crt test-client-ca.crt
|
||||
$ icinga2 pki verify --cert icinga2-agent2.localdomain.crt --cacert ca.crt
|
||||
|
||||
information/cli: Verifying certificate 'icinga2-agent2.localdomain.crt'
|
||||
|
||||
Version: 3
|
||||
Subject: CN = icinga2-agent2.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 14 11:29:36 2020 GMT
|
||||
Valid Until: Feb 10 11:29:36 2035 GMT
|
||||
Serial: 12:fe:a6:22:f5:e3:db:a2:95:8e:92:b2:af:1a:e3:01:44:c4:70:e0
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names: icinga2-agent2.localdomain
|
||||
Fingerprint: 40 98 A0 77 58 4F CA D1 05 AC 18 53 D7 52 8D D7 9C 7F 5A 23 B4 AF 63 A4 92 9D DC FF 89 EF F1 4C
|
||||
|
||||
information/cli: with CA certificate 'ca.crt'.
|
||||
|
||||
Version: 3
|
||||
Subject: CN = Icinga CA
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Jul 31 12:26:08 2019 GMT
|
||||
Valid Until: Jul 27 12:26:08 2034 GMT
|
||||
Serial: 89:fe:d6:12:66:25:3a:c5:07:c1:eb:d4:e6:f2:df:ca:13:6e:dc:e7
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names:
|
||||
Fingerprint: 9A 11 29 A8 A3 89 F8 56 30 1A E4 0A B2 6B 28 46 07 F0 14 17 BD 19 A4 FC BD 41 40 B5 1A 8F BF 20
|
||||
|
||||
information/cli: OK: Certificate with CN 'icinga2-agent2.localdomain' is signed by CA.
|
||||
```
|
||||
|
||||
#### Verify Certificate matches Common Name (CN) <a id="troubleshooting-certificate-verification-common-name-match"></a>
|
||||
|
||||
This allows to verify the common name inside the certificate with a given string parameter.
|
||||
Typical troubleshooting involve upper/lower case CNs (Windows).
|
||||
|
||||
```
|
||||
$ icinga2 pki verify --cert icinga2-agent2.localdomain.crt --cn icinga2-agent2.localdomain
|
||||
|
||||
information/cli: Verifying common name (CN) 'icinga2-agent2.localdomain in certificate 'icinga2-agent2.localdomain.crt'.
|
||||
|
||||
Version: 3
|
||||
Subject: CN = icinga2-agent2.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 14 11:29:36 2020 GMT
|
||||
Valid Until: Feb 10 11:29:36 2035 GMT
|
||||
Serial: 12:fe:a6:22:f5:e3:db:a2:95:8e:92:b2:af:1a:e3:01:44:c4:70:e0
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names: icinga2-agent2.localdomain
|
||||
Fingerprint: 40 98 A0 77 58 4F CA D1 05 AC 18 53 D7 52 8D D7 9C 7F 5A 23 B4 AF 63 A4 92 9D DC FF 89 EF F1 4C
|
||||
|
||||
information/cli: OK: CN 'icinga2-agent2.localdomain' matches certificate CN 'icinga2-agent2.localdomain'.
|
||||
```
|
||||
|
||||
In the example below, the certificate uses an upper case CN.
|
||||
|
||||
```
|
||||
$ icinga2 pki verify --cert icinga2-agent2.localdomain.crt --cn icinga2-agent2.localdomain
|
||||
|
||||
information/cli: Verifying common name (CN) 'icinga2-agent2.localdomain in certificate 'icinga2-agent2.localdomain.crt'.
|
||||
|
||||
Version: 3
|
||||
Subject: CN = ICINGA2-agent2.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 14 11:29:36 2020 GMT
|
||||
Valid Until: Feb 10 11:29:36 2035 GMT
|
||||
Serial: 12:fe:a6:22:f5:e3:db:a2:95:8e:92:b2:af:1a:e3:01:44:c4:70:e0
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Subject Alt Names: ICINGA2-agent2.localdomain
|
||||
Fingerprint: 40 98 A0 77 58 4F CA D1 05 AC 18 53 D7 52 8D D7 9C 7F 5A 23 B4 AF 63 A4 92 9D DC FF 89 EF F1 4C
|
||||
|
||||
critical/cli: CRITICAL: CN 'icinga2-agent2.localdomain' does NOT match certificate CN 'icinga2-agent2.localdomain'.
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Certificate Signing <a id="troubleshooting-certificate-signing"></a>
|
||||
|
||||
Icinga offers two methods:
|
||||
|
|
|
@ -10,6 +10,9 @@ follow the instructions for v2.7 too.
|
|||
|
||||
## Upgrading to v2.12 <a id="upgrading-to-2-12"></a>
|
||||
|
||||
* CLI
|
||||
* New `pki verify` CLI command for better [TLS certificate troubleshooting](15-troubleshooting.md#troubleshooting-certificate-verification)
|
||||
|
||||
### Behavior changes <a id="upgrading-to-2-12-behavior-changes"></a>
|
||||
|
||||
The behavior of multi parent [dependencies](03-monitoring-basics.md#dependencies) was fixed to e.g. render hosts unreachable when both router uplinks are down.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "base/convert.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/context.hpp"
|
||||
#include "base/convert.hpp"
|
||||
#include "base/utility.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/exception.hpp"
|
||||
|
@ -817,6 +818,14 @@ bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::sh
|
|||
X509_STORE_CTX_free(csc);
|
||||
X509_STORE_free(store);
|
||||
|
||||
if (rc == 0) {
|
||||
int err = X509_STORE_CTX_get_error(csc);
|
||||
|
||||
BOOST_THROW_EXCEPTION(openssl_error()
|
||||
<< boost::errinfo_api_function("X509_verify_cert")
|
||||
<< errinfo_openssl_error(err));
|
||||
}
|
||||
|
||||
return rc == 1;
|
||||
}
|
||||
|
||||
|
@ -837,6 +846,59 @@ bool IsCa(const std::shared_ptr<X509>& cacert)
|
|||
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
|
||||
}
|
||||
|
||||
int GetCertificateVersion(const std::shared_ptr<X509>& cert)
|
||||
{
|
||||
return X509_get_version(cert.get()) + 1;
|
||||
}
|
||||
|
||||
String GetSignatureAlgorithm(const std::shared_ptr<X509>& cert)
|
||||
{
|
||||
int alg;
|
||||
int sign_alg;
|
||||
X509_PUBKEY *key;
|
||||
X509_ALGOR *algor;
|
||||
|
||||
key = X509_get_X509_PUBKEY(cert.get());
|
||||
|
||||
X509_PUBKEY_get0_param(nullptr, nullptr, 0, &algor, key); //TODO: Error handling
|
||||
|
||||
alg = OBJ_obj2nid (algor->algorithm);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
sign_alg = OBJ_obj2nid((cert.get())->sig_alg->algorithm);
|
||||
#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */
|
||||
sign_alg = X509_get_signature_nid(cert.get());
|
||||
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
|
||||
|
||||
return Convert::ToString((sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln(sign_alg));
|
||||
}
|
||||
|
||||
Array::Ptr GetSubjectAltNames(const std::shared_ptr<X509>& cert)
|
||||
{
|
||||
GENERAL_NAMES* subjectAltNames = (GENERAL_NAMES*)X509_get_ext_d2i(cert.get(), NID_subject_alt_name, nullptr, nullptr);
|
||||
|
||||
Array::Ptr sans = new Array();
|
||||
|
||||
for (int i = 0; i < sk_GENERAL_NAME_num(subjectAltNames); i++) {
|
||||
GENERAL_NAME* gen = sk_GENERAL_NAME_value(subjectAltNames, i);
|
||||
if (gen->type == GEN_URI || gen->type == GEN_DNS || gen->type == GEN_EMAIL) {
|
||||
ASN1_IA5STRING *asn1_str = gen->d.uniformResourceIdentifier;
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
String san = Convert::ToString(ASN1_STRING_data(asn1_str));
|
||||
#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */
|
||||
String san = Convert::ToString(ASN1_STRING_get0_data(asn1_str));
|
||||
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
|
||||
|
||||
sans->Add(san);
|
||||
}
|
||||
}
|
||||
|
||||
GENERAL_NAMES_free(subjectAltNames);
|
||||
|
||||
return sans;
|
||||
}
|
||||
|
||||
std::string to_string(const errinfo_openssl_error& e)
|
||||
{
|
||||
std::ostringstream tmp;
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
#include "base/i2-base.hpp"
|
||||
#include "base/object.hpp"
|
||||
#include "base/shared.hpp"
|
||||
#include "base/array.hpp"
|
||||
#include "base/string.hpp"
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/comp.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
@ -50,6 +53,9 @@ String RandomString(int length);
|
|||
|
||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
||||
bool IsCa(const std::shared_ptr<X509>& cacert);
|
||||
int GetCertificateVersion(const std::shared_ptr<X509>& cert);
|
||||
String GetSignatureAlgorithm(const std::shared_ptr<X509>& cert);
|
||||
Array::Ptr GetSubjectAltNames(const std::shared_ptr<X509>& cert);
|
||||
|
||||
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ set(cli_SOURCES
|
|||
pkisavecertcommand.cpp pkisavecertcommand.hpp
|
||||
pkisigncsrcommand.cpp pkisigncsrcommand.hpp
|
||||
pkiticketcommand.cpp pkiticketcommand.hpp
|
||||
pkiverifycommand.cpp pkiverifycommand.hpp
|
||||
variablegetcommand.cpp variablegetcommand.hpp
|
||||
variablelistcommand.cpp variablelistcommand.hpp
|
||||
variableutility.cpp variableutility.hpp
|
||||
|
@ -40,7 +41,7 @@ endif()
|
|||
|
||||
add_library(cli OBJECT ${cli_SOURCES})
|
||||
|
||||
add_dependencies(cli base config remote)
|
||||
add_dependencies(cli base config icinga remote)
|
||||
|
||||
set_target_properties (
|
||||
cli PROPERTIES
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include "cli/pkiverifycommand.hpp"
|
||||
#include "icinga/service.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace icinga;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
REGISTER_CLICOMMAND("pki/verify", PKIVerifyCommand);
|
||||
|
||||
String PKIVerifyCommand::GetDescription() const
|
||||
{
|
||||
return "Verify TLS certificates: CN, signed by CA, is CA; Print certificate";
|
||||
}
|
||||
|
||||
String PKIVerifyCommand::GetShortDescription() const
|
||||
{
|
||||
return "verify TLS certificates: CN, signed by CA, is CA; Print certificate";
|
||||
}
|
||||
|
||||
void PKIVerifyCommand::InitParameters(boost::program_options::options_description& visibleDesc,
|
||||
boost::program_options::options_description& hiddenDesc) const
|
||||
{
|
||||
visibleDesc.add_options()
|
||||
("cn", po::value<std::string>(), "Common Name (optional). Use with '--cert' to check the CN in the certificate.")
|
||||
("cert", po::value<std::string>(), "Certificate file path (optional). Standalone: print certificate. With '--cacert': Verify against CA.")
|
||||
("cacert", po::value<std::string>(), "CA certificate file path (optional). If passed standalone, verifies whether this is a CA certificate");
|
||||
}
|
||||
|
||||
std::vector<String> PKIVerifyCommand::GetArgumentSuggestions(const String& argument, const String& word) const
|
||||
{
|
||||
if (argument == "cert" || argument == "cacert")
|
||||
return GetBashCompletionSuggestions("file", word);
|
||||
else
|
||||
return CLICommand::GetArgumentSuggestions(argument, word);
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry point for the "pki verify" CLI command.
|
||||
*
|
||||
* @returns An exit status.
|
||||
*/
|
||||
int PKIVerifyCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
|
||||
{
|
||||
String cn, certFile, caCertFile;
|
||||
|
||||
if (vm.count("cn"))
|
||||
cn = vm["cn"].as<std::string>();
|
||||
|
||||
if (vm.count("cert"))
|
||||
certFile = vm["cert"].as<std::string>();
|
||||
|
||||
if (vm.count("cacert"))
|
||||
caCertFile = vm["cacert"].as<std::string>();
|
||||
|
||||
/* Verify CN in certificate. */
|
||||
if (!cn.IsEmpty() && !certFile.IsEmpty()) {
|
||||
std::shared_ptr<X509> cert;
|
||||
try {
|
||||
cert = GetX509Certificate(certFile);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Verifying common name (CN) '" << cn << " in certificate '" << certFile << "'.";
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
|
||||
|
||||
String certCN = GetCertificateCN(cert);
|
||||
|
||||
if (cn == certCN) {
|
||||
Log(LogInformation, "cli")
|
||||
<< "OK: CN '" << cn << "' matches certificate CN '" << certCN << "'.";
|
||||
|
||||
return ServiceOK;
|
||||
} else {
|
||||
Log(LogCritical, "cli")
|
||||
<< "CRITICAL: CN '" << cn << "' does NOT match certificate CN '" << certCN << "'.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify certificate. */
|
||||
if (!certFile.IsEmpty() && !caCertFile.IsEmpty()) {
|
||||
std::shared_ptr<X509> cert;
|
||||
try {
|
||||
cert = GetX509Certificate(certFile);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
std::shared_ptr<X509> cacert;
|
||||
try {
|
||||
cacert = GetX509Certificate(caCertFile);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Cannot read CA certificate file '" << caCertFile << "'. Please ensure that it exists and is readable.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Verifying certificate '" << certFile << "'";
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< " with CA certificate '" << caCertFile << "'.";
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n";
|
||||
|
||||
String certCN = GetCertificateCN(cert);
|
||||
|
||||
bool signedByCA;
|
||||
|
||||
try {
|
||||
signedByCA = VerifyCertificate(cacert, cert);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA: " << DiagnosticInformation(ex, false);
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
if (signedByCA) {
|
||||
Log(LogInformation, "cli")
|
||||
<< "OK: Certificate with CN '" << certCN << "' is signed by CA.";
|
||||
|
||||
return ServiceOK;
|
||||
} else {
|
||||
Log(LogCritical, "cli")
|
||||
<< "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Standalone CA checks. */
|
||||
if (certFile.IsEmpty() && !caCertFile.IsEmpty()) {
|
||||
std::shared_ptr<X509> cacert;
|
||||
try {
|
||||
cacert = GetX509Certificate(caCertFile);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Cannot read CA certificate file '" << caCertFile << "'. Please ensure that it exists and is readable.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Checking whether certificate '" << caCertFile << "' is a valid CA certificate.";
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n";
|
||||
|
||||
if (IsCa(cacert)) {
|
||||
Log(LogInformation, "cli")
|
||||
<< "OK: CA certificate file '" << caCertFile << "' was verified successfully.\n";
|
||||
|
||||
return ServiceOK;
|
||||
} else {
|
||||
Log(LogCritical, "cli")
|
||||
<< "CRITICAL: The file '" << caCertFile << "' does not seem to be a CA certificate file.\n";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print certificate */
|
||||
if (!certFile.IsEmpty()) {
|
||||
std::shared_ptr<X509> cert;
|
||||
try {
|
||||
cert = GetX509Certificate(certFile);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Printing certificate '" << certFile << "'";
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
|
||||
|
||||
return ServiceOK;
|
||||
}
|
||||
|
||||
/* Error handling. */
|
||||
if (!cn.IsEmpty() && certFile.IsEmpty()) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "The '--cn' parameter requires the '--cert' parameter.";
|
||||
|
||||
return ServiceCritical;
|
||||
}
|
||||
|
||||
if (cn.IsEmpty() && certFile.IsEmpty() && caCertFile.IsEmpty()) {
|
||||
Log(LogInformation, "cli")
|
||||
<< "Please add the '--help' parameter to see all available options.";
|
||||
|
||||
return ServiceOK;
|
||||
}
|
||||
|
||||
return ServiceOK;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#ifndef PKIVERIFYCOMMAND_H
|
||||
#define PKIVERIFYCOMMAND_H
|
||||
|
||||
#include "cli/clicommand.hpp"
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* The "pki verify" command.
|
||||
*
|
||||
* @ingroup cli
|
||||
*/
|
||||
class PKIVerifyCommand final : public CLICommand
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(PKIVerifyCommand);
|
||||
|
||||
String GetDescription() const override;
|
||||
String GetShortDescription() const override;
|
||||
void InitParameters(boost::program_options::options_description& visibleDesc,
|
||||
boost::program_options::options_description& hiddenDesc) const override;
|
||||
std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const override;
|
||||
int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* PKIVERIFYCOMMAND_H */
|
|
@ -53,7 +53,11 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
|
|||
|
||||
String cn = GetCertificateCN(cert);
|
||||
|
||||
bool signedByCA = VerifyCertificate(cacert, cert);
|
||||
bool signedByCA;
|
||||
|
||||
try {
|
||||
signedByCA = VerifyCertificate(cacert, cert);
|
||||
} catch (const std::exception&) { } /* Swallow the exception on purpose, cacert will never be a non-CA certificate. */
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Received certificate request for CN '" << cn << "'"
|
||||
|
@ -199,7 +203,7 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
|
|||
* 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)) {
|
||||
if (!signedByCA) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "The CA in '" << listener->GetDefaultCaPath() << "' does not match the CA which Icinga uses "
|
||||
<< "for its own cluster connections. This is most likely a configuration problem.";
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "base/tcpsocket.hpp"
|
||||
#include "base/json.hpp"
|
||||
#include "base/utility.hpp"
|
||||
#include "base/convert.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include "remote/jsonrpc.hpp"
|
||||
#include <fstream>
|
||||
|
@ -321,27 +322,43 @@ String PkiUtility::GetCertificateInformation(const std::shared_ptr<X509>& cert)
|
|||
BIO *out = BIO_new(BIO_s_mem());
|
||||
String pre;
|
||||
|
||||
pre = "\n Subject: ";
|
||||
pre = "\n Version: " + Convert::ToString(GetCertificateVersion(cert));
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
|
||||
pre = "\n Subject: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
X509_NAME_print_ex(out, X509_get_subject_name(cert.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
|
||||
|
||||
pre = "\n Issuer: ";
|
||||
pre = "\n Issuer: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
X509_NAME_print_ex(out, X509_get_issuer_name(cert.get()), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
|
||||
|
||||
pre = "\n Valid From: ";
|
||||
pre = "\n Valid From: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
ASN1_TIME_print(out, X509_get_notBefore(cert.get()));
|
||||
|
||||
pre = "\n Valid Until: ";
|
||||
pre = "\n Valid Until: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
ASN1_TIME_print(out, X509_get_notAfter(cert.get()));
|
||||
|
||||
pre = "\n Fingerprint: ";
|
||||
pre = "\n Serial: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
ASN1_INTEGER *asn1_serial = X509_get_serialNumber(cert.get());
|
||||
for (int i = 0; i < asn1_serial->length; i++) {
|
||||
BIO_printf(out, "%02x%c", asn1_serial->data[i], ((i + 1 == asn1_serial->length) ? '\n' : ':'));
|
||||
}
|
||||
|
||||
pre = "\n Signature Algorithm: " + GetSignatureAlgorithm(cert);
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
|
||||
pre = "\n Subject Alt Names: " + GetSubjectAltNames(cert)->Join(" ");
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
|
||||
pre = "\n Fingerprint: ";
|
||||
BIO_write(out, pre.CStr(), pre.GetLength());
|
||||
unsigned char md[EVP_MAX_MD_SIZE];
|
||||
unsigned int diglen;
|
||||
X509_digest(cert.get(), EVP_sha1(), md, &diglen);
|
||||
X509_digest(cert.get(), EVP_sha256(), md, &diglen);
|
||||
|
||||
char *data;
|
||||
long length = BIO_get_mem_data(out, &data);
|
||||
|
|
Loading…
Reference in New Issue