mirror of https://github.com/Icinga/icinga2.git
Merge pull request #5571 from Icinga/feature/ca-proxy
Implement support for forwarding certificate signing requests in the cluster
This commit is contained in:
commit
cd31327f72
|
@ -2155,7 +2155,7 @@ Rephrased: If the parent service object changes into the `Warning` state, this
|
|||
dependency will fail and render all child objects (hosts or services) unreachable.
|
||||
|
||||
You can determine the child's reachability by querying the `is_reachable` attribute
|
||||
in for example [DB IDO](23-appendix.md#schema-db-ido-extensions).
|
||||
in for example [DB IDO](24-appendix.md#schema-db-ido-extensions).
|
||||
|
||||
### Implicit Dependencies for Services on Host <a id="dependencies-implicit-host-service"></a>
|
||||
|
||||
|
|
|
@ -189,53 +189,38 @@ The setup wizard will ensure that the following steps are taken:
|
|||
|
||||
* Enable the `api` feature.
|
||||
* Generate a new certificate authority (CA) in `/var/lib/icinga2/ca` if it doesn't exist.
|
||||
* Create a certificate signing request (CSR) for the local node.
|
||||
* Sign the CSR with the local CA and copy all files to the `/etc/icinga2/pki` directory.
|
||||
* Update the `zones.conf` file with the new zone hierarchy.
|
||||
* Update `/etc/icinga2/features-enabled/api.conf` and `constants.conf`.
|
||||
* Create a certificate for this node signed by the CA key.
|
||||
* Update the [zones.conf](04-configuring-icinga-2.md#zones-conf) file with the new zone hierarchy.
|
||||
* Update the [ApiListener](06-distributed-monitoring.md#distributed-monitoring-apilistener) and [constants](04-configuring-icinga-2.md#constants-conf) configuration.
|
||||
|
||||
Here is an example of a master setup for the `icinga2-master1.localdomain` node on CentOS 7:
|
||||
|
||||
[root@icinga2-master1.localdomain /]# icinga2 node wizard
|
||||
Welcome to the Icinga 2 Setup Wizard!
|
||||
```
|
||||
[root@icinga2-master1.localdomain /]# icinga2 node wizard
|
||||
|
||||
We'll guide you through all required configuration details.
|
||||
Welcome to the Icinga 2 Setup Wizard!
|
||||
|
||||
Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]: n
|
||||
Starting the Master setup routine...
|
||||
Please specify the common name (CN) [icinga2-master1.localdomain]: icinga2-master1.localdomain
|
||||
Checking for existing certificates for common name 'icinga2-master1.localdomain'...
|
||||
Certificates not yet generated. Running 'api setup' now.
|
||||
information/cli: Generating new CA.
|
||||
information/base: Writing private key to '/var/lib/icinga2/ca/ca.key'.
|
||||
information/base: Writing X509 certificate to '/var/lib/icinga2/ca/ca.crt'.
|
||||
information/cli: Generating new CSR in '/etc/icinga2/pki/icinga2-master1.localdomain.csr'.
|
||||
information/base: Writing private key to '/etc/icinga2/pki/icinga2-master1.localdomain.key'.
|
||||
information/base: Writing certificate signing request to '/etc/icinga2/pki/icinga2-master1.localdomain.csr'.
|
||||
information/cli: Signing CSR with CA and writing certificate to '/etc/icinga2/pki/icinga2-master1.localdomain.crt'.
|
||||
information/cli: Copying CA certificate to '/etc/icinga2/pki/ca.crt'.
|
||||
Generating master configuration for Icinga 2.
|
||||
information/cli: Adding new ApiUser 'root' in '/etc/icinga2/conf.d/api-users.conf'.
|
||||
information/cli: Enabling the 'api' feature.
|
||||
Enabling feature api. Make sure to restart Icinga 2 for these changes to take effect.
|
||||
information/cli: Dumping config items to file '/etc/icinga2/zones.conf'.
|
||||
information/cli: Created backup file '/etc/icinga2/zones.conf.orig'.
|
||||
Please specify the API bind host/port (optional):
|
||||
Bind Host []:
|
||||
Bind Port []:
|
||||
information/cli: Created backup file '/etc/icinga2/features-available/api.conf.orig'.
|
||||
information/cli: Updating constants.conf.
|
||||
information/cli: Created backup file '/etc/icinga2/constants.conf.orig'.
|
||||
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
|
||||
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
|
||||
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
|
||||
Done.
|
||||
We will guide you through all required configuration details.
|
||||
|
||||
Now restart your Icinga 2 daemon to finish the installation!
|
||||
Please specify if this is a satellite/client setup ('n' installs a master setup) [Y/n]: n
|
||||
|
||||
[root@icinga2-master1.localdomain /]# systemctl restart icinga2
|
||||
Starting the Master setup routine...
|
||||
|
||||
As you can see, the CA public and private key are stored in the `/var/lib/icinga2/ca` directory.
|
||||
Please specify the common name (CN) [icinga2-master1.localdomain]: icinga2-master1.localdomain
|
||||
Reconfiguring Icinga...
|
||||
Checking for existing certificates for common name 'master1'...
|
||||
Generating master configuration for Icinga 2.
|
||||
|
||||
Please specify the API bind host/port (optional):
|
||||
Bind Host []:
|
||||
Bind Port []:
|
||||
|
||||
Done.
|
||||
|
||||
Now restart your Icinga 2 daemon to finish the installation!
|
||||
```
|
||||
|
||||
You can verify that the CA public and private keys are stored in the `/var/lib/icinga2/ca` directory.
|
||||
Keep this path secure and include it in your [backups](02-getting-started.md#install-backup).
|
||||
|
||||
In case you lose the CA private key you have to generate a new CA for signing new client
|
||||
|
@ -246,23 +231,53 @@ Once the master setup is complete, you can also use this node as primary [CSR au
|
|||
master. The following section will explain how to use the CLI commands in order to fetch their
|
||||
signed certificate from this master node.
|
||||
|
||||
## Client/Satellite Setup <a id="distributed-monitoring-setup-satellite-client"></a>
|
||||
## Signing Certificates on the Master <a id="distributed-monitoring-setup-sign-certificates-master"></a>
|
||||
|
||||
This section describes the setup of a satellite and/or client connected to an
|
||||
existing master node setup. If you haven't done so already, please [run the master setup](06-distributed-monitoring.md#distributed-monitoring-setup-master).
|
||||
All certificates must be signed by the same certificate authority (CA). This ensures
|
||||
that all nodes trust each other in a distributed monitoring environment.
|
||||
|
||||
Icinga 2 on the master node must be running and accepting connections on port `5665`.
|
||||
This CA is generated during the [master setup](06-distributed-monitoring.md#distributed-monitoring-setup-master)
|
||||
and should be the same on all master instances.
|
||||
|
||||
You can avoid signing and deploying certificates [manually](#06-distributed-monitoring.md#distributed-monitoring-advanced-hints-certificates)
|
||||
by using built-in methods for auto-signing certificate signing requests (CSR):
|
||||
|
||||
* [CSR Auto-Signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing) which uses a client ticket generated on the master as trust identifier.
|
||||
* [On-Demand CSR Signing](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing) which allows to sign pending certificate requests on the master.
|
||||
|
||||
Both methods are described in detail below.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> [On-Demand CSR Signing](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing) is available in Icinga 2 v2.8+.
|
||||
|
||||
### CSR Auto-Signing <a id="distributed-monitoring-setup-csr-auto-signing"></a>
|
||||
|
||||
The `node wizard` command will set up a satellite/client using CSR auto-signing. This
|
||||
involves that the setup wizard sends a certificate signing request (CSR) to the
|
||||
master node.
|
||||
There is a security mechanism in place which requires the client to send in a valid
|
||||
ticket for CSR auto-signing.
|
||||
A client which sends a certificate signing request (CSR) must authenticate itself
|
||||
in a trusted way. The master generates a client ticket which is included in this request.
|
||||
That way the master can verify that the request matches the previously trusted ticket
|
||||
and sign the request.
|
||||
|
||||
This ticket must be generated beforehand. The `ticket_salt` attribute for the [ApiListener](09-object-types.md#objecttype-apilistener)
|
||||
must be configured in order to make this work.
|
||||
> **Note**
|
||||
>
|
||||
> Icinga 2 v2.8 adds the possibility to forward signing requests on a satellite
|
||||
> to the master node. This helps with the setup of [three level clusters](#06-distributed-monitoring.md#distributed-monitoring-scenarios-master-satellite-client)
|
||||
> and more.
|
||||
|
||||
Advantages:
|
||||
|
||||
* Nodes can be installed by different users who have received the client ticket.
|
||||
* No manual interaction necessary on the master node.
|
||||
* Automation tools like Puppet, Ansible, etc. can retrieve the pre-generated ticket in their client catalog
|
||||
and run the node setup directly.
|
||||
|
||||
Disadvantages:
|
||||
|
||||
* Tickets need to be generated on the master and copied to client setup wizards.
|
||||
* No central signing management.
|
||||
|
||||
|
||||
Setup wizards for satellite/client nodes will ask you for this specific client ticket.
|
||||
|
||||
There are two possible ways to retrieve the ticket:
|
||||
|
||||
|
@ -280,7 +295,7 @@ The following example shows how to generate a ticket on the master node `icinga2
|
|||
[root@icinga2-master1.localdomain /]# icinga2 pki ticket --cn icinga2-client1.localdomain
|
||||
|
||||
Querying the [Icinga 2 API](12-icinga2-api.md#icinga2-api) on the master requires an [ApiUser](12-icinga2-api.md#icinga2-api-authentication)
|
||||
object with at least the `actions/generate-ticket`.
|
||||
object with at least the `actions/generate-ticket` permission.
|
||||
|
||||
[root@icinga2-master1.localdomain /]# vim /etc/icinga2/conf.d/api-users.conf
|
||||
|
||||
|
@ -303,6 +318,55 @@ Example: Retrieve the ticket on the Puppet master node and send the compiled cat
|
|||
to the authorized Puppet agent node which will invoke the
|
||||
[automated setup steps](06-distributed-monitoring.md#distributed-monitoring-automation-cli-node-setup).
|
||||
|
||||
### On-Demand CSR Signing <a id="distributed-monitoring-setup-on-demand-csr-signing"></a>
|
||||
|
||||
Icinga 2 v2.8 adds the possibility to sign certificates from clients without
|
||||
requiring a client ticket for auto-signing.
|
||||
|
||||
Instead, the client sends a certificate signing request to specified parent node.
|
||||
This could either be directly the master, or a satellite which forwards the request
|
||||
to the signing master.
|
||||
|
||||
Advantages:
|
||||
|
||||
* Central certificate request signing management.
|
||||
* No pre-generated ticket is required for client setups.
|
||||
|
||||
Disadvantages:
|
||||
|
||||
* Asynchronous step for automated deployments.
|
||||
* Needs client verification on the master.
|
||||
|
||||
|
||||
You can list certificate requests by using the `ca list` CLI command. This also shows
|
||||
which requests already have been signed.
|
||||
|
||||
```
|
||||
[root@icinga2-master1.localdomain /]# icinga2 ca list
|
||||
Fingerprint | Timestamp | Signed | Subject
|
||||
-----------------------------------------------------------------|---------------------|--------|--------
|
||||
403da5b228df384f07f980f45ba50202529cded7c8182abf96740660caa09727 | 2017/09/06 17:02:40 | * | CN = icinga2-client1.localdomain
|
||||
71700c28445109416dd7102038962ac3fd421fbb349a6e7303b6033ec1772850 | 2017/09/06 17:20:02 | | CN = icinga2-client2.localdomain
|
||||
```
|
||||
|
||||
**Tip**: Add `--json` to the CLI command to retrieve the details in JSON format.
|
||||
|
||||
If you want to sign a specific request, you need to use the `ca sign` CLI command
|
||||
and pass its fingerprint as argument.
|
||||
|
||||
```
|
||||
[root@icinga2-master1.localdomain /]# icinga2 ca sign 71700c28445109416dd7102038962ac3fd421fbb349a6e7303b6033ec1772850
|
||||
information/cli: Signed certificate for 'CN = icinga2-client2.localdomain'.
|
||||
```
|
||||
|
||||
## Client/Satellite Setup <a id="distributed-monitoring-setup-satellite-client"></a>
|
||||
|
||||
This section describes the setup of a satellite and/or client connected to an
|
||||
existing master node setup. If you haven't done so already, please [run the master setup](06-distributed-monitoring.md#distributed-monitoring-setup-master).
|
||||
|
||||
Icinga 2 on the master node must be running and accepting connections on port `5665`.
|
||||
|
||||
|
||||
### Client/Satellite Linux Setup <a id="distributed-monitoring-setup-client-linux"></a>
|
||||
|
||||
Please ensure that you've run all the steps mentioned in the [client/satellite section](06-distributed-monitoring.md#distributed-monitoring-setup-satellite-client).
|
||||
|
@ -311,20 +375,166 @@ Install the [Icinga 2 package](02-getting-started.md#setting-up-icinga2) and set
|
|||
the required [plugins](02-getting-started.md#setting-up-check-plugins) if you haven't done
|
||||
so already.
|
||||
|
||||
The next step is to run the `node wizard` CLI command. Prior to that
|
||||
ensure to collect the required information:
|
||||
The next step is to run the `node wizard` CLI command.
|
||||
|
||||
In this example we're generating a ticket on the master node `icinga2-master1.localdomain` for the client `icinga2-client1.localdomain`:
|
||||
|
||||
[root@icinga2-master1.localdomain /]# icinga2 pki ticket --cn icinga2-client1.localdomain
|
||||
4f75d2ecd253575fe9180938ebff7cbca262f96e
|
||||
|
||||
Note: You don't need this step if you have chosen to use [On-Demand CSR Signing](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing).
|
||||
|
||||
Start the wizard on the client `icinga2-client1.localdomain`:
|
||||
|
||||
```
|
||||
[root@icinga2-client1.localdomain /]# icinga2 node wizard
|
||||
|
||||
Welcome to the Icinga 2 Setup Wizard!
|
||||
|
||||
We will guide you through all required configuration details.
|
||||
```
|
||||
|
||||
Press `Enter` or add `y` to start a satellite or client setup.
|
||||
|
||||
```
|
||||
Please specify if this is a satellite/client setup ('n' installs a master setup) [Y/n]:
|
||||
```
|
||||
|
||||
Press `Enter` to use the proposed name in brackets, or add a specific common name (CN). By convention
|
||||
this should be the FQDN.
|
||||
|
||||
```
|
||||
Starting the Client/Satellite setup routine...
|
||||
|
||||
Please specify the common name (CN) [icinga2-client1.localdomain]: icinga2-client1.localdomain
|
||||
```
|
||||
|
||||
Specify the direct parent for this node. This could be your primary master `icinga2-master1.localdomain`
|
||||
or a satellite node in a multi level cluster scenario.
|
||||
|
||||
```
|
||||
Please specify the parent endpoint(s) (master or satellite) where this node should connect to:
|
||||
Master/Satellite Common Name (CN from your master/satellite node): icinga2-master1.localdomain
|
||||
```
|
||||
|
||||
Press `Enter` or choose `y` to establish a connection to the parent node.
|
||||
|
||||
```
|
||||
Do you want to establish a connection to the parent node from this node? [Y/n]:
|
||||
```
|
||||
|
||||
> **Note:**
|
||||
>
|
||||
> If this node cannot connect to the parent node, choose `n`. The setup
|
||||
> wizard will provide instructions for this scenario -- signing questions are disabled then.
|
||||
|
||||
Add the connection details for `icinga2-master1.localdomain`.
|
||||
|
||||
```
|
||||
Please specify the master/satellite connection information:
|
||||
Master/Satellite endpoint host (IP address or FQDN): 192.168.56.101
|
||||
Master/Satellite endpoint port [5665]: 5665
|
||||
```
|
||||
|
||||
You can add more parent nodes if necessary. Press `Enter` or choose `n`
|
||||
if you don't want to add any. This comes in handy if you have more than one
|
||||
parent node, e.g. two masters or two satellites.
|
||||
|
||||
```
|
||||
Add more master/satellite endpoints? [y/N]:
|
||||
```
|
||||
|
||||
Verify the parent node's certificate:
|
||||
|
||||
```
|
||||
Parent certificate information:
|
||||
|
||||
Subject: CN = icinga2-master1.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Sep 7 13:41:24 2017 GMT
|
||||
Valid Until: Sep 3 13:41:24 2032 GMT
|
||||
Fingerprint: AC 99 8B 2B 3D B0 01 00 E5 21 FA 05 2E EC D5 A9 EF 9E AA E3
|
||||
|
||||
Is this information correct? [y/N]: y
|
||||
```
|
||||
|
||||
The setup wizard fetches the parent node's certificate and ask
|
||||
you to verify this information. This is to prevent MITM attacks or
|
||||
any kind of untrusted parent relationship.
|
||||
|
||||
Note: The certificate is not fetched if you have chosen not to connect
|
||||
to the parent node.
|
||||
|
||||
Proceed with adding the optional client ticket for [CSR auto-signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing):
|
||||
|
||||
```
|
||||
Please specify the request ticket generated on your Icinga 2 master (optional).
|
||||
(Hint: # icinga2 pki ticket --cn 'icinga2-client1.localdomain'):
|
||||
4f75d2ecd253575fe9180938ebff7cbca262f96e
|
||||
```
|
||||
|
||||
In case you've chosen to use [On-Demand CSR Signing](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing)
|
||||
you can leave the ticket question blank.
|
||||
|
||||
Instead, Icinga 2 tells you to approve the request later on the master node.
|
||||
|
||||
```
|
||||
No ticket was specified. Please approve the certificate signing request manually
|
||||
on the master (see 'icinga2 ca list' and 'icinga2 ca sign --help' for details).
|
||||
```
|
||||
|
||||
You can optionally specify a different bind host and/or port.
|
||||
|
||||
```
|
||||
Please specify the API bind host/port (optional):
|
||||
Bind Host []:
|
||||
Bind Port []:
|
||||
```
|
||||
|
||||
The next step asks you to accept configuration (required for [config sync mode](06-distributed-monitoring.md#distributed-monitoring-top-down-config-sync))
|
||||
and commands (required for [command endpoint mode](06-distributed-monitoring.md#distributed-monitoring-top-down-command-endpoint)).
|
||||
|
||||
```
|
||||
Accept config from parent node? [y/N]: y
|
||||
Accept commands from parent node? [y/N]: y
|
||||
```
|
||||
|
||||
The wizard proceeds and you are good to go.
|
||||
|
||||
```
|
||||
Reconfiguring Icinga...
|
||||
|
||||
Done.
|
||||
|
||||
Now restart your Icinga 2 daemon to finish the installation!
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If you have chosen not to connect to the parent node, you cannot start
|
||||
> 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.
|
||||
|
||||
Restart Icinga 2 as requested.
|
||||
|
||||
```
|
||||
[root@icinga2-client1.localdomain /]# systemctl restart icinga2
|
||||
```
|
||||
|
||||
Here is an overview of all parameters in detail:
|
||||
|
||||
Parameter | Description
|
||||
--------------------|--------------------
|
||||
Common name (CN) | **Required.** By convention this should be the host's FQDN. Defaults to the FQDN.
|
||||
Master common name | **Required.** Use the common name you've specified for your master node before.
|
||||
Establish connection to the master | **Optional.** Whether the client should attempt to connect to the master or not. Defaults to `y`.
|
||||
Master endpoint host | **Required if the the client needs to connect to the master.** The master's IP address or FQDN. This information is included in the `Endpoint` object configuration in the `zones.conf` file.
|
||||
Master endpoint port | **Optional if the the client needs to connect to the master.** The master's listening port. This information is included in the `Endpoint` object configuration.
|
||||
Add more master endpoints | **Optional.** If you have multiple master nodes configured, add them here.
|
||||
Master connection for CSR auto-signing | **Required.** The master node's IP address or FQDN and port where the client should request a certificate from. Defaults to the master endpoint host.
|
||||
Certificate information | **Required.** Verify that the connecting host really is the requested master node.
|
||||
Request ticket | **Required.** Paste the previously generated [ticket number](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing).
|
||||
Establish connection to the parent node | **Optional.** Whether the node should attempt to connect to the parent node or not. Defaults to `y`.
|
||||
Master/Satellite endpoint host | **Required if the the client needs to connect to the master/satellite.** The parent endpoint's IP address or FQDN. This information is included in the `Endpoint` object configuration in the `zones.conf` file.
|
||||
Master/Satellite endpoint port | **Optional if the the client needs to connect to the master/satellite.** The parent endpoints's listening port. This information is included in the `Endpoint` object configuration.
|
||||
Add more master/satellite endpoints | **Optional.** If you have multiple master/satellite nodes configured, add them here.
|
||||
Parent Certificate information | **Required.** Verify that the connecting host really is the requested master node.
|
||||
Request ticket | **Optional.** Add the [ticket](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing) generated on the master.
|
||||
API bind host | **Optional.** Allows to specify the address the ApiListener is bound to. For advanced usage only.
|
||||
API bind port | **Optional.** Allows to specify the port the ApiListener is bound to. For advanced usage only (requires changing the default port 5665 everywhere).
|
||||
Accept config | **Optional.** Whether this node accepts configuration sync from the master node (required for [config sync mode](06-distributed-monitoring.md#distributed-monitoring-top-down-config-sync)). For [security reasons](06-distributed-monitoring.md#distributed-monitoring-security) this defaults to `n`.
|
||||
|
@ -334,84 +544,28 @@ The setup wizard will ensure that the following steps are taken:
|
|||
|
||||
* Enable the `api` feature.
|
||||
* Create a certificate signing request (CSR) for the local node.
|
||||
* Request a signed certificate with the provided ticket number on the master node.
|
||||
* Allow to verify the master's certificate.
|
||||
* Store the signed client certificate and ca.crt in `/etc/icinga2/pki`.
|
||||
* Request a signed certificate i(optional with the provided ticket number) on the master node.
|
||||
* Allow to verify the parent node's certificate.
|
||||
* Store the signed client certificate and ca.crt in `/var/lib/icinga2/certs`.
|
||||
* Update the `zones.conf` file with the new zone hierarchy.
|
||||
* Update `/etc/icinga2/features-enabled/api.conf` (`accept_config`, `accept_commands`) and `constants.conf`.
|
||||
|
||||
In this example we're generating a ticket on the master node `icinga2-master1.localdomain` for the client `icinga2-client1.localdomain`:
|
||||
|
||||
[root@icinga2-master1.localdomain /]# icinga2 pki ticket --cn icinga2-client1.localdomain
|
||||
4f75d2ecd253575fe9180938ebff7cbca262f96e
|
||||
You can verify that the certificate files are stored in the `/var/lib/icinga2/certs` directory.
|
||||
|
||||
The following example shows a client setup for the `icinga2-client1.localdomain` node on CentOS 7. This client
|
||||
is configured to accept configuration and commands from the master:
|
||||
|
||||
[root@icinga2-client1.localdomain /]# icinga2 node wizard
|
||||
Welcome to the Icinga 2 Setup Wizard!
|
||||
|
||||
We'll guide you through all required configuration details.
|
||||
|
||||
Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]:
|
||||
Starting the Node setup routine...
|
||||
Please specify the common name (CN) [icinga2-client1.localdomain]: icinga2-client1.localdomain
|
||||
Please specify the master endpoint(s) this node should connect to:
|
||||
Master Common Name (CN from your master setup): icinga2-master1.localdomain
|
||||
Do you want to establish a connection to the master from this node? [Y/n]:
|
||||
Please fill out the master connection information:
|
||||
Master endpoint host (Your master's IP address or FQDN): 192.168.56.101
|
||||
Master endpoint port [5665]:
|
||||
Add more master endpoints? [y/N]:
|
||||
Please specify the master connection for CSR auto-signing (defaults to master endpoint host):
|
||||
Host [192.168.56.101]: 192.168.2.101
|
||||
Port [5665]:
|
||||
information/base: Writing private key to '/etc/icinga2/pki/icinga2-client1.localdomain.key'.
|
||||
information/base: Writing X509 certificate to '/etc/icinga2/pki/icinga2-client1.localdomain.crt'.
|
||||
information/cli: Fetching public certificate from master (192.168.56.101, 5665):
|
||||
|
||||
Certificate information:
|
||||
|
||||
Subject: CN = icinga2-master1.localdomain
|
||||
Issuer: CN = Icinga CA
|
||||
Valid From: Feb 23 14:45:32 2016 GMT
|
||||
Valid Until: Feb 19 14:45:32 2031 GMT
|
||||
Fingerprint: AC 99 8B 2B 3D B0 01 00 E5 21 FA 05 2E EC D5 A9 EF 9E AA E3
|
||||
|
||||
Is this information correct? [y/N]: y
|
||||
information/cli: Received trusted master certificate.
|
||||
|
||||
Please specify the request ticket generated on your Icinga 2 master.
|
||||
(Hint: # icinga2 pki ticket --cn 'icinga2-client1.localdomain'): 4f75d2ecd253575fe9180938ebff7cbca262f96e
|
||||
information/cli: Requesting certificate with ticket '4f75d2ecd253575fe9180938ebff7cbca262f96e'.
|
||||
|
||||
information/cli: Created backup file '/etc/icinga2/pki/icinga2-client1.localdomain.crt.orig'.
|
||||
information/cli: Writing signed certificate to file '/etc/icinga2/pki/icinga2-client1.localdomain.crt'.
|
||||
information/cli: Writing CA certificate to file '/etc/icinga2/pki/ca.crt'.
|
||||
Please specify the API bind host/port (optional):
|
||||
Bind Host []:
|
||||
Bind Port []:
|
||||
Accept config from master? [y/N]: y
|
||||
Accept commands from master? [y/N]: y
|
||||
information/cli: Disabling the Notification feature.
|
||||
Disabling feature notification. Make sure to restart Icinga 2 for these changes to take effect.
|
||||
information/cli: Enabling the Apilistener feature.
|
||||
information/cli: Generating local zones.conf.
|
||||
information/cli: Dumping config items to file '/etc/icinga2/zones.conf'.
|
||||
information/cli: Updating constants.conf.
|
||||
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
|
||||
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
|
||||
Done.
|
||||
|
||||
Now restart your Icinga 2 daemon to finish the installation!
|
||||
|
||||
[root@icinga2-client1.localdomain /]# systemctl restart icinga2
|
||||
|
||||
As you can see, the certificate files are stored in the `/etc/icinga2/pki` directory.
|
||||
> **Note**
|
||||
>
|
||||
> If the client is not directly connected to the certificate signing master,
|
||||
> signing requests and responses might need some minutes to fully update the client certificates.
|
||||
>
|
||||
> If you have chosen to use [On-Demand CSR Signing](06-distributed-monitoring.md#distributed-monitoring-setup-on-demand-csr-signing)
|
||||
> certificates need to be signed on the master first.
|
||||
|
||||
Now that you've successfully installed a satellite/client, please proceed to
|
||||
the [configuration modes](06-distributed-monitoring.md#distributed-monitoring-configuration-modes).
|
||||
|
||||
|
||||
|
||||
### Client/Satellite Windows Setup <a id="distributed-monitoring-setup-client-windows"></a>
|
||||
|
||||
Download the MSI-Installer package from [https://packages.icinga.com/windows/](https://packages.icinga.com/windows/).
|
||||
|
@ -2276,6 +2430,13 @@ Open Icinga Web 2 and check your newly added Windows NSClient++ check :)
|
|||
You can find additional hints in this section if you prefer to go your own route
|
||||
with automating setups (setup, certificates, configuration).
|
||||
|
||||
### Certificate Auto-Renewal <a id="distributed-monitoring-certificate-auto-renewal"></a>
|
||||
|
||||
Icinga 2 v2.8+ adds the possibility that nodes request certificate updates
|
||||
on their own. If their expiration date is soon enough, they automatically
|
||||
renew their already signed certificate by sending a signing request to the
|
||||
parent node.
|
||||
|
||||
### High-Availability for Icinga 2 Features <a id="distributed-monitoring-high-availability-features"></a>
|
||||
|
||||
All nodes in the same zone require that you enable the same features for high-availability (HA).
|
||||
|
@ -2469,24 +2630,24 @@ Sign the CSR with the previously created CA:
|
|||
|
||||
[root@icinga2-master1.localdomain /root]# icinga2 pki sign-csr --csr icinga2-master1.localdomain.csr --cert icinga2-master1.localdomain
|
||||
|
||||
Copy the host's certificate files and the public CA certificate to `/etc/icinga2/pki`:
|
||||
Copy the host's certificate files and the public CA certificate to `/var/lib/icinga2/certs`:
|
||||
|
||||
[root@icinga2-master1.localdomain /root]# mkdir -p /etc/icinga2/pki
|
||||
[root@icinga2-master1.localdomain /root]# cp icinga2-master1.localdomain.{crt,key} /etc/icinga2/pki
|
||||
[root@icinga2-master1.localdomain /root]# cp /var/lib/icinga2/ca/ca.crt /etc/icinga2/pki
|
||||
[root@icinga2-master1.localdomain /root]# mkdir -p /var/lib/icinga2/certs
|
||||
[root@icinga2-master1.localdomain /root]# cp icinga2-master1.localdomain.{crt,key} /var/lib/icinga2/certs
|
||||
[root@icinga2-master1.localdomain /root]# cp /var/lib/icinga2/ca/ca.crt /var/lib/icinga2/certs
|
||||
|
||||
Ensure that proper permissions are set (replace `icinga` with the Icinga 2 daemon user):
|
||||
|
||||
[root@icinga2-master1.localdomain /root]# chown -R icinga:icinga /etc/icinga2/pki
|
||||
[root@icinga2-master1.localdomain /root]# chmod 600 /etc/icinga2/pki/*.key
|
||||
[root@icinga2-master1.localdomain /root]# chmod 644 /etc/icinga2/pki/*.crt
|
||||
[root@icinga2-master1.localdomain /root]# chown -R icinga:icinga /var/lib/icinga2/certs
|
||||
[root@icinga2-master1.localdomain /root]# chmod 600 /var/lib/icinga2/certs/*.key
|
||||
[root@icinga2-master1.localdomain /root]# chmod 644 /var/lib/icinga2/certs/*.crt
|
||||
|
||||
The CA public and private key are stored in the `/var/lib/icinga2/ca` directory. Keep this path secure and include
|
||||
it in your backups.
|
||||
|
||||
Example for creating multiple certificates at once:
|
||||
|
||||
[root@icinga2-master1.localdomain /etc/icinga2/pki]# for node in icinga2-master1.localdomain icinga2-master2.localdomain icinga2-satellite1.localdomain; do icinga2 pki new-cert --cn $node --csr $node.csr --key $node.key; done
|
||||
[root@icinga2-master1.localdomain /var/lib/icinga2/certs]# for node in icinga2-master1.localdomain icinga2-master2.localdomain icinga2-satellite1.localdomain; do icinga2 pki new-cert --cn $node --csr $node.csr --key $node.key; done
|
||||
information/base: Writing private key to 'icinga2-master1.localdomain.key'.
|
||||
information/base: Writing certificate signing request to 'icinga2-master1.localdomain.csr'.
|
||||
information/base: Writing private key to 'icinga2-master2.localdomain.key'.
|
||||
|
@ -2494,7 +2655,7 @@ Example for creating multiple certificates at once:
|
|||
information/base: Writing private key to 'icinga2-satellite1.localdomain.key'.
|
||||
information/base: Writing certificate signing request to 'icinga2-satellite1.localdomain.csr'.
|
||||
|
||||
[root@icinga2-master1.localdomain /etc/icinga2/pki]# for node in icinga2-master1.localdomain icinga2-master2.localdomain icinga2-satellite1.localdomain; do sudo icinga2 pki sign-csr --csr $node.csr --cert $node.crt; done
|
||||
[root@icinga2-master1.localdomain /var/lib/icinga2/certs]# for node in icinga2-master1.localdomain icinga2-master2.localdomain icinga2-satellite1.localdomain; do sudo icinga2 pki sign-csr --csr $node.csr --cert $node.crt; done
|
||||
information/pki: Writing certificate to file 'icinga2-master1.localdomain.crt'.
|
||||
information/pki: Writing certificate to file 'icinga2-master2.localdomain.crt'.
|
||||
information/pki: Writing certificate to file 'icinga2-satellite1.localdomain.crt'.
|
||||
|
@ -2555,11 +2716,11 @@ host/port you can specify it like this:
|
|||
|
||||
#### Node Setup with Satellites/Clients <a id="distributed-monitoring-automation-cli-node-setup-satellite-client"></a>
|
||||
|
||||
Make sure that the `/etc/icinga2/pki` exists and is owned by the `icinga`
|
||||
Make sure that the `/var/lib/icinga2/certs` exists and is owned by the `icinga`
|
||||
user (or the user Icinga 2 is running as).
|
||||
|
||||
[root@icinga2-client1.localdomain /]# mkdir -p /etc/icinga2/pki
|
||||
[root@icinga2-client1.localdomain /]# chown -R icinga:icinga /etc/icinga2/pki
|
||||
[root@icinga2-client1.localdomain /]# mkdir -p /var/lib/icinga2/certs
|
||||
[root@icinga2-client1.localdomain /]# chown -R icinga:icinga /var/lib/icinga2/certs
|
||||
|
||||
First you'll need to generate a new local self-signed certificate.
|
||||
Pass the following details to the `pki new-cert` CLI command:
|
||||
|
@ -2567,13 +2728,13 @@ Pass the following details to the `pki new-cert` CLI command:
|
|||
Parameter | Description
|
||||
--------------------|--------------------
|
||||
Common name (CN) | **Required.** By convention this should be the host's FQDN. Defaults to the FQDN.
|
||||
Client certificate files | **Required.** These generated files will be put into the specified location (--key and --file). By convention this should be using `/etc/icinga2/pki` as directory.
|
||||
Client certificate files | **Required.** These generated files will be put into the specified location (--key and --file). By convention this should be using `/var/lib/icinga2/certs` as directory.
|
||||
|
||||
Example:
|
||||
|
||||
[root@icinga2-client1.localdomain /]# icinga2 pki new-cert --cn icinga2-client1.localdomain \
|
||||
--key /etc/icinga2/pki/icinga2-client1.localdomain.key \
|
||||
--cert /etc/icinga2/pki/icinga2-client1.localdomain.crt
|
||||
--key /var/lib/icinga2/certs/icinga2-client1.localdomain.key \
|
||||
--cert /var/lib/icinga2/certs/icinga2-client1.localdomain.crt
|
||||
|
||||
Request the master certificate from the master host (`icinga2-master1.localdomain`)
|
||||
and store it as `trusted-master.crt`. Review it and continue.
|
||||
|
@ -2588,9 +2749,9 @@ Pass the following details to the `pki save-cert` CLI command:
|
|||
|
||||
Example:
|
||||
|
||||
[root@icinga2-client1.localdomain /]# icinga2 pki save-cert --key /etc/icinga2/pki/icinga2-client1.localdomain.key \
|
||||
--cert /etc/icinga2/pki/icinga2-client1.localdomain.crt \
|
||||
--trustedcert /etc/icinga2/pki/trusted-master.crt \
|
||||
[root@icinga2-client1.localdomain /]# icinga2 pki save-cert --key /var/lib/icinga2/certs/icinga2-client1.localdomain.key \
|
||||
--cert /var/lib/icinga2/certs/icinga2-client1.localdomain.crt \
|
||||
--trustedcert /var/lib/icinga2/certs/trusted-master.crt \
|
||||
--host icinga2-master1.localdomain
|
||||
|
||||
Continue with the additional node setup step. Specify a local endpoint and zone name (`icinga2-client1.localdomain`)
|
||||
|
@ -2617,7 +2778,7 @@ Example:
|
|||
--endpoint icinga2-master1.localdomain \
|
||||
--zone icinga2-client1.localdomain \
|
||||
--master_host icinga2-master1.localdomain \
|
||||
--trustedcert /etc/icinga2/pki/trusted-master.crt \
|
||||
--trustedcert /var/lib/icinga2/certs/trusted-master.crt \
|
||||
--accept-commands --accept-config
|
||||
|
||||
In case the client should connect to the master node, you'll
|
||||
|
|
|
@ -181,7 +181,7 @@ SNMP Traps can be received and filtered by using [SNMPTT](http://snmptt.sourcefo
|
|||
and specific trap handlers passing the check results to Icinga 2.
|
||||
|
||||
Following the SNMPTT [Format](http://snmptt.sourceforge.net/docs/snmptt.shtml#SNMPTT.CONF-FORMAT)
|
||||
documentation and the Icinga external command syntax found [here](23-appendix.md#external-commands-list-detail)
|
||||
documentation and the Icinga external command syntax found [here](24-appendix.md#external-commands-list-detail)
|
||||
we can create generic services that can accommodate any number of hosts for a given scenario.
|
||||
|
||||
### Simple SNMP Traps <a id="simple-traps"></a>
|
||||
|
|
|
@ -31,12 +31,15 @@ The `NodeName` constant must be defined in [constants.conf](04-configuring-icing
|
|||
|
||||
Example:
|
||||
|
||||
object ApiListener "api" {
|
||||
cert_path = SysconfDir + "/icinga2/pki/" + NodeName + ".crt"
|
||||
key_path = SysconfDir + "/icinga2/pki/" + NodeName + ".key"
|
||||
ca_path = SysconfDir + "/icinga2/pki/ca.crt"
|
||||
}
|
||||
```
|
||||
object ApiListener "api" {
|
||||
cert_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".crt"
|
||||
key_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".key"
|
||||
ca_path = LocalStateDir + "/lib/icinga2/certs/ca.crt"
|
||||
|
||||
ticket_salt = TicketSalt
|
||||
}
|
||||
```
|
||||
|
||||
Configuration Attributes:
|
||||
|
||||
|
@ -45,6 +48,7 @@ Configuration Attributes:
|
|||
cert\_path |**Required.** Path to the public key.
|
||||
key\_path |**Required.** Path to the private key.
|
||||
ca\_path |**Required.** Path to the CA certificate file.
|
||||
ticket\_salt |**Optional.** Private key for auto-signing. **Required** for a signing master instance.
|
||||
crl\_path |**Optional.** Path to the CRL file.
|
||||
bind\_host |**Optional.** The IP address the api listener should be bound to. Defaults to `0.0.0.0`.
|
||||
bind\_port |**Optional.** The port the api listener should be bound to. Defaults to `5665`.
|
||||
|
|
|
@ -78,11 +78,16 @@ Example for PostgreSQL:
|
|||
(1 Zeile)
|
||||
|
||||
|
||||
A detailed list on the available table attributes can be found in the [DB IDO Schema documentation](23-appendix.md#schema-db-ido).
|
||||
A detailed list on the available table attributes can be found in the [DB IDO Schema documentation](24-appendix.md#schema-db-ido).
|
||||
|
||||
|
||||
## External Commands <a id="external-commands"></a>
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Please use the [REST API](12-icinga2-api.md#icinga2-api) as modern and secure alternative
|
||||
> for external actions.
|
||||
|
||||
Icinga 2 provides an external command pipe for processing commands
|
||||
triggering specific actions (for example rescheduling a service check
|
||||
through the web interface).
|
||||
|
@ -106,7 +111,7 @@ a forced service check:
|
|||
Oct 17 15:01:25 icinga-server icinga2: Executing external command: [1382014885] SCHEDULE_FORCED_SVC_CHECK;localhost;ping4;1382014885
|
||||
Oct 17 15:01:25 icinga-server icinga2: Rescheduling next check for service 'ping4'
|
||||
|
||||
A list of currently supported external commands can be found [here](23-appendix.md#external-commands-list-detail).
|
||||
A list of currently supported external commands can be found [here](24-appendix.md#external-commands-list-detail).
|
||||
|
||||
Detailed information on the commands and their required parameters can be found
|
||||
on the [Icinga 1.x documentation](https://docs.icinga.com/latest/en/extcommands2.html).
|
||||
|
@ -441,7 +446,7 @@ re-implementation of the Livestatus protocol which is compatible with MK
|
|||
Livestatus.
|
||||
|
||||
Details on the available tables and attributes with Icinga 2 can be found
|
||||
in the [Livestatus Schema](23-appendix.md#schema-livestatus) section.
|
||||
in the [Livestatus Schema](24-appendix.md#schema-livestatus) section.
|
||||
|
||||
You can enable Livestatus using icinga2 feature enable:
|
||||
|
||||
|
@ -517,7 +522,7 @@ Example using the tcp socket listening on port `6558`:
|
|||
|
||||
### Livestatus COMMAND Queries <a id="livestatus-command-queries"></a>
|
||||
|
||||
A list of available external commands and their parameters can be found [here](23-appendix.md#external-commands-list-detail)
|
||||
A list of available external commands and their parameters can be found [here](24-appendix.md#external-commands-list-detail)
|
||||
|
||||
$ echo -e 'COMMAND <externalcommandstring>' | netcat 127.0.0.1 6558
|
||||
|
||||
|
@ -618,7 +623,7 @@ Default separators.
|
|||
|
||||
The `commands` table is populated with `CheckCommand`, `EventCommand` and `NotificationCommand` objects.
|
||||
|
||||
A detailed list on the available table attributes can be found in the [Livestatus Schema documentation](23-appendix.md#schema-livestatus).
|
||||
A detailed list on the available table attributes can be found in the [Livestatus Schema documentation](24-appendix.md#schema-livestatus).
|
||||
|
||||
|
||||
## Status Data Files <a id="status-data"></a>
|
||||
|
|
|
@ -29,7 +29,7 @@ findings and details please.
|
|||
* The newest Icinga 2 crash log if relevant, located in `/var/log/icinga2/crash`
|
||||
* Additional details
|
||||
* If the check command failed, what's the output of your manual plugin tests?
|
||||
* In case of [debugging](20-development.md#development) Icinga 2, the full back traces and outputs
|
||||
* In case of [debugging](21-development.md#development) Icinga 2, the full back traces and outputs
|
||||
|
||||
## Analyze your Environment <a id="troubleshooting-analyze-environment"></a>
|
||||
|
||||
|
@ -666,9 +666,9 @@ the following
|
|||
|
||||
Steps on the client `icinga2-node2.localdomain`:
|
||||
|
||||
# ls -la /etc/icinga2/pki
|
||||
# ls -la /var/lib/icinga2/certs
|
||||
|
||||
# cd /etc/icinga2/pki/
|
||||
# cd /var/lib/icinga2/certs/
|
||||
# openssl x509 -in icinga2-node2.localdomain.crt -text
|
||||
Certificate:
|
||||
Data:
|
||||
|
@ -688,7 +688,7 @@ Steps on the client `icinga2-node2.localdomain`:
|
|||
|
||||
Try to manually connect from `icinga2-node2.localdomain` to the master node `icinga2-node1.localdomain`:
|
||||
|
||||
# openssl s_client -CAfile /etc/icinga2/pki/ca.crt -cert /etc/icinga2/pki/icinga2-node2.localdomain.crt -key /etc/icinga2/pki/icinga2-node2.localdomain.key -connect icinga2-node1.localdomain:5665
|
||||
# openssl s_client -CAfile /var/lib/icinga2/certs/ca.crt -cert /var/lib/icinga2/certs/icinga2-node2.localdomain.crt -key /var/lib/icinga2/certs/icinga2-node2.localdomain.key -connect icinga2-node1.localdomain:5665
|
||||
|
||||
CONNECTED(00000003)
|
||||
---
|
||||
|
@ -712,19 +712,19 @@ If these messages do not go away, make sure to [verify the master and client cer
|
|||
|
||||
#### Cluster Troubleshooting SSL Certificate Verification <a id="troubleshooting-cluster-ssl-certificate-verification"></a>
|
||||
|
||||
Make sure to verify the client's certificate and its received `ca.crt` in `/etc/icinga2/pki` and ensure that
|
||||
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**.
|
||||
|
||||
# openssl verify -verbose -CAfile /etc/icinga2/pki/ca.crt /etc/icinga2/pki/icinga2-node1.localdomain.crt
|
||||
# openssl verify -verbose -CAfile /var/lib/icinga2/certs/ca.crt /var/lib/icinga2/certs/icinga2-node1.localdomain.crt
|
||||
icinga2-node1.localdomain.crt: OK
|
||||
|
||||
# openssl verify -verbose -CAfile /etc/icinga2/pki/ca.crt /etc/icinga2/pki/icinga2-node2.localdomain.crt
|
||||
# openssl verify -verbose -CAfile /var/lib/icinga2/certs/ca.crt /var/lib/icinga2/certs/icinga2-node2.localdomain.crt
|
||||
icinga2-node2.localdomain.crt: OK
|
||||
|
||||
Fetch the `ca.crt` file from the client node and compare it to your master's `ca.crt` file:
|
||||
|
||||
# scp icinga2-node2:/etc/icinga2/pki/ca.crt test-client-ca.crt
|
||||
# diff -ur /etc/icinga2/pki/ca.crt test-client-ca.crt
|
||||
# scp icinga2-node2:/var/lib/icinga2/certs/ca.crt test-client-ca.crt
|
||||
# diff -ur /var/lib/icinga2/certs/ca.crt test-client-ca.crt
|
||||
|
||||
On SLES11 you'll need to use the `openssl1` command instead of `openssl`.
|
||||
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
# Upgrading Icinga 2 <a id="upgrading-icinga-2"></a>
|
||||
# Upgrading Icinga 2 <a id="upgrading-icinga-2"></a>
|
||||
|
||||
Upgrading Icinga 2 is usually quite straightforward. Ordinarily the only manual steps involved
|
||||
are scheme updates for the IDO database.
|
||||
|
||||
## Upgrading to v2.8 <a id="upgrading-to-2-8"></a>
|
||||
|
||||
The default certificate path was changed from `/etc/icinga2/pki` to
|
||||
`/var/lib/icinga2/certs`.
|
||||
|
||||
This applies to Windows clients in the same way: `%ProgramData%\etc\icinga2\pki`
|
||||
was moved to `%ProgramData%`\var\lib\icinga2\certs`.
|
||||
|
||||
The [setup CLI commands](06-distributed-monitoring.md#distributed-monitoring-setup-master) and the
|
||||
default [ApiListener configuration](06-distributed-monitoring.md#distributed-monitoring-apilistener)
|
||||
have been adjusted to these paths too.
|
||||
|
||||
## Upgrading the MySQL database <a id="upgrading-mysql-db"></a>
|
||||
|
||||
If you're upgrading an existing Icinga 2 instance, you should check the
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
# Technical Concepts <a id="technical-concepts"></a>
|
||||
|
||||
This chapter provides insights into specific Icinga 2
|
||||
components, libraries, features and any other technical concept
|
||||
and design.
|
||||
|
||||
<!--
|
||||
## Application <a id="technical-concepts-application"></a>
|
||||
|
||||
### Libraries <a id="technical-concepts-application-libraries"></a>
|
||||
|
||||
|
||||
## Configuration <a id="technical-concepts-configuration"></a>
|
||||
|
||||
### Compiler <a id="technical-concepts-configuration-compiler"></a>
|
||||
-->
|
||||
|
||||
## Features <a id="technical-concepts-features"></a>
|
||||
|
||||
Features are implemented in specific libraries and can be enabled
|
||||
using CLI commands.
|
||||
|
||||
Features either write specific data or receive data.
|
||||
|
||||
Examples for writing data: [DB IDO](14-features.md#db-ido), [Graphite](14-features.md#graphite-carbon-cache-writer), [InfluxDB](14-features.md#influxdb-writer). [GELF](14-features.md#gelfwriter), etc.
|
||||
Examples for receiving data: [REST API](12-icinga2-api.md#icinga2-api), etc.
|
||||
|
||||
The implementation of features makes use of existing libraries
|
||||
and functionality. This makes the code more abstract, but shorter
|
||||
and easier to read.
|
||||
|
||||
Features register callback functions on specific events they want
|
||||
to handle. For example the `GraphiteWriter` feature subscribes to
|
||||
new CheckResult events.
|
||||
|
||||
Each time Icinga 2 receives and processes a new check result, this
|
||||
event is triggered and forwarded to all subscribers.
|
||||
|
||||
The GraphiteWriter feature calls the registered function and processes
|
||||
the received data. Features which connect Icinga 2 to external interfaces
|
||||
normally parse and reformat the received data into an applicable format.
|
||||
|
||||
The GraphiteWriter uses a TCP socket to communicate with the carbon cache
|
||||
daemon of Graphite. The InfluxDBWriter is instead writing bulk metric messages
|
||||
to InfluxDB's HTTP API.
|
||||
|
||||
|
||||
|
||||
## Cluster <a id="technical-concepts-cluster"></a>
|
||||
|
||||
### Communication <a id="technical-concepts-cluster-communication"></a>
|
||||
|
||||
Icinga 2 uses its own certificate authority (CA) by default. The
|
||||
public and private CA keys can be generated on the signing master.
|
||||
|
||||
Each node certificate must be signed by the private CA key.
|
||||
|
||||
Note: The following description uses `parent node` and `child node`.
|
||||
This also applies to nodes in the same cluster zone.
|
||||
|
||||
During the connection attempt, an SSL handshake is performed.
|
||||
If the public certificate of a child node is not signed by the same
|
||||
CA, the child node is not trusted and the connection will be closed.
|
||||
|
||||
If the SSL handshake succeeds, the parent node reads the
|
||||
certificate's common name (CN) of the child node and looks for
|
||||
a local Endpoint object name configuration.
|
||||
|
||||
If there is no Endpoint object found, further communication
|
||||
(runtime and config sync, etc.) is terminated.
|
||||
|
||||
The child node also checks the CN from the parent node's public
|
||||
certificate. If the child node does not find any local Endpoint
|
||||
object name configuration, it will not trust the parent node.
|
||||
|
||||
Both checks prevent accepting cluster messages from an untrusted
|
||||
source endpoint.
|
||||
|
||||
If an Endpoint match was found, there is one additional security
|
||||
mechanism in place: Endpoints belong to a Zone hierarchy.
|
||||
|
||||
Several cluster messages can only be sent "top down", others like
|
||||
check results are allowed being sent from the child to the parent node.
|
||||
|
||||
Once this check succeeds the cluster messages are exchanged and processed.
|
||||
|
||||
|
||||
### CSR Signing <a id="technical-concepts-cluster-csr-signing"></a>
|
||||
|
||||
In order to make things easier, Icinga 2 provides built-in methods
|
||||
to allow child nodes to request a signed certificate from the
|
||||
signing master.
|
||||
|
||||
Icinga 2 v2.8 introduces the possibility to request certificates
|
||||
from indirectly connected nodes. This is required for multi level
|
||||
cluster environments with masters, satellites and clients.
|
||||
|
||||
CSR Signing in general starts with the master setup. This step
|
||||
ensures that the master is in a working CSR signing state with:
|
||||
|
||||
* public and private CA key in `/var/lib/icinga2/ca`
|
||||
* private `TicketSalt` constant defined inside the `api` feature
|
||||
* Cluster communication is ready and Icinga 2 listens on port 5665
|
||||
|
||||
The child node setup which is run with CLI commands will now
|
||||
attempt to connect to the parent node. This is not necessarily
|
||||
the signing master instance, but could also be a parent satellite node.
|
||||
|
||||
During this process the child node asks the user to verify the
|
||||
parent node's public certificate to prevent MITM attacks.
|
||||
|
||||
There are two methods to request signed certificates:
|
||||
|
||||
* Add the ticket into the request. This ticket was generated on the master
|
||||
beforehand and contains hashed details for which client it has been created.
|
||||
The signing master uses this information to automatically sign the certificate
|
||||
request.
|
||||
|
||||
* Do not add a ticket into the request. It will be sent to the signing master
|
||||
which stores the pending request. Manual user interaction with CLI commands
|
||||
is necessary to sign the request.
|
||||
|
||||
The certificate request is sent as `pki::RequestCertificate` cluster
|
||||
message to the parent node.
|
||||
|
||||
If the parent node is not the signing master, it stores the request
|
||||
in `/var/lib/icinga2/certificate-requests` and forwards the
|
||||
cluster message to its parent node.
|
||||
|
||||
Once the message arrives on the signing master, it first verifies that
|
||||
the sent certificate request is valid. This is to prevent unwanted errors
|
||||
or modified requests from the "proxy" node.
|
||||
|
||||
After verification, the signing master checks if the request contains
|
||||
a valid signing ticket. It hashes the certificate's common name and
|
||||
compares the value to the received ticket number.
|
||||
|
||||
If the ticket is valid, the certificate request is immediately signed
|
||||
with CA key. The request is sent back to the client inside a `pki::UpdateCertificate`
|
||||
cluster message.
|
||||
|
||||
If the child node was not the certificate request origin, it only updates
|
||||
the cached request for the child node and send another cluster message
|
||||
down to its child node (e.g. from a satellite to a client).
|
||||
|
||||
|
||||
If no ticket was specified, the signing master waits until the
|
||||
`ca sign` CLI command manually signed the certificate.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Push notifications for manual request signing is not yet implemented (TODO).
|
||||
|
||||
Once the child node reconnects it synchronizes all signed certificate requests.
|
||||
This takes some minutes and requires all nodes to reconnect to each other.
|
||||
|
||||
|
||||
#### CSR Signing: Clients without parent connection <a id="technical-concepts-cluster-csr-signing-clients-no-connection"></a>
|
||||
|
||||
There is an additional scenario: The setup on a child node does
|
||||
not necessarily need a connection to the parent node.
|
||||
|
||||
This mode leaves the node in a semi-configured state. You need
|
||||
to manually copy the master's public CA key into `/var/lib/icinga2/certs/ca.crt`
|
||||
on the client before starting Icinga 2.
|
||||
|
||||
The parent node needs to actively connect to the child node.
|
||||
Once this connections succeeds, the child node will actively
|
||||
request a signed certificate.
|
||||
|
||||
The update procedure works the same way as above.
|
||||
|
||||
### High Availability <a id="technical-concepts-cluster-ha"></a>
|
||||
|
||||
High availability is automatically enabled between two nodes in the same
|
||||
cluster zone.
|
||||
|
||||
This requires the same configuration and enabled features on both nodes.
|
||||
|
||||
HA zone members trust each other and share event updates as cluster messages.
|
||||
This includes for example check results, next check timestamp updates, acknowledgements
|
||||
or notifications.
|
||||
|
||||
This ensures that both nodes are synchronized. If one node goes away, the
|
||||
remaining node takes over and continues as normal.
|
||||
|
||||
|
||||
Cluster nodes automatically determine the authority for configuration
|
||||
objects. This results in activated but paused objects. You can verify
|
||||
that by querying the `paused` attribute for all objects via REST API
|
||||
or debug console.
|
||||
|
||||
Nodes inside a HA zone calculate the object authority independent from each other.
|
||||
|
||||
The number of endpoints in a zone is defined through the configuration. This number
|
||||
is used inside a local modulo calculation to determine whether the node feels
|
||||
responsible for this object or not.
|
||||
|
||||
This object authority is important for selected features explained below.
|
||||
|
||||
Since features are configuration objects too, you must ensure that all nodes
|
||||
inside the HA zone share the same enabled features. If configured otherwise,
|
||||
one might have a checker feature on the left node, nothing on the right node.
|
||||
This leads to late check results because one half is not executed by the right
|
||||
node which holds half of the object authorities.
|
||||
|
||||
### High Availability: Checker <a id="technical-concepts-cluster-ha-checker"></a>
|
||||
|
||||
The `checker` feature only executes checks for `Checkable` objects (Host, Service)
|
||||
where it is authoritative.
|
||||
|
||||
That way each node only executes checks for a segment of the overall configuration objects.
|
||||
|
||||
The cluster message routing ensures that all check results are synchronized
|
||||
to nodes which are not authoritative for this configuration object.
|
||||
|
||||
|
||||
### High Availability: Notifications <a id="technical-concepts-cluster-notifications"></a>
|
||||
|
||||
The `notification` feature only sends notifications for `Notification` objects
|
||||
where it is authoritative.
|
||||
|
||||
That way each node only executes notifications for a segment of all notification objects.
|
||||
|
||||
Notified users and other event details are synchronized throughout the cluster.
|
||||
This is required if for example the DB IDO feature is active on the other node.
|
||||
|
||||
### High Availability: DB IDO <a id="technical-concepts-cluster-ha-ido"></a>
|
||||
|
||||
If you don't have HA enabled for the IDO feature, both nodes will
|
||||
write their status and historical data to their own separate database
|
||||
backends.
|
||||
|
||||
In order to avoid data separation and a split view (each node would require its
|
||||
own Icinga Web 2 installation on top), the high availability option was added
|
||||
to the DB IDO feature. This is enabled by default with the `enable_ha` setting.
|
||||
|
||||
This requires a central database backend. Best practice is to use a MySQL cluster
|
||||
with a virtual IP.
|
||||
|
||||
Both Icinga 2 nodes require the connection and credential details configured in
|
||||
their DB IDO feature.
|
||||
|
||||
During startup Icinga 2 calculates whether the feature configuration object
|
||||
is authoritative on this node or not. The order is an alpha-numeric
|
||||
comparison, e.g. if you have `master1` and `master2`, Icinga 2 will enable
|
||||
the DB IDO feature on `master2` by default.
|
||||
|
||||
If the connection between endpoints drops, the object authority is re-calculated.
|
||||
|
||||
In order to prevent data duplication in a split-brain scenario where both
|
||||
nodes would write into the same database, there is another safety mechanism
|
||||
in place.
|
||||
|
||||
The split-brain decision which node will write to the database is calculated
|
||||
from a quorum inside the `programstatus` table. Each node
|
||||
verifies whether the `endpoint_name` column is not itself on database connect.
|
||||
In addition to that the DB IDO feature compares the `last_update_time` column
|
||||
against the current timestamp plus the configured `failover_timeout` offset.
|
||||
|
||||
That way only one active DB IDO feature writes to the database, even if they
|
||||
are not currently connected in a cluster zone. This prevents data duplication
|
||||
in historical tables.
|
||||
|
||||
<!--
|
||||
## REST API <a id="technical-concepts-rest-api"></a>
|
||||
|
||||
Icinga 2 provides its own HTTP server which shares the port 5665 with
|
||||
the JSON-RPC cluster protocol.
|
||||
-->
|
|
@ -11,7 +11,7 @@ on your migration requirements.
|
|||
For a long-term migration of your configuration you should consider re-creating
|
||||
your configuration based on the proposed Icinga 2 configuration paradigm.
|
||||
|
||||
Please read the [next chapter](22-migrating-from-icinga-1x.md#differences-1x-2) to find out more about the differences
|
||||
Please read the [next chapter](23-migrating-from-icinga-1x.md#differences-1x-2) to find out more about the differences
|
||||
between 1.x and 2.
|
||||
|
||||
### Manual Config Migration Hints <a id="manual-config-migration-hints"></a>
|
||||
|
@ -24,7 +24,7 @@ The examples are taken from Icinga 1.x test and production environments and conv
|
|||
straight into a possible Icinga 2 format. If you found a different strategy, please
|
||||
let us know!
|
||||
|
||||
If you require in-depth explanations, please check the [next chapter](22-migrating-from-icinga-1x.md#differences-1x-2).
|
||||
If you require in-depth explanations, please check the [next chapter](23-migrating-from-icinga-1x.md#differences-1x-2).
|
||||
|
||||
#### Manual Config Migration Hints for Intervals <a id="manual-config-migration-hints-Intervals"></a>
|
||||
|
||||
|
@ -185,7 +185,7 @@ While you could manually migrate this like (please note the new generic command
|
|||
|
||||
#### Manual Config Migration Hints for Runtime Macros <a id="manual-config-migration-hints-runtime-macros"></a>
|
||||
|
||||
Runtime macros have been renamed. A detailed comparison table can be found [here](22-migrating-from-icinga-1x.md#differences-1x-2-runtime-macros).
|
||||
Runtime macros have been renamed. A detailed comparison table can be found [here](23-migrating-from-icinga-1x.md#differences-1x-2-runtime-macros).
|
||||
|
||||
For example, accessing the service check output looks like the following in Icinga 1.x:
|
||||
|
||||
|
@ -257,7 +257,7 @@ while the service check command resolves its value to the service attribute attr
|
|||
#### Manual Config Migration Hints for Contacts (Users) <a id="manual-config-migration-hints-contacts-users"></a>
|
||||
|
||||
Contacts in Icinga 1.x act as users in Icinga 2, but do not have any notification commands specified.
|
||||
This migration part is explained in the [next chapter](22-migrating-from-icinga-1x.md#manual-config-migration-hints-notifications).
|
||||
This migration part is explained in the [next chapter](23-migrating-from-icinga-1x.md#manual-config-migration-hints-notifications).
|
||||
|
||||
define contact{
|
||||
contact_name testconfig-user
|
||||
|
@ -267,7 +267,7 @@ This migration part is explained in the [next chapter](22-migrating-from-icinga-
|
|||
email icinga@localhost
|
||||
}
|
||||
|
||||
The `service_notification_options` can be [mapped](22-migrating-from-icinga-1x.md#manual-config-migration-hints-notification-filters)
|
||||
The `service_notification_options` can be [mapped](23-migrating-from-icinga-1x.md#manual-config-migration-hints-notification-filters)
|
||||
into generic `state` and `type` filters, if additional notification filtering is required. `alias` gets
|
||||
renamed to `display_name`.
|
||||
|
||||
|
@ -319,7 +319,7 @@ Assign it to the host or service and set the newly generated notification comman
|
|||
|
||||
|
||||
Convert the `notification_options` attribute from Icinga 1.x to Icinga 2 `states` and `types`. Details
|
||||
[here](22-migrating-from-icinga-1x.md#manual-config-migration-hints-notification-filters). Add the notification period.
|
||||
[here](23-migrating-from-icinga-1x.md#manual-config-migration-hints-notification-filters). Add the notification period.
|
||||
|
||||
states = [ OK, Warning, Critical ]
|
||||
types = [ Recovery, Problem, Custom ]
|
||||
|
@ -556,7 +556,7 @@ enabled.
|
|||
assign where "hg_svcdep2" in host.groups
|
||||
}
|
||||
|
||||
Host dependencies are explained in the [next chapter](22-migrating-from-icinga-1x.md#manual-config-migration-hints-host-parents).
|
||||
Host dependencies are explained in the [next chapter](23-migrating-from-icinga-1x.md#manual-config-migration-hints-host-parents).
|
||||
|
||||
|
||||
|
||||
|
@ -955,7 +955,7 @@ In Icinga 1.x arguments are specified in the `check_command` attribute and
|
|||
are separated from the command name using an exclamation mark (`!`).
|
||||
|
||||
Please check the migration hints for a detailed
|
||||
[migration example](22-migrating-from-icinga-1x.md#manual-config-migration-hints-check-command-arguments).
|
||||
[migration example](23-migrating-from-icinga-1x.md#manual-config-migration-hints-check-command-arguments).
|
||||
|
||||
> **Note**
|
||||
>
|
|
@ -692,16 +692,16 @@ Not supported: `debug_info`.
|
|||
|
||||
#### Livestatus Hostsbygroup Table Attributes <a id="schema-livestatus-hostsbygroup-table-attributes"></a>
|
||||
|
||||
All [hosts](23-appendix.md#schema-livestatus-hosts-table-attributes) table attributes grouped with
|
||||
the [hostgroups](23-appendix.md#schema-livestatus-hostgroups-table-attributes) table prefixed with `hostgroup_`.
|
||||
All [hosts](24-appendix.md#schema-livestatus-hosts-table-attributes) table attributes grouped with
|
||||
the [hostgroups](24-appendix.md#schema-livestatus-hostgroups-table-attributes) table prefixed with `hostgroup_`.
|
||||
|
||||
#### Livestatus Servicesbygroup Table Attributes <a id="schema-livestatus-servicesbygroup-table-attributes"></a>
|
||||
|
||||
All [services](23-appendix.md#schema-livestatus-services-table-attributes) table attributes grouped with
|
||||
the [servicegroups](23-appendix.md#schema-livestatus-servicegroups-table-attributes) table prefixed with `servicegroup_`.
|
||||
All [services](24-appendix.md#schema-livestatus-services-table-attributes) table attributes grouped with
|
||||
the [servicegroups](24-appendix.md#schema-livestatus-servicegroups-table-attributes) table prefixed with `servicegroup_`.
|
||||
|
||||
#### Livestatus Servicesbyhostgroup Table Attributes <a id="schema-livestatus-servicesbyhostgroup-table-attributes"></a>
|
||||
|
||||
All [services](23-appendix.md#schema-livestatus-services-table-attributes) table attributes grouped with
|
||||
the [hostgroups](23-appendix.md#schema-livestatus-hostgroups-table-attributes) table prefixed with `hostgroup_`.
|
||||
All [services](24-appendix.md#schema-livestatus-services-table-attributes) table attributes grouped with
|
||||
the [hostgroups](24-appendix.md#schema-livestatus-hostgroups-table-attributes) table prefixed with `hostgroup_`.
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
*/
|
||||
|
||||
object ApiListener "api" {
|
||||
cert_path = SysconfDir + "/icinga2/pki/" + NodeName + ".crt"
|
||||
key_path = SysconfDir + "/icinga2/pki/" + NodeName + ".key"
|
||||
ca_path = SysconfDir + "/icinga2/pki/ca.crt"
|
||||
cert_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".crt"
|
||||
key_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".key"
|
||||
ca_path = LocalStateDir + "/lib/icinga2/certs/ca.crt"
|
||||
|
||||
ticket_salt = TicketSalt
|
||||
}
|
||||
|
|
|
@ -575,6 +575,12 @@ boost::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
|
|||
return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, false);
|
||||
}
|
||||
|
||||
boost::shared_ptr<X509> CreateCertIcingaCA(const boost::shared_ptr<X509>& cert)
|
||||
{
|
||||
boost::shared_ptr<EVP_PKEY> pkey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
|
||||
return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get()));
|
||||
}
|
||||
|
||||
String CertificateToString(const boost::shared_ptr<X509>& cert)
|
||||
{
|
||||
BIO *mem = BIO_new(BIO_s_mem());
|
||||
|
@ -590,6 +596,21 @@ String CertificateToString(const boost::shared_ptr<X509>& cert)
|
|||
return result;
|
||||
}
|
||||
|
||||
boost::shared_ptr<X509> StringToCertificate(const String& cert)
|
||||
{
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
BIO_write(bio, (const void *)cert.CStr(), cert.GetLength());
|
||||
|
||||
X509 *rawCert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
if (!rawCert)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("The specified X509 certificate is invalid."));
|
||||
|
||||
return boost::shared_ptr<X509>(rawCert, X509_free);
|
||||
}
|
||||
|
||||
String PBKDF2_SHA1(const String& password, const String& salt, int iterations)
|
||||
{
|
||||
unsigned char digest[SHA_DIGEST_LENGTH];
|
||||
|
@ -707,4 +728,24 @@ String RandomString(int length)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool VerifyCertificate(const boost::shared_ptr<X509>& caCertificate, const boost::shared_ptr<X509>& certificate)
|
||||
{
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
|
||||
if (!store)
|
||||
return false;
|
||||
|
||||
X509_STORE_add_cert(store, caCertificate.get());
|
||||
|
||||
X509_STORE_CTX *csc = X509_STORE_CTX_new();
|
||||
X509_STORE_CTX_init(csc, store, certificate.get(), NULL);
|
||||
|
||||
int rc = X509_verify_cert(csc);
|
||||
|
||||
X509_STORE_CTX_free(csc);
|
||||
X509_STORE_free(store);
|
||||
|
||||
return rc == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,11 +48,14 @@ int I2_BASE_API MakeX509CSR(const String& cn, const String& keyfile, const Strin
|
|||
boost::shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
|
||||
String I2_BASE_API GetIcingaCADir(void);
|
||||
String I2_BASE_API CertificateToString(const boost::shared_ptr<X509>& cert);
|
||||
boost::shared_ptr<X509> I2_BASE_API StringToCertificate(const String& cert);
|
||||
boost::shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
|
||||
boost::shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(const boost::shared_ptr<X509>& cert);
|
||||
String I2_BASE_API PBKDF2_SHA1(const String& password, const String& salt, int iterations);
|
||||
String I2_BASE_API SHA1(const String& s, bool binary = false);
|
||||
String I2_BASE_API SHA256(const String& s);
|
||||
String I2_BASE_API RandomString(int length);
|
||||
bool I2_BASE_API VerifyCertificate(const boost::shared_ptr<X509>& caCertificate, const boost::shared_ptr<X509>& certificate);
|
||||
|
||||
class I2_BASE_API openssl_error : virtual public std::exception, virtual public boost::exception { };
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
set(cli_SOURCES
|
||||
apisetupcommand.cpp apisetuputility.cpp
|
||||
calistcommand.cpp casigncommand.cpp
|
||||
nodeaddcommand.cpp nodeblackandwhitelistcommand.cpp nodelistcommand.cpp noderemovecommand.cpp
|
||||
nodesetcommand.cpp nodesetupcommand.cpp nodeupdateconfigcommand.cpp nodewizardcommand.cpp nodeutility.cpp
|
||||
clicommand.cpp
|
||||
|
@ -25,7 +26,6 @@ set(cli_SOURCES
|
|||
featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp featureutility.cpp
|
||||
objectlistcommand.cpp objectlistutility.cpp
|
||||
pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkisavecertcommand.cpp pkiticketcommand.cpp
|
||||
pkiutility.cpp
|
||||
repositoryclearchangescommand.cpp repositorycommitcommand.cpp repositoryobjectcommand.cpp repositoryutility.cpp
|
||||
variablegetcommand.cpp variablelistcommand.cpp variableutility.cpp
|
||||
troubleshootcommand.cpp
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/apisetuputility.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "cli/nodeutility.hpp"
|
||||
#include "cli/featureutility.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/console.hpp"
|
||||
#include "base/application.hpp"
|
||||
|
@ -68,7 +69,7 @@ bool ApiSetupUtility::SetupMasterCertificates(const String& cn)
|
|||
if (PkiUtility::NewCa() > 0)
|
||||
Log(LogWarning, "cli", "Found CA, skipping and using the existing one.");
|
||||
|
||||
String pki_path = PkiUtility::GetPkiPath();
|
||||
String pki_path = ApiListener::GetCertsDir();
|
||||
Utility::MkDirP(pki_path, 0700);
|
||||
|
||||
String user = ScriptGlobal::Get("RunAsUser");
|
||||
|
@ -116,7 +117,7 @@ bool ApiSetupUtility::SetupMasterCertificates(const String& cn)
|
|||
}
|
||||
|
||||
/* Copy CA certificate to /etc/icinga2/pki */
|
||||
String ca_path = PkiUtility::GetLocalCaPath();
|
||||
String ca_path = ApiListener::GetCaDir();
|
||||
String ca = ca_path + "/ca.crt";
|
||||
String ca_key = ca_path + "/ca.key";
|
||||
String target_ca = pki_path + "/ca.crt";
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the Free Software Foundation *
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#include "cli/calistcommand.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#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);
|
||||
|
||||
String CAListCommand::GetDescription(void) const
|
||||
{
|
||||
return "Lists all certificate signing requests.";
|
||||
}
|
||||
|
||||
String CAListCommand::GetShortDescription(void) const
|
||||
{
|
||||
return "lists all certificate signing requests";
|
||||
}
|
||||
|
||||
void CAListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
|
||||
boost::program_options::options_description& hiddenDesc) const
|
||||
{
|
||||
visibleDesc.add_options()
|
||||
("json", "encode output as JSON")
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry point for the "ca list" CLI command.
|
||||
*
|
||||
* @returns An exit status.
|
||||
*/
|
||||
int CAListCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
|
||||
{
|
||||
Dictionary::Ptr requests = PkiUtility::GetCertificateRequests();
|
||||
|
||||
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->Get("timestamp")
|
||||
<< " | "
|
||||
<< (request->Contains("cert_response") ? "*" : " ") << " "
|
||||
<< " | "
|
||||
<< request->Get("subject")
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the Free Software Foundation *
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CALISTCOMMAND_H
|
||||
#define CALISTCOMMAND_H
|
||||
|
||||
#include "cli/clicommand.hpp"
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* The "ca list" command.
|
||||
*
|
||||
* @ingroup cli
|
||||
*/
|
||||
class CAListCommand : public CLICommand
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(CAListCommand);
|
||||
|
||||
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:
|
||||
static void PrintRequest(const String& requestFile);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CALISTCOMMAND_H */
|
|
@ -0,0 +1,105 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the Free Software Foundation *
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#include "cli/casigncommand.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
REGISTER_CLICOMMAND("ca/sign", CASignCommand);
|
||||
|
||||
String CASignCommand::GetDescription(void) const
|
||||
{
|
||||
return "Signs an outstanding certificate request.";
|
||||
}
|
||||
|
||||
String CASignCommand::GetShortDescription(void) const
|
||||
{
|
||||
return "signs an outstanding certificate request";
|
||||
}
|
||||
|
||||
int CASignCommand::GetMinArguments(void) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
ImpersonationLevel CASignCommand::GetImpersonationLevel(void) const
|
||||
{
|
||||
return ImpersonateIcinga;
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry point for the "ca sign" CLI command.
|
||||
*
|
||||
* @returns An exit status.
|
||||
*/
|
||||
int CASignCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
|
||||
{
|
||||
String requestFile = ApiListener::GetCertificateRequestsDir() + "/" + 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)
|
||||
return 1;
|
||||
|
||||
String certRequestText = request->Get("cert_request");
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the Free Software Foundation *
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CASIGNCOMMAND_H
|
||||
#define CASIGNCOMMAND_H
|
||||
|
||||
#include "cli/clicommand.hpp"
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* The "ca sign" command.
|
||||
*
|
||||
* @ingroup cli
|
||||
*/
|
||||
class CASignCommand : public CLICommand
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(CASignCommand);
|
||||
|
||||
virtual String GetDescription(void) const override;
|
||||
virtual String GetShortDescription(void) const override;
|
||||
virtual int GetMinArguments(void) const override;
|
||||
virtual ImpersonationLevel GetImpersonationLevel(void) const override;
|
||||
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CASIGNCOMMAND_H */
|
|
@ -20,8 +20,9 @@
|
|||
#include "cli/nodesetupcommand.hpp"
|
||||
#include "cli/nodeutility.hpp"
|
||||
#include "cli/featureutility.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "cli/apisetuputility.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/console.hpp"
|
||||
#include "base/application.hpp"
|
||||
|
@ -130,14 +131,14 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
|
|||
cn = vm["cn"].as<std::string>();
|
||||
|
||||
/* check whether the user wants to generate a new certificate or not */
|
||||
String existing_path = PkiUtility::GetPkiPath() + "/" + cn + ".crt";
|
||||
String existingPath = ApiListener::GetCertsDir() + "/" + cn + ".crt";
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Checking for existing certificates for common name '" << cn << "'...";
|
||||
<< "Checking in existing certificates for common name '" << cn << "'...";
|
||||
|
||||
if (Utility::PathExists(existing_path)) {
|
||||
if (Utility::PathExists(existingPath)) {
|
||||
Log(LogWarning, "cli")
|
||||
<< "Certificate '" << existing_path << "' for CN '" << cn << "' already exists. Not generating new certificate.";
|
||||
<< "Certificate '" << existingPath << "' for CN '" << cn << "' already exists. Not generating new certificate.";
|
||||
} else {
|
||||
Log(LogInformation, "cli")
|
||||
<< "Certificates not yet generated. Running 'api setup' now.";
|
||||
|
@ -156,13 +157,11 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
|
|||
}
|
||||
|
||||
/* write zones.conf and update with zone + endpoint information */
|
||||
|
||||
Log(LogInformation, "cli", "Generating zone and object configuration.");
|
||||
|
||||
NodeUtility::GenerateNodeMasterIcingaConfig();
|
||||
|
||||
/* update the ApiListener config - SetupMaster() will always enable it */
|
||||
|
||||
Log(LogInformation, "cli", "Updating the APIListener feature.");
|
||||
|
||||
String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
|
||||
|
@ -175,9 +174,9 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
|
|||
<< " * The API listener is used for distributed monitoring setups.\n"
|
||||
<< " */\n"
|
||||
<< "object ApiListener \"api\" {\n"
|
||||
<< " cert_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".crt\"\n"
|
||||
<< " key_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".key\"\n"
|
||||
<< " ca_path = SysconfDir + \"/icinga2/pki/ca.crt\"\n";
|
||||
<< " cert_path = LocalStateDir + \"/lib/icinga2/certs/\" + NodeName + \".crt\"\n"
|
||||
<< " key_path = LocalStateDir + \"/lib/icinga2/certs/\" + NodeName + \".key\"\n"
|
||||
<< " ca_path = LocalStateDir + \"/lib/icinga2/certs/ca.crt\"\n";
|
||||
|
||||
if (vm.count("listen")) {
|
||||
std::vector<String> tokens;
|
||||
|
@ -262,7 +261,8 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
/* require master host information for auto-signing requests */
|
||||
|
||||
if (!vm.count("master_host")) {
|
||||
Log(LogCritical, "cli", "Please pass the master host connection information for auto-signing using '--master_host <host>'");
|
||||
Log(LogCritical, "cli", "Please pass the master host connection information for auto-signing using '--master_host <host>'. This can also be a direct parent satellite since 2.8.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -278,13 +278,13 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
master_port = tokens[1];
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Verifying master host connection information: host '" << master_host << "', port '" << master_port << "'.";
|
||||
<< "Verifying parent host connection information: host '" << master_host << "', port '" << master_port << "'.";
|
||||
|
||||
/* trusted cert must be passed (retrieved by the user with 'pki save-cert' before) */
|
||||
|
||||
if (!vm.count("trustedcert")) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Please pass the trusted cert retrieved from the master\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 master.crt').";
|
||||
return 1;
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
|
||||
/* pki request a signed certificate from the master */
|
||||
|
||||
String pki_path = PkiUtility::GetPkiPath();
|
||||
String pki_path = ApiListener::GetCertsDir();
|
||||
Utility::MkDirP(pki_path, 0700);
|
||||
|
||||
String user = ScriptGlobal::Get("RunAsUser");
|
||||
|
@ -336,10 +336,10 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
<< "Cannot set ownership for user '" << user << "' group '" << group << "' on file '" << key << "'. Verify it yourself!";
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli", "Requesting a signed certificate from the master.");
|
||||
Log(LogInformation, "cli", "Requesting a signed certificate from the parent Icinga node.");
|
||||
|
||||
if (PkiUtility::RequestCertificate(master_host, master_port, key, cert, ca, trustedcert, ticket) != 0) {
|
||||
Log(LogCritical, "cli", "Failed to request certificate from Icinga 2 master.");
|
||||
Log(LogCritical, "cli", "Failed to request certificate from parent Icinga node.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -379,9 +379,9 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
<< " * The API listener is used for distributed monitoring setups.\n"
|
||||
<< " */\n"
|
||||
<< "object ApiListener \"api\" {\n"
|
||||
<< " cert_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".crt\"\n"
|
||||
<< " key_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".key\"\n"
|
||||
<< " ca_path = SysconfDir + \"/icinga2/pki/ca.crt\"\n";
|
||||
<< " cert_path = LocalStateDir + \"/lib/icinga2/certs/\" + NodeName + \".crt\"\n"
|
||||
<< " key_path = LocalStateDir + \"/lib/icinga2/certs/\" + NodeName + \".key\"\n"
|
||||
<< " ca_path = LocalStateDir + \"/lib/icinga2/certs/ca.crt\"\n";
|
||||
|
||||
if (vm.count("listen")) {
|
||||
std::vector<String> tokens;
|
||||
|
@ -406,7 +406,6 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
fp << " accept_commands = false\n";
|
||||
|
||||
fp << "\n"
|
||||
<< " ticket_salt = TicketSalt\n"
|
||||
<< "}\n";
|
||||
|
||||
fp.close();
|
||||
|
@ -431,7 +430,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
/* update constants.conf with NodeName = CN */
|
||||
if (cn != Utility::GetFQDN()) {
|
||||
Log(LogWarning, "cli")
|
||||
<< "CN '" << cn << "' does not match the default FQDN '" << Utility::GetFQDN() << "'. Requires update for NodeName constant in constants.conf!";
|
||||
<< "CN '" << cn << "' does not match the default FQDN '" << Utility::GetFQDN() << "'. Requires an update for the NodeName constant in constants.conf!";
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli", "Updating constants.conf.");
|
||||
|
@ -441,8 +440,33 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
|
|||
NodeUtility::UpdateConstant("NodeName", cn);
|
||||
NodeUtility::UpdateConstant("ZoneName", vm["zone"].as<std::string>());
|
||||
|
||||
/* tell the user to reload icinga2 */
|
||||
String ticketPath = ApiListener::GetCertsDir() + "/ticket";
|
||||
|
||||
String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp);
|
||||
|
||||
if (!Utility::SetFileOwnership(tempTicketPath, user, group)) {
|
||||
Log(LogWarning, "cli")
|
||||
<< "Cannot set ownership for user '" << user
|
||||
<< "' group '" << group
|
||||
<< "' on file '" << tempTicketPath << "'. Verify it yourself!";
|
||||
}
|
||||
|
||||
fp << ticket;
|
||||
|
||||
fp.close();
|
||||
|
||||
#ifdef _WIN32
|
||||
_unlink(ticketPath.CStr());
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if (rename(tempTicketPath.CStr(), ticketPath.CStr()) < 0) {
|
||||
BOOST_THROW_EXCEPTION(posix_error()
|
||||
<< boost::errinfo_api_function("rename")
|
||||
<< boost::errinfo_errno(errno)
|
||||
<< boost::errinfo_file_name(tempTicketPath));
|
||||
}
|
||||
|
||||
/* tell the user to reload icinga2 */
|
||||
Log(LogInformation, "cli", "Make sure to restart Icinga 2.");
|
||||
|
||||
return 0;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -40,6 +40,12 @@ public:
|
|||
virtual int GetMaxArguments(void) const override;
|
||||
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
|
||||
virtual ImpersonationLevel GetImpersonationLevel(void) const override;
|
||||
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
|
||||
boost::program_options::options_description& hiddenDesc) const override;
|
||||
|
||||
private:
|
||||
int ClientSetup(void) const;
|
||||
int MasterSetup(void) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkinewcacommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkinewcertcommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkirequestcommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
#include <iostream>
|
||||
|
@ -95,17 +95,16 @@ int PKIRequestCommand::Run(const boost::program_options::variables_map& vm, cons
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (!vm.count("ticket")) {
|
||||
Log(LogCritical, "cli", "Ticket (--ticket) must be specified.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
String port = "5665";
|
||||
String ticket;
|
||||
|
||||
if (vm.count("port"))
|
||||
port = vm["port"].as<std::string>();
|
||||
|
||||
if (vm.count("ticket"))
|
||||
ticket = vm["ticket"].as<std::string>();
|
||||
|
||||
return PkiUtility::RequestCertificate(vm["host"].as<std::string>(), port, vm["key"].as<std::string>(),
|
||||
vm["cert"].as<std::string>(), vm["ca"].as<std::string>(), GetX509Certificate(vm["trustedcert"].as<std::string>()),
|
||||
vm["ticket"].as<std::string>());
|
||||
ticket);
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkisavecertcommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
#include "base/console.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
namespace po = boost::program_options;
|
||||
|
@ -77,13 +78,26 @@ int PKISaveCertCommand::Run(const boost::program_options::variables_map& vm, con
|
|||
return 1;
|
||||
}
|
||||
|
||||
boost::shared_ptr<X509> cert =
|
||||
PkiUtility::FetchCert(vm["host"].as<std::string>(), vm["port"].as<std::string>());
|
||||
String host = vm["host"].as<std::string>();
|
||||
String port = vm["port"].as<std::string>();
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Retrieving X.509 certificate for '" << host << ":" << port << "'.";
|
||||
|
||||
boost::shared_ptr<X509> cert = PkiUtility::FetchCert(host, port);
|
||||
|
||||
if (!cert) {
|
||||
Log(LogCritical, "cli", "Failed to fetch certificate from host");
|
||||
Log(LogCritical, "cli", "Failed to fetch certificate from host.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
|
||||
std::cout << ConsoleColorTag(Console_ForegroundRed)
|
||||
<< "***\n"
|
||||
<< "*** You have to ensure that this certificate actually matches the parent\n"
|
||||
<< "*** instance's certificate in order to avoid man-in-the-middle attacks.\n"
|
||||
<< "***\n\n"
|
||||
<< ConsoleColorTag(Console_Normal);
|
||||
|
||||
return PkiUtility::WriteCert(cert, vm["trustedcert"].as<std::string>());
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkisigncsrcommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "cli/pkiticketcommand.hpp"
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "cli/variableutility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include <iostream>
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "icinga/notificationcommand.hpp"
|
||||
#include "remote/apiaction.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "remote/httputility.hpp"
|
||||
#include "base/utility.hpp"
|
||||
#include "base/convert.hpp"
|
||||
|
|
|
@ -28,8 +28,9 @@ set(remote_SOURCES
|
|||
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
|
||||
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
|
||||
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
|
||||
httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
|
||||
httputility.cpp infohandler.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp
|
||||
messageorigin.cpp modifyobjecthandler.cpp statushandler.cpp objectqueryhandler.cpp templatequeryhandler.cpp
|
||||
pkiutility.cpp
|
||||
typequeryhandler.cpp url.cpp variablequeryhandler.cpp zone.cpp zone.thpp
|
||||
)
|
||||
|
||||
|
|
|
@ -55,6 +55,26 @@ ApiListener::ApiListener(void)
|
|||
m_SyncQueue.SetName("ApiListener, SyncQueue");
|
||||
}
|
||||
|
||||
String ApiListener::GetApiDir(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/api/";
|
||||
}
|
||||
|
||||
String ApiListener::GetCertsDir(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/certs/";
|
||||
}
|
||||
|
||||
String ApiListener::GetCaDir(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/ca/";
|
||||
}
|
||||
|
||||
String ApiListener::GetCertificateRequestsDir(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/certificate-requests/";
|
||||
}
|
||||
|
||||
void ApiListener::OnConfigLoaded(void)
|
||||
{
|
||||
if (m_Instance)
|
||||
|
@ -81,8 +101,15 @@ void ApiListener::OnConfigLoaded(void)
|
|||
Log(LogInformation, "ApiListener")
|
||||
<< "My API identity: " << GetIdentity();
|
||||
|
||||
UpdateSSLContext();
|
||||
}
|
||||
|
||||
void ApiListener::UpdateSSLContext(void)
|
||||
{
|
||||
boost::shared_ptr<SSL_CTX> context;
|
||||
|
||||
try {
|
||||
m_SSLContext = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath());
|
||||
context = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath());
|
||||
} catch (const std::exception&) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '"
|
||||
+ GetCertPath() + "' key path: '" + GetKeyPath() + "' ca path: '" + GetCaPath() + "'.", GetDebugInfo()));
|
||||
|
@ -90,7 +117,7 @@ void ApiListener::OnConfigLoaded(void)
|
|||
|
||||
if (!GetCrlPath().IsEmpty()) {
|
||||
try {
|
||||
AddCRLToSSLContext(m_SSLContext, GetCrlPath());
|
||||
AddCRLToSSLContext(context, GetCrlPath());
|
||||
} catch (const std::exception&) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Cannot add certificate revocation list to SSL context for crl path: '"
|
||||
+ GetCrlPath() + "'.", GetDebugInfo()));
|
||||
|
@ -99,7 +126,7 @@ void ApiListener::OnConfigLoaded(void)
|
|||
|
||||
if (!GetCipherList().IsEmpty()) {
|
||||
try {
|
||||
SetCipherListToSSLContext(m_SSLContext, GetCipherList());
|
||||
SetCipherListToSSLContext(context, GetCipherList());
|
||||
} catch (const std::exception&) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Cannot set cipher list to SSL context for cipher list: '"
|
||||
+ GetCipherList() + "'.", GetDebugInfo()));
|
||||
|
@ -108,11 +135,23 @@ void ApiListener::OnConfigLoaded(void)
|
|||
|
||||
if (!GetTlsProtocolmin().IsEmpty()){
|
||||
try {
|
||||
SetTlsProtocolminToSSLContext(m_SSLContext, GetTlsProtocolmin());
|
||||
SetTlsProtocolminToSSLContext(context, GetTlsProtocolmin());
|
||||
} catch (const std::exception&) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + GetTlsProtocolmin() + "'.", GetDebugInfo()));
|
||||
}
|
||||
}
|
||||
|
||||
m_SSLContext = context;
|
||||
|
||||
for (const Endpoint::Ptr& endpoint : ConfigType::GetObjectsByType<Endpoint>()) {
|
||||
for (const JsonRpcConnection::Ptr& client : endpoint->GetClients()) {
|
||||
client->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
for (const JsonRpcConnection::Ptr& client : m_AnonymousClients) {
|
||||
client->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiListener::OnAllConfigLoaded(void)
|
||||
|
@ -165,6 +204,12 @@ void ApiListener::Start(bool runtimeCreated)
|
|||
m_AuthorityTimer->SetInterval(30);
|
||||
m_AuthorityTimer->Start();
|
||||
|
||||
m_CleanupCertificateRequestsTimer = new Timer();
|
||||
m_CleanupCertificateRequestsTimer->OnTimerExpired.connect(boost::bind(&ApiListener::CleanupCertificateRequestsTimerHandler, this));
|
||||
m_CleanupCertificateRequestsTimer->SetInterval(3600);
|
||||
m_CleanupCertificateRequestsTimer->Start();
|
||||
m_CleanupCertificateRequestsTimer->Reschedule(0);
|
||||
|
||||
OnMasterChanged(true);
|
||||
}
|
||||
|
||||
|
@ -184,11 +229,6 @@ ApiListener::Ptr ApiListener::GetInstance(void)
|
|||
return m_Instance;
|
||||
}
|
||||
|
||||
boost::shared_ptr<SSL_CTX> ApiListener::GetSSLContext(void) const
|
||||
{
|
||||
return m_SSLContext;
|
||||
}
|
||||
|
||||
Endpoint::Ptr ApiListener::GetMaster(void) const
|
||||
{
|
||||
Zone::Ptr zone = Zone::GetLocalZone();
|
||||
|
@ -395,7 +435,6 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
|
|||
Log(LogWarning, "ApiListener")
|
||||
<< "Certificate validation failed for endpoint '" << hostname
|
||||
<< "': " << tlsStream->GetVerifyError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,6 +517,18 @@ void ApiListener::SyncClient(const JsonRpcConnection::Ptr& aclient, const Endpoi
|
|||
endpoint->SetSyncing(true);
|
||||
}
|
||||
|
||||
Zone::Ptr myZone = Zone::GetLocalZone();
|
||||
|
||||
if (myZone->GetParent() == eZone) {
|
||||
Log(LogInformation, "ApiListener")
|
||||
<< "Requesting new certificate for this Icinga instance from endpoint '" << endpoint->GetName() << "'.";
|
||||
|
||||
JsonRpcConnection::SendCertificateRequest(aclient, MessageOrigin::Ptr(), String());
|
||||
|
||||
if (Utility::PathExists(ApiListener::GetCertificateRequestsDir()))
|
||||
Utility::Glob(ApiListener::GetCertificateRequestsDir() + "/*.json", boost::bind(&JsonRpcConnection::SendCertificateRequest, aclient, MessageOrigin::Ptr(), _1), GlobFile);
|
||||
}
|
||||
|
||||
/* Make sure that the config updates are synced
|
||||
* before the logs are replayed.
|
||||
*/
|
||||
|
@ -597,7 +648,6 @@ void ApiListener::ApiTimerHandler(void)
|
|||
<< "Setting log position for identity '" << endpoint->GetName() << "': "
|
||||
<< Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", ts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ApiListener::ApiReconnectTimerHandler(void)
|
||||
|
@ -669,6 +719,33 @@ void ApiListener::ApiReconnectTimerHandler(void)
|
|||
<< "Connected endpoints: " << Utility::NaturalJoin(names);
|
||||
}
|
||||
|
||||
static void CleanupCertificateRequest(const String& path, double expiryTime)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
struct stat statbuf;
|
||||
if (lstat(path.CStr(), &statbuf) < 0)
|
||||
return;
|
||||
#else /* _WIN32 */
|
||||
struct _stat statbuf;
|
||||
if (_stat(path.CStr(), &statbuf) < 0)
|
||||
return;
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if (statbuf.st_mtime < expiryTime)
|
||||
(void) unlink(path.CStr());
|
||||
}
|
||||
|
||||
void ApiListener::CleanupCertificateRequestsTimerHandler(void)
|
||||
{
|
||||
String requestsDir = GetCertificateRequestsDir();
|
||||
|
||||
if (Utility::PathExists(requestsDir)) {
|
||||
/* remove certificate requests that are older than a week */
|
||||
double expiryTime = Utility::GetTime() - 7 * 24 * 60 * 60;
|
||||
Utility::Glob(requestsDir + "/*.json", boost::bind(&CleanupCertificateRequest, _1, expiryTime), GlobFile);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiListener::RelayMessage(const MessageOrigin::Ptr& origin,
|
||||
const ConfigObject::Ptr& secobj, const Dictionary::Ptr& message, bool log)
|
||||
{
|
||||
|
@ -863,11 +940,6 @@ void ApiListener::SyncRelayMessage(const MessageOrigin::Ptr& origin,
|
|||
PersistMessage(message, secobj);
|
||||
}
|
||||
|
||||
String ApiListener::GetApiDir(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/api/";
|
||||
}
|
||||
|
||||
/* must hold m_LogLock */
|
||||
void ApiListener::OpenLogFile(void)
|
||||
{
|
||||
|
|
|
@ -59,17 +59,20 @@ public:
|
|||
|
||||
ApiListener(void);
|
||||
|
||||
static ApiListener::Ptr GetInstance(void);
|
||||
static String GetApiDir(void);
|
||||
static String GetCertsDir(void);
|
||||
static String GetCaDir(void);
|
||||
static String GetCertificateRequestsDir(void);
|
||||
|
||||
boost::shared_ptr<SSL_CTX> GetSSLContext(void) const;
|
||||
void UpdateSSLContext(void);
|
||||
|
||||
static ApiListener::Ptr GetInstance(void);
|
||||
|
||||
Endpoint::Ptr GetMaster(void) const;
|
||||
bool IsMaster(void) const;
|
||||
|
||||
Endpoint::Ptr GetLocalEndpoint(void) const;
|
||||
|
||||
static String GetApiDir(void);
|
||||
|
||||
void SyncSendMessage(const Endpoint::Ptr& endpoint, const Dictionary::Ptr& message);
|
||||
void RelayMessage(const MessageOrigin::Ptr& origin, const ConfigObject::Ptr& secobj, const Dictionary::Ptr& message, bool log);
|
||||
|
||||
|
@ -117,12 +120,14 @@ private:
|
|||
Timer::Ptr m_Timer;
|
||||
Timer::Ptr m_ReconnectTimer;
|
||||
Timer::Ptr m_AuthorityTimer;
|
||||
Timer::Ptr m_CleanupCertificateRequestsTimer;
|
||||
Endpoint::Ptr m_LocalEndpoint;
|
||||
|
||||
static ApiListener::Ptr m_Instance;
|
||||
|
||||
void ApiTimerHandler(void);
|
||||
void ApiReconnectTimerHandler(void);
|
||||
void CleanupCertificateRequestsTimerHandler(void);
|
||||
|
||||
bool AddListener(const String& node, const String& service);
|
||||
void AddConnection(const Endpoint::Ptr& endpoint);
|
||||
|
|
|
@ -0,0 +1,391 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the Free Software Foundation *
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#include "remote/jsonrpcconnection.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/apifunction.hpp"
|
||||
#include "remote/jsonrpc.hpp"
|
||||
#include "base/configtype.hpp"
|
||||
#include "base/objectlock.hpp"
|
||||
#include "base/utility.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include "base/convert.hpp"
|
||||
#include <boost/thread/once.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <fstream>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||
REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
|
||||
static Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||
REGISTER_APIFUNCTION(UpdateCertificate, pki, &UpdateCertificateHandler);
|
||||
|
||||
Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
||||
{
|
||||
if (!params)
|
||||
return Empty;
|
||||
|
||||
String certText = params->Get("cert_request");
|
||||
|
||||
boost::shared_ptr<X509> cert;
|
||||
|
||||
Dictionary::Ptr result = new Dictionary();
|
||||
|
||||
/* Use the presented client certificate if not provided. */
|
||||
if (certText.IsEmpty())
|
||||
cert = origin->FromClient->GetStream()->GetPeerCertificate();
|
||||
else
|
||||
cert = StringToCertificate(certText);
|
||||
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetCaPath());
|
||||
|
||||
String cn = GetCertificateCN(cert);
|
||||
|
||||
bool signedByCA = VerifyCertificate(cacert, cert);
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Received certificate request for CN '" << cn << "'"
|
||||
<< (signedByCA ? "" : " not") << " signed by our CA.";
|
||||
|
||||
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) {
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "The certificate for CN '" << cn << "' cannot be renewed yet.";
|
||||
result->Set("status_code", 1);
|
||||
result->Set("error", "The certificate for CN '" + cn + "' cannot be renewed yet.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int n;
|
||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
|
||||
if (!X509_digest(cert.get(), EVP_sha256(), digest, &n)) {
|
||||
result->Set("status_code", 1);
|
||||
result->Set("error", "Could not calculate fingerprint for the X509 certificate for CN '" + cn + "'.");
|
||||
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "Could not calculate fingerprint for the X509 certificate requested for CN '"
|
||||
<< cn << "'.";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char certFingerprint[EVP_MAX_MD_SIZE*2+1];
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
sprintf(certFingerprint + 2 * i, "%02x", digest[i]);
|
||||
|
||||
result->Set("fingerprint_request", certFingerprint);
|
||||
|
||||
String requestDir = ApiListener::GetCertificateRequestsDir();
|
||||
String requestPath = requestDir + "/" + certFingerprint + ".json";
|
||||
|
||||
result->Set("ca", CertificateToString(cacert));
|
||||
|
||||
JsonRpcConnection::Ptr client = origin->FromClient;
|
||||
|
||||
/* If we already have a signed certificate request, send it to the client. */
|
||||
if (Utility::PathExists(requestPath)) {
|
||||
Dictionary::Ptr request = Utility::LoadJsonFile(requestPath);
|
||||
|
||||
String certResponse = request->Get("cert_response");
|
||||
|
||||
if (!certResponse.IsEmpty()) {
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Sending certificate response for CN '" << cn
|
||||
<< "' to endpoint '" << client->GetIdentity() << "'.";
|
||||
|
||||
result->Set("cert", certResponse);
|
||||
result->Set("status_code", 0);
|
||||
|
||||
Dictionary::Ptr message = new Dictionary();
|
||||
message->Set("jsonrpc", "2.0");
|
||||
message->Set("method", "pki::UpdateCertificate");
|
||||
message->Set("params", result);
|
||||
JsonRpc::SendMessage(client->GetStream(), message);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<X509> newcert;
|
||||
boost::shared_ptr<EVP_PKEY> pubkey;
|
||||
X509_NAME *subject;
|
||||
Dictionary::Ptr message;
|
||||
String ticket;
|
||||
|
||||
/* Check whether we are a signing instance or we
|
||||
* must delay the signing request.
|
||||
*/
|
||||
if (!Utility::PathExists(GetIcingaCADir() + "/ca.key"))
|
||||
goto delayed_request;
|
||||
|
||||
if (!signedByCA) {
|
||||
String salt = listener->GetTicketSalt();
|
||||
|
||||
ticket = params->Get("ticket");
|
||||
|
||||
/* Auto-signing is disabled by either a) no TicketSalt
|
||||
* or b) the client did not include a ticket in its request.
|
||||
*/
|
||||
if (salt.IsEmpty() || ticket.IsEmpty())
|
||||
goto delayed_request;
|
||||
|
||||
String realTicket = PBKDF2_SHA1(cn, salt, 50000);
|
||||
|
||||
if (ticket != realTicket) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "Ticket for CN '" << cn << "' is invalid.";
|
||||
|
||||
result->Set("status_code", 1);
|
||||
result->Set("error", "Invalid ticket for CN '" + cn + "'.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
pubkey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free);
|
||||
subject = X509_get_subject_name(cert.get());
|
||||
|
||||
newcert = CreateCertIcingaCA(pubkey.get(), subject);
|
||||
|
||||
/* verify that the new cert matches the CA we're using for the ApiListener;
|
||||
* 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)) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "The CA in '" << listener->GetCaPath() << "' does not match the CA which Icinga uses "
|
||||
<< "for its own cluster connections. This is most likely a configuration problem.";
|
||||
goto delayed_request;
|
||||
}
|
||||
|
||||
/* Send the signed certificate update. */
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Sending certificate response for CN '" << cn << "' to endpoint '"
|
||||
<< client->GetIdentity() << "'" << (!ticket.IsEmpty() ? " (auto-signing ticket)" : "" ) << ".";
|
||||
|
||||
result->Set("cert", CertificateToString(newcert));
|
||||
|
||||
result->Set("status_code", 0);
|
||||
|
||||
message = new Dictionary();
|
||||
message->Set("jsonrpc", "2.0");
|
||||
message->Set("method", "pki::UpdateCertificate");
|
||||
message->Set("params", result);
|
||||
JsonRpc::SendMessage(client->GetStream(), message);
|
||||
|
||||
return result;
|
||||
|
||||
delayed_request:
|
||||
/* Send a delayed certificate signing request. */
|
||||
Utility::MkDirP(requestDir, 0700);
|
||||
|
||||
Dictionary::Ptr request = new Dictionary();
|
||||
request->Set("cert_request", CertificateToString(cert));
|
||||
request->Set("ticket", params->Get("ticket"));
|
||||
|
||||
Utility::SaveJsonFile(requestPath, 0600, request);
|
||||
|
||||
JsonRpcConnection::SendCertificateRequest(JsonRpcConnection::Ptr(), origin, requestPath);
|
||||
|
||||
result->Set("status_code", 2);
|
||||
result->Set("error", "Certificate request for CN '" + cn + "' is pending. Waiting for approval from the parent Icinga instance.");
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Certificate request for CN '" << cn << "' is pending. Waiting for approval.";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void JsonRpcConnection::SendCertificateRequest(const JsonRpcConnection::Ptr& aclient, const MessageOrigin::Ptr& origin, const String& path)
|
||||
{
|
||||
Dictionary::Ptr message = new Dictionary();
|
||||
message->Set("jsonrpc", "2.0");
|
||||
message->Set("method", "pki::RequestCertificate");
|
||||
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
|
||||
if (!listener)
|
||||
return;
|
||||
|
||||
Dictionary::Ptr params = new Dictionary();
|
||||
message->Set("params", params);
|
||||
|
||||
/* Path is empty if this is our own request. */
|
||||
if (path.IsEmpty()) {
|
||||
String ticketPath = ApiListener::GetCertsDir() + "/ticket";
|
||||
|
||||
std::ifstream fp(ticketPath.CStr());
|
||||
String ticket((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
|
||||
fp.close();
|
||||
|
||||
params->Set("ticket", ticket);
|
||||
} else {
|
||||
Dictionary::Ptr request = Utility::LoadJsonFile(path);
|
||||
|
||||
if (request->Contains("cert_response"))
|
||||
return;
|
||||
|
||||
params->Set("cert_request", request->Get("cert_request"));
|
||||
params->Set("ticket", request->Get("ticket"));
|
||||
}
|
||||
|
||||
/* Send the request to a) the connected client
|
||||
* or b) the local zone and all parents.
|
||||
*/
|
||||
if (aclient)
|
||||
JsonRpc::SendMessage(aclient->GetStream(), message);
|
||||
else
|
||||
listener->RelayMessage(origin, Zone::GetLocalZone(), message, false);
|
||||
}
|
||||
|
||||
Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
||||
{
|
||||
if (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone)) {
|
||||
Log(LogWarning, "ClusterEvents")
|
||||
<< "Discarding 'update certificate' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
String ca = params->Get("ca");
|
||||
String cert = params->Get("cert");
|
||||
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
|
||||
if (!listener)
|
||||
return Empty;
|
||||
|
||||
boost::shared_ptr<X509> oldCert = GetX509Certificate(listener->GetCertPath());
|
||||
boost::shared_ptr<X509> newCert = StringToCertificate(cert);
|
||||
|
||||
String cn = GetCertificateCN(newCert);
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Received certificate update message for CN '" << cn << "'";
|
||||
|
||||
/* Check if this is a certificate update for a subordinate instance. */
|
||||
boost::shared_ptr<EVP_PKEY> oldKey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(oldCert.get()), EVP_PKEY_free);
|
||||
boost::shared_ptr<EVP_PKEY> newKey = boost::shared_ptr<EVP_PKEY>(X509_get_pubkey(newCert.get()), EVP_PKEY_free);
|
||||
|
||||
if (X509_NAME_cmp(X509_get_subject_name(oldCert.get()), X509_get_subject_name(newCert.get())) != 0 ||
|
||||
EVP_PKEY_cmp(oldKey.get(), newKey.get()) != 1) {
|
||||
String certFingerprint = params->Get("fingerprint_request");
|
||||
|
||||
/* Validate the fingerprint format. */
|
||||
boost::regex expr("^[0-9a-f]+$");
|
||||
|
||||
if (!boost::regex_match(certFingerprint.GetData(), expr)) {
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< "Endpoint '" << origin->FromClient->GetIdentity() << "' sent an invalid certificate fingerprint: '"
|
||||
<< certFingerprint << "' for CN '" << cn << "'.";
|
||||
return Empty;
|
||||
}
|
||||
|
||||
String requestDir = ApiListener::GetCertificateRequestsDir();
|
||||
String requestPath = requestDir + "/" + certFingerprint + ".json";
|
||||
|
||||
/* Save the received signed certificate request to disk. */
|
||||
if (Utility::PathExists(requestPath)) {
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Saved certificate update for CN '" << cn << "'";
|
||||
|
||||
Dictionary::Ptr request = Utility::LoadJsonFile(requestPath);
|
||||
request->Set("cert_response", cert);
|
||||
Utility::SaveJsonFile(requestPath, 0644, request);
|
||||
}
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
/* Update CA certificate. */
|
||||
String caPath = listener->GetCaPath();
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Updating CA certificate in '" << caPath << "'.";
|
||||
|
||||
std::fstream cafp;
|
||||
String tempCaPath = Utility::CreateTempFile(caPath + ".XXXXXX", 0644, cafp);
|
||||
cafp << ca;
|
||||
cafp.close();
|
||||
|
||||
#ifdef _WIN32
|
||||
_unlink(caPath.CStr());
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if (rename(tempCaPath.CStr(), caPath.CStr()) < 0) {
|
||||
BOOST_THROW_EXCEPTION(posix_error()
|
||||
<< boost::errinfo_api_function("rename")
|
||||
<< boost::errinfo_errno(errno)
|
||||
<< boost::errinfo_file_name(tempCaPath));
|
||||
}
|
||||
|
||||
/* Update signed certificate. */
|
||||
String certPath = listener->GetCertPath();
|
||||
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Updating client certificate for CN '" << cn << "' in '" << certPath << "'.";
|
||||
|
||||
std::fstream certfp;
|
||||
String tempCertPath = Utility::CreateTempFile(certPath + ".XXXXXX", 0644, certfp);
|
||||
certfp << cert;
|
||||
certfp.close();
|
||||
|
||||
#ifdef _WIN32
|
||||
_unlink(certPath.CStr());
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if (rename(tempCertPath.CStr(), certPath.CStr()) < 0) {
|
||||
BOOST_THROW_EXCEPTION(posix_error()
|
||||
<< boost::errinfo_api_function("rename")
|
||||
<< boost::errinfo_errno(errno)
|
||||
<< boost::errinfo_file_name(tempCertPath));
|
||||
}
|
||||
|
||||
/* Remove ticket for successful signing request. */
|
||||
String ticketPath = ApiListener::GetCertsDir() + "/ticket";
|
||||
|
||||
if (unlink(ticketPath.CStr()) < 0 && errno != ENOENT) {
|
||||
BOOST_THROW_EXCEPTION(posix_error()
|
||||
<< boost::errinfo_api_function("unlink")
|
||||
<< boost::errinfo_errno(errno)
|
||||
<< boost::errinfo_file_name(ticketPath));
|
||||
}
|
||||
|
||||
/* Update the certificates at runtime and reconnect all endpoints. */
|
||||
Log(LogInformation, "JsonRpcConnection")
|
||||
<< "Updating the client certificate for CN '" << cn << "' at runtime and reconnecting the endpoints.";
|
||||
|
||||
listener->UpdateSSLContext();
|
||||
|
||||
return Empty;
|
||||
}
|
|
@ -33,8 +33,6 @@ using namespace icinga;
|
|||
|
||||
static Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||
REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
|
||||
static Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
|
||||
REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
|
||||
|
||||
static boost::once_flag l_JsonRpcConnectionOnceFlag = BOOST_ONCE_INIT;
|
||||
static Timer::Ptr l_JsonRpcConnectionTimeoutTimer;
|
||||
|
@ -186,7 +184,21 @@ void JsonRpcConnection::MessageHandler(const String& jsonString)
|
|||
origin->FromZone = Zone::GetByName(message->Get("originZone"));
|
||||
}
|
||||
|
||||
String method = message->Get("method");
|
||||
Value vmethod;
|
||||
|
||||
if (!message->Get("method", &vmethod)) {
|
||||
Value vid;
|
||||
|
||||
if (!message->Get("id", &vid))
|
||||
return;
|
||||
|
||||
Log(LogWarning, "JsonRpcConnection",
|
||||
"We received a JSON-RPC response message. This should never happen because we're only ever sending notifications.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
String method = vmethod;
|
||||
|
||||
Log(LogNotice, "JsonRpcConnection")
|
||||
<< "Received '" << method << "' message from '" << m_Identity << "'";
|
||||
|
@ -202,11 +214,10 @@ void JsonRpcConnection::MessageHandler(const String& jsonString)
|
|||
resultMessage->Set("result", afunc->Invoke(origin, message->Get("params")));
|
||||
} catch (const std::exception& ex) {
|
||||
/* TODO: Add a user readable error message for the remote caller */
|
||||
resultMessage->Set("error", DiagnosticInformation(ex));
|
||||
std::ostringstream info;
|
||||
info << "Error while processing message for identity '" << m_Identity << "'";
|
||||
String diagInfo = DiagnosticInformation(ex);
|
||||
resultMessage->Set("error", diagInfo);
|
||||
Log(LogWarning, "JsonRpcConnection")
|
||||
<< info.str() << "\n" << DiagnosticInformation(ex);
|
||||
<< "Error while processing message for identity '" << m_Identity << "'\n" << diagInfo;
|
||||
}
|
||||
|
||||
if (message->Contains("id")) {
|
||||
|
@ -276,46 +287,6 @@ Value SetLogPositionHandler(const MessageOrigin::Ptr& origin, const Dictionary::
|
|||
return Empty;
|
||||
}
|
||||
|
||||
Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
||||
{
|
||||
if (!params)
|
||||
return Empty;
|
||||
|
||||
Dictionary::Ptr result = new Dictionary();
|
||||
|
||||
if (!origin->FromClient->IsAuthenticated()) {
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
String salt = listener->GetTicketSalt();
|
||||
|
||||
if (salt.IsEmpty()) {
|
||||
result->Set("error", "Ticket salt is not configured.");
|
||||
return result;
|
||||
}
|
||||
|
||||
String ticket = params->Get("ticket");
|
||||
String realTicket = PBKDF2_SHA1(origin->FromClient->GetIdentity(), salt, 50000);
|
||||
|
||||
if (ticket != realTicket) {
|
||||
result->Set("error", "Invalid ticket.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<X509> cert = origin->FromClient->GetStream()->GetPeerCertificate();
|
||||
|
||||
EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
|
||||
X509_NAME *subject = X509_get_subject_name(cert.get());
|
||||
|
||||
boost::shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
|
||||
result->Set("cert", CertificateToString(newcert));
|
||||
|
||||
String cacertfile = GetIcingaCADir() + "/ca.crt";
|
||||
boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
|
||||
result->Set("ca", CertificateToString(cacert));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void JsonRpcConnection::CheckLiveness(void)
|
||||
{
|
||||
if (m_Seen < Utility::GetTime() - 60 && (!m_Endpoint || !m_Endpoint->GetSyncing())) {
|
||||
|
|
|
@ -75,6 +75,8 @@ public:
|
|||
static int GetWorkQueueLength(void);
|
||||
static double GetWorkQueueRate(void);
|
||||
|
||||
static void SendCertificateRequest(const JsonRpcConnection::Ptr& aclient, const intrusive_ptr<MessageOrigin>& origin, const String& path);
|
||||
|
||||
private:
|
||||
int m_ID;
|
||||
String m_Identity;
|
||||
|
@ -98,6 +100,8 @@ private:
|
|||
static void StaticInitialize(void);
|
||||
static void TimeoutTimerHandler(void);
|
||||
void CheckLiveness(void);
|
||||
|
||||
void CertificateRequestResponseHandler(const Dictionary::Ptr& message);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
******************************************************************************/
|
||||
|
||||
#include "cli/pkiutility.hpp"
|
||||
#include "cli/clicommand.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
|
@ -34,19 +34,9 @@
|
|||
|
||||
using namespace icinga;
|
||||
|
||||
String PkiUtility::GetPkiPath(void)
|
||||
{
|
||||
return Application::GetSysconfDir() + "/icinga2/pki";
|
||||
}
|
||||
|
||||
String PkiUtility::GetLocalCaPath(void)
|
||||
{
|
||||
return Application::GetLocalStateDir() + "/lib/icinga2/ca";
|
||||
}
|
||||
|
||||
int PkiUtility::NewCa(void)
|
||||
{
|
||||
String caDir = GetLocalCaPath();
|
||||
String caDir = ApiListener::GetCaDir();
|
||||
String caCertFile = caDir + "/ca.crt";
|
||||
String caKeyFile = caDir + "/ca.key";
|
||||
|
||||
|
@ -91,7 +81,8 @@ int PkiUtility::SignCsr(const String& csrfile, const String& certfile)
|
|||
|
||||
BIO_free(csrbio);
|
||||
|
||||
boost::shared_ptr<X509> cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req));
|
||||
boost::shared_ptr<EVP_PKEY> pubkey = boost::shared_ptr<EVP_PKEY>(X509_REQ_get_pubkey(req), EVP_PKEY_free);
|
||||
boost::shared_ptr<X509> cert = CreateCertIcingaCA(pubkey.get(), X509_REQ_get_subject_name(req));
|
||||
|
||||
X509_REQ_free(req);
|
||||
|
||||
|
@ -258,11 +249,69 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
|
|||
|
||||
Dictionary::Ptr result = response->Get("result");
|
||||
|
||||
if (result->Contains("ca")) {
|
||||
try {
|
||||
StringToCertificate(result->Get("ca"));
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Could not write CA file: " << DiagnosticInformation(ex, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Writing CA certificate to file '" << cafile << "'.";
|
||||
|
||||
std::ofstream fpca;
|
||||
fpca.open(cafile.CStr());
|
||||
fpca << result->Get("ca");
|
||||
fpca.close();
|
||||
|
||||
if (fpca.fail()) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Could not open CA certificate file '" << cafile << "' for writing.";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result->Contains("error")) {
|
||||
Log(LogCritical, "cli", result->Get("error"));
|
||||
LogSeverity severity;
|
||||
|
||||
Value vstatus;
|
||||
|
||||
if (!result->Get("status_code", &vstatus))
|
||||
vstatus = 1;
|
||||
|
||||
int status = vstatus;
|
||||
|
||||
if (status == 1)
|
||||
severity = LogCritical;
|
||||
else {
|
||||
severity = LogInformation;
|
||||
Log(severity, "cli", "!!!!!!");
|
||||
}
|
||||
|
||||
Log(severity, "cli")
|
||||
<< "!!! " << result->Get("error");
|
||||
|
||||
if (status == 1)
|
||||
return 1;
|
||||
else {
|
||||
Log(severity, "cli", "!!!!!!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
StringToCertificate(result->Get("cert"));
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Could not write certificate file: " << DiagnosticInformation(ex, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Writing signed certificate to file '" << certfile << "'.";
|
||||
|
||||
std::ofstream fpcert;
|
||||
fpcert.open(certfile.CStr());
|
||||
fpcert << result->Get("cert");
|
||||
|
@ -274,23 +323,6 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
|
|||
return 1;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Writing signed certificate to file '" << certfile << "'.";
|
||||
|
||||
std::ofstream fpca;
|
||||
fpca.open(cafile.CStr());
|
||||
fpca << result->Get("ca");
|
||||
fpca.close();
|
||||
|
||||
if (fpca.fail()) {
|
||||
Log(LogCritical, "cli")
|
||||
<< "Could not open CA certificate file '" << cafile << "' for writing.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
Log(LogInformation, "cli")
|
||||
<< "Writing CA certificate to file '" << cafile << "'.";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -325,6 +357,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]) << ' ';
|
||||
|
@ -333,3 +368,70 @@ String PkiUtility::GetCertificateInformation(const boost::shared_ptr<X509>& cert
|
|||
|
||||
return info.str();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
/* XXX (requires OpenSSL >= 1.0.0)
|
||||
time_t now;
|
||||
time(&now);
|
||||
ASN1_TIME *tm = ASN1_TIME_adj(NULL, now, 0, 0);
|
||||
|
||||
int day, sec;
|
||||
ASN1_TIME_diff(&day, &sec, tm, X509_get_notBefore(certRequest.get()));
|
||||
|
||||
result->Set("timestamp", static_cast<double>(now) + day * 24 * 60 * 60 + sec); */
|
||||
|
||||
BIO *out = BIO_new(BIO_s_mem());
|
||||
ASN1_TIME_print(out, X509_get_notBefore(certRequest.get()));
|
||||
|
||||
char *data;
|
||||
long length;
|
||||
length = BIO_get_mem_data(out, &data);
|
||||
|
||||
result->Set("timestamp", String(data, data + length));
|
||||
BIO_free(out);
|
||||
|
||||
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);
|
||||
|
||||
length = BIO_get_mem_data(out, &data);
|
||||
|
||||
result->Set("subject", String(data, data + length));
|
||||
BIO_free(out);
|
||||
|
||||
requests->Set(fingerprint, result);
|
||||
}
|
||||
|
||||
Dictionary::Ptr PkiUtility::GetCertificateRequests(void)
|
||||
{
|
||||
Dictionary::Ptr requests = new Dictionary();
|
||||
|
||||
String requestDir = ApiListener::GetCertificateRequestsDir();
|
||||
|
||||
if (Utility::PathExists(requestDir))
|
||||
Utility::Glob(requestDir + "/*.json", boost::bind(&CollectRequestHandler, requests, _1), GlobFile);
|
||||
|
||||
return requests;
|
||||
}
|
|
@ -20,8 +20,7 @@
|
|||
#ifndef PKIUTILITY_H
|
||||
#define PKIUTILITY_H
|
||||
|
||||
#include "base/i2-base.hpp"
|
||||
#include "cli/i2-cli.hpp"
|
||||
#include "remote/i2-remote.hpp"
|
||||
#include "base/dictionary.hpp"
|
||||
#include "base/string.hpp"
|
||||
#include <openssl/x509v3.h>
|
||||
|
@ -30,14 +29,11 @@ namespace icinga
|
|||
{
|
||||
|
||||
/**
|
||||
* @ingroup cli
|
||||
* @ingroup remote
|
||||
*/
|
||||
class I2_CLI_API PkiUtility
|
||||
class I2_REMOTE_API PkiUtility
|
||||
{
|
||||
public:
|
||||
static String GetPkiPath(void);
|
||||
static String GetLocalCaPath(void);
|
||||
|
||||
static int NewCa(void);
|
||||
static int NewCert(const String& cn, const String& keyfile, const String& csrfile, const String& certfile);
|
||||
static int SignCsr(const String& csrfile, const String& certfile);
|
||||
|
@ -46,8 +42,9 @@ public:
|
|||
static int GenTicket(const String& cn, const String& salt, std::ostream& ticketfp);
|
||||
static int RequestCertificate(const String& host, const String& port, const String& keyfile,
|
||||
const String& certfile, const String& cafile, const boost::shared_ptr<X509>& trustedcert,
|
||||
const String& ticket);
|
||||
const String& ticket = String());
|
||||
static String GetCertificateInformation(const boost::shared_ptr<X509>& certificate);
|
||||
static Dictionary::Ptr GetCertificateRequests(void);
|
||||
|
||||
private:
|
||||
PkiUtility(void);
|
Loading…
Reference in New Issue