Merge pull request #7835 from Icinga/feature/improve-node-setup-save-cert-cli

Improve experience with 'Node Setup for Agents/Satellite'
This commit is contained in:
Michael Friedrich 2020-02-14 08:58:55 +01:00 committed by GitHub
commit b32ae4e8c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 36 deletions

View File

@ -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 > Icinga 2 yet. The wizard asked you to manually copy the master's public
> CA certificate file into `/var/lib/icinga2/certs/ca.crt`. > 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. 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 <a id="distributed-monitoring-automation-cli-node-setup-agent-satellite"></a> #### Node Setup with Agents/Satellites <a id="distributed-monitoring-automation-cli-node-setup-agent-satellite"></a>
##### Preparations
Make sure that the `/var/lib/icinga2/certs` directory exists and is owned by the `icinga` 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). user (or the user Icinga 2 is running as).
@ -3353,29 +3355,49 @@ Example:
--cert /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt --cert /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt
``` ```
Request the master certificate from the master host (`icinga2-master1.localdomain`) ##### Verify Parent Connection
and store it as `trusted-master.crt`. Review it and continue.
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: Pass the following details to the `pki save-cert` CLI command:
Parameter | Description 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. 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. 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 \ [root@icinga2-agent1.localdomain /]# icinga2 pki save-cert \
--cert /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt \
--trustedcert /var/lib/icinga2/certs/trusted-parent.crt \ --trustedcert /var/lib/icinga2/certs/trusted-parent.crt \
--host icinga2-master1.localdomain --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 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: 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. 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). 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 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. Parent endpoint | **Required.** Specify the parent's endpoint name.
Local zone name | **Required.** Specify the agent/satellite zone 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 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. 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. 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 [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. **Optional**: Add an ApiUser object configuration for remote troubleshooting.
``` ```
@ -3471,13 +3484,6 @@ object ApiUser "root" {
EOF 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. Finally restart Icinga 2.
``` ```

View File

@ -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. 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. 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 <a id="upgrading-to-2-11"></a> ## Upgrading to v2.11 <a id="upgrading-to-2-11"></a>
### Bugfixes for 2.11 <a id="upgrading-to-2-11-bugfixes"></a> ### Bugfixes for 2.11 <a id="upgrading-to-2-11-bugfixes"></a>

View File

@ -809,6 +809,23 @@ bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::sh
return rc == 1; return rc == 1;
} }
bool IsCa(const std::shared_ptr<X509>& 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::string to_string(const errinfo_openssl_error& e)
{ {
std::ostringstream tmp; std::ostringstream tmp;

View File

@ -47,6 +47,7 @@ String SHA256(const String& s);
String RandomString(int length); String RandomString(int length);
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate); bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
bool IsCa(const std::shared_ptr<X509>& cacert);
class openssl_error : virtual public std::exception, virtual public boost::exception { }; class openssl_error : virtual public std::exception, virtual public boost::exception { };

View File

@ -44,10 +44,10 @@ void NodeSetupCommand::InitParameters(boost::program_options::options_descriptio
("parent_zone", po::value<std::string>(), "The name of the parent zone") ("parent_zone", po::value<std::string>(), "The name of the parent zone")
("listen", po::value<std::string>(), "Listen on host,port") ("listen", po::value<std::string>(), "Listen on host,port")
("ticket", po::value<std::string>(), "Generated ticket number for this request (optional)") ("ticket", po::value<std::string>(), "Generated ticket number for this request (optional)")
("trustedcert", po::value<std::string>(), "Trusted master certificate file") ("trustedcert", po::value<std::string>(), "Trusted parent certificate file as connection verification (received via 'pki save-cert')")
("cn", po::value<std::string>(), "The certificate's common name") ("cn", po::value<std::string>(), "The certificate's common name")
("accept-config", "Accept config from master") ("accept-config", "Accept config from parent node")
("accept-commands", "Accept commands from master") ("accept-commands", "Accept commands from parent node")
("master", "Use setup for a master instance") ("master", "Use setup for a master instance")
("global_zones", po::value<std::vector<std::string> >(), "The names of the additional global zones to 'global-templates' and 'director-global'.") ("global_zones", po::value<std::vector<std::string> >(), "The names of the additional global zones to 'global-templates' and 'director-global'.")
("disable-confd", "Disables the conf.d directory during the setup"); ("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")) { if (!vm.count("trustedcert")) {
Log(LogCritical, "cli") Log(LogCritical, "cli")
<< "Please pass the trusted cert retrieved from the parent node (master or satellite)\n" << "Please pass the trusted cert retrieved from the parent node (master or satellite)\n"
<< "(Hint: 'icinga2 pki save-cert --host <masterhost> --port <5665> --key local.key --cert local.crt --trustedcert parent.crt')."; << "(Hint: 'icinga2 pki save-cert --host <parenthost> --port <5665> --key local.key --cert local.crt --trustedcert trusted-parent.crt').";
return 1; return 1;
} }
trustedParentCert = GetX509Certificate(vm["trustedcert"].as<std::string>()); trustedParentCert = GetX509Certificate(vm["trustedcert"].as<std::string>());
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") Log(LogInformation, "cli")
<< "Verifying trusted certificate file '" << vm["trustedcert"].as<std::string>() << "'."; << "Verifying trusted certificate file '" << vm["trustedcert"].as<std::string>() << "'.";

View File

@ -26,16 +26,14 @@ void PKISaveCertCommand::InitParameters(boost::program_options::options_descript
boost::program_options::options_description& hiddenDesc) const boost::program_options::options_description& hiddenDesc) const
{ {
visibleDesc.add_options() visibleDesc.add_options()
("key", po::value<std::string>(), "Key file path (input), obsolete")
("cert", po::value<std::string>(), "Certificate file path (input), obsolete")
("trustedcert", po::value<std::string>(), "Trusted certificate file path (output)") ("trustedcert", po::value<std::string>(), "Trusted certificate file path (output)")
("host", po::value<std::string>(), "Icinga 2 host") ("host", po::value<std::string>(), "Parent Icinga instance to fetch the public TLS certificate from")
("port", po::value<std::string>()->default_value("5665"), "Icinga 2 port"); ("port", po::value<std::string>()->default_value("5665"), "Icinga 2 port");
} }
std::vector<String> PKISaveCertCommand::GetArgumentSuggestions(const String& argument, const String& word) const std::vector<String> PKISaveCertCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{ {
if (argument == "key" || argument == "cert" || argument == "trustedcert") if (argument == "trustedcert")
return GetBashCompletionSuggestions("file", word); return GetBashCompletionSuggestions("file", word);
else if (argument == "host") else if (argument == "host")
return GetBashCompletionSuggestions("hostname", word); return GetBashCompletionSuggestions("hostname", word);
@ -66,7 +64,7 @@ int PKISaveCertCommand::Run(const boost::program_options::variables_map& vm, con
String port = vm["port"].as<std::string>(); String port = vm["port"].as<std::string>();
Log(LogInformation, "cli") Log(LogInformation, "cli")
<< "Retrieving X.509 certificate for '" << host << ":" << port << "'."; << "Retrieving TLS certificate for '" << host << ":" << port << "'.";
std::shared_ptr<X509> cert = PkiUtility::FetchCert(host, port); std::shared_ptr<X509> cert = PkiUtility::FetchCert(host, port);