diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index e8114fc57..762fbeb65 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -691,7 +691,7 @@ Now restart your Icinga 2 daemon to finish the installation! > Icinga 2 yet. The wizard asked you to manually copy the master's public > CA certificate file into `/var/lib/icinga2/certs/ca.crt`. > -> You need to manually sign the CSR on the master node. +> You need to [manually sign the CSR on the master node](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing-master). Restart Icinga 2 as requested. @@ -3329,6 +3329,8 @@ In case you don't need anything in `conf.d`, use the following command line: #### Node Setup with Agents/Satellites +##### Preparations + Make sure that the `/var/lib/icinga2/certs` directory exists and is owned by the `icinga` user (or the user Icinga 2 is running as). @@ -3353,29 +3355,49 @@ Example: --cert /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt ``` -Request the master certificate from the master host (`icinga2-master1.localdomain`) -and store it as `trusted-master.crt`. Review it and continue. +##### Verify Parent Connection + +In order to verify the parent connection and avoid man-in-the-middle attacks, +fetch the parent instance's certificate and verify that it matches the connection. +The `trusted-parent.crt` file is a temporary file passed to `node setup` in the +next step and does not need to be stored for later usage. Pass the following details to the `pki save-cert` CLI command: Parameter | Description --------------------|-------------------- - Client certificate files | **Required.** Pass the previously generated files using the `--key` and `--cert` parameters. Trusted parent certificate | **Required.** Store the parent's certificate file. Manually verify that you're trusting it. Parent host | **Required.** FQDN or IP address of the parent host. -Example: +Request the master certificate from the master host (`icinga2-master1.localdomain`) +and store it as `trusted-parent.crt`. Review it and continue. ``` -[root@icinga2-agent1.localdomain /]# icinga2 pki save-cert --key /var/lib/icinga2/certs/icinga2-agent1.localdomain.key \ ---cert /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt \ +[root@icinga2-agent1.localdomain /]# icinga2 pki save-cert \ --trustedcert /var/lib/icinga2/certs/trusted-parent.crt \ --host icinga2-master1.localdomain + +information/cli: Retrieving TLS certificate for 'icinga2-master1.localdomain:5665'. + + Subject: CN = icinga2-master1.localdomain + Issuer: CN = icinga2-master1.localdomain + Valid From: Feb 4 08:59:05 2020 GMT + Valid Until: Jan 31 08:59:05 2035 GMT + Fingerprint: B4 90 DE 46 81 DD 2E BF EE 9D D5 47 61 43 EF C6 6D 86 A6 CC + +*** +*** You have to ensure that this certificate actually matches the parent +*** instance's certificate in order to avoid man-in-the-middle attacks. +*** + +information/pki: Writing certificate to file '/var/lib/icinga2/certs/trusted-parent.crt'. ``` -Continue with the additional node setup step. Specify a local endpoint and zone name (`icinga2-agent1.localdomain`) +##### Node Setup + +Continue with the additional `node setup` step. Specify a local endpoint and zone name (`icinga2-agent1.localdomain`) and set the master host (`icinga2-master1.localdomain`) as parent zone configuration. Specify the path to -the previously stored trusted master certificate. +the previously stored trusted parent certificate (`trusted-parent.crt`). Pass the following details to the `node setup` CLI command: @@ -3383,7 +3405,7 @@ Pass the following details to the `node setup` CLI command: --------------------|-------------------- Common name (CN) | **Optional.** Specified with the `--cn` parameter. By convention this should be the host's FQDN. Request ticket | **Required.** Add the previously generated [ticket number](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing). - Trusted master certificate | **Required.** Add the previously fetched trusted master certificate (this step means that you've verified its origin). + Trusted parent certificate | **Required.** Trusted parent certificate file as connection verification (received via 'pki save-cert'). Parent host | **Optional.** FQDN or IP address of the parent host. This is where the command connects for CSR signing. If not specified, you need to manually copy the parent's public CA certificate file into `/var/lib/icinga2/certs/ca.crt` in order to start Icinga 2. Parent endpoint | **Required.** Specify the parent's endpoint name. Local zone name | **Required.** Specify the agent/satellite zone name. @@ -3438,7 +3460,7 @@ certificate file in `/var/lib/icinga2/certs/ca.crt`. Once Icinga 2 is started, i a ticket signing request to the parent node. If you have provided a ticket, the master node signs the request and sends it back to the agent/satellite which performs a certificate update in-memory. -In case you did not provide a ticket, you need to manually sign the CSR on the master node +In case you did not provide a ticket, you need to [manually sign the CSR on the master node](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing-master) which holds the CA's key pair. @@ -3451,15 +3473,6 @@ you can safely disable the `checker` feature. The `node setup` CLI command alrea [root@icinga2-agent1.localdomain /]# icinga2 feature disable checker ``` -Disable "conf.d" inclusion if this is a [top down](06-distributed-monitoring.md#distributed-monitoring-top-down) -configured agent. - -``` -[root@icinga2-agent1.localdomain /]# sed -i 's/include_recursive "conf.d"/\/\/include_recursive "conf.d"/g' /etc/icinga2/icinga2.conf -``` - -**Note**: This is the default since v2.9. - **Optional**: Add an ApiUser object configuration for remote troubleshooting. ``` @@ -3471,13 +3484,6 @@ object ApiUser "root" { EOF ``` -In case you've previously disabled the "conf.d" directory only -add the file file `conf.d/api-users.conf`: - -``` -[root@icinga2-agent1.localdomain /]# echo 'include "conf.d/api-users.conf"' >> /etc/icinga2/icinga2.conf -``` - Finally restart Icinga 2. ``` diff --git a/doc/16-upgrading-icinga-2.md b/doc/16-upgrading-icinga-2.md index b4a1c570d..7d18fed58 100644 --- a/doc/16-upgrading-icinga-2.md +++ b/doc/16-upgrading-icinga-2.md @@ -35,6 +35,9 @@ different results for As of v2.12 our [API](12-icinga2-api.md) URL endpoint [`/v1/actions/acknowledge-problem`](12-icinga2-api.md#icinga2-api-actions-acknowledge-problem) refuses acknowledging an already acknowledged checkable by overwriting the acknowledgement. To replace an acknowledgement you have to remove the old one before adding the new one. +The deprecated parameters `--cert` and `--key` for the `pki save-cert` CLI command +have been removed from the command and documentation. + ## Upgrading to v2.11 ### Bugfixes for 2.11 diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 7b8a6967f..72635f450 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -809,6 +809,23 @@ bool VerifyCertificate(const std::shared_ptr& caCertificate, const std::sh return rc == 1; } +bool IsCa(const std::shared_ptr& cacert) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* OpenSSL 1.1.x provides https://www.openssl.org/docs/man1.1.0/man3/X509_check_ca.html + * + * 0 if it is not CA certificate, + * 1 if it is proper X509v3 CA certificate with basicConstraints extension CA:TRUE, + * 3 if it is self-signed X509 v1 certificate + * 4 if it is certificate with keyUsage extension with bit keyCertSign set, but without basicConstraints, + * 5 if it has outdated Netscape Certificate Type extension telling that it is CA certificate. + */ + return (X509_check_ca(cacert.get()) == 1); +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + BOOST_THROW_EXCEPTION(std::invalid_argument("Not supported on this platform, OpenSSL version too old.")); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ +} + std::string to_string(const errinfo_openssl_error& e) { std::ostringstream tmp; diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index 0f120aaf9..715bfd426 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -47,6 +47,7 @@ String SHA256(const String& s); String RandomString(int length); bool VerifyCertificate(const std::shared_ptr& caCertificate, const std::shared_ptr& certificate); +bool IsCa(const std::shared_ptr& cacert); class openssl_error : virtual public std::exception, virtual public boost::exception { }; diff --git a/lib/cli/nodesetupcommand.cpp b/lib/cli/nodesetupcommand.cpp index ad21f1d72..30a8146bc 100644 --- a/lib/cli/nodesetupcommand.cpp +++ b/lib/cli/nodesetupcommand.cpp @@ -44,10 +44,10 @@ void NodeSetupCommand::InitParameters(boost::program_options::options_descriptio ("parent_zone", po::value(), "The name of the parent zone") ("listen", po::value(), "Listen on host,port") ("ticket", po::value(), "Generated ticket number for this request (optional)") - ("trustedcert", po::value(), "Trusted master certificate file") + ("trustedcert", po::value(), "Trusted parent certificate file as connection verification (received via 'pki save-cert')") ("cn", po::value(), "The certificate's common name") - ("accept-config", "Accept config from master") - ("accept-commands", "Accept commands from master") + ("accept-config", "Accept config from parent node") + ("accept-commands", "Accept commands from parent node") ("master", "Use setup for a master instance") ("global_zones", po::value >(), "The names of the additional global zones to 'global-templates' and 'director-global'.") ("disable-confd", "Disables the conf.d directory during the setup"); @@ -369,12 +369,22 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm, if (!vm.count("trustedcert")) { Log(LogCritical, "cli") << "Please pass the trusted cert retrieved from the parent node (master or satellite)\n" - << "(Hint: 'icinga2 pki save-cert --host --port <5665> --key local.key --cert local.crt --trustedcert parent.crt')."; + << "(Hint: 'icinga2 pki save-cert --host --port <5665> --key local.key --cert local.crt --trustedcert trusted-parent.crt')."; return 1; } trustedParentCert = GetX509Certificate(vm["trustedcert"].as()); + try { + if (IsCa(trustedParentCert)) { + Log(LogCritical, "cli") + << "The trusted parent certificate is NOT a client certificate. It seems you passed the 'ca.crt' CA certificate via '--trustedcert' parameter."; + return 1; + } + } catch (const std::exception&) { + /* Swallow the error and do not run the check on unsupported OpenSSL platforms. */ + } + Log(LogInformation, "cli") << "Verifying trusted certificate file '" << vm["trustedcert"].as() << "'."; diff --git a/lib/cli/pkisavecertcommand.cpp b/lib/cli/pkisavecertcommand.cpp index dbde44339..a946396f4 100644 --- a/lib/cli/pkisavecertcommand.cpp +++ b/lib/cli/pkisavecertcommand.cpp @@ -26,16 +26,14 @@ void PKISaveCertCommand::InitParameters(boost::program_options::options_descript boost::program_options::options_description& hiddenDesc) const { visibleDesc.add_options() - ("key", po::value(), "Key file path (input), obsolete") - ("cert", po::value(), "Certificate file path (input), obsolete") ("trustedcert", po::value(), "Trusted certificate file path (output)") - ("host", po::value(), "Icinga 2 host") + ("host", po::value(), "Parent Icinga instance to fetch the public TLS certificate from") ("port", po::value()->default_value("5665"), "Icinga 2 port"); } std::vector PKISaveCertCommand::GetArgumentSuggestions(const String& argument, const String& word) const { - if (argument == "key" || argument == "cert" || argument == "trustedcert") + if (argument == "trustedcert") return GetBashCompletionSuggestions("file", word); else if (argument == "host") return GetBashCompletionSuggestions("hostname", word); @@ -66,7 +64,7 @@ int PKISaveCertCommand::Run(const boost::program_options::variables_map& vm, con String port = vm["port"].as(); Log(LogInformation, "cli") - << "Retrieving X.509 certificate for '" << host << ":" << port << "'."; + << "Retrieving TLS certificate for '" << host << ":" << port << "'."; std::shared_ptr cert = PkiUtility::FetchCert(host, port);