Implement support for migrating certificates to /var/lib/icinga2/certs

This commit includes documentation too.

Signed-off-by: Michael Friedrich <michael.friedrich@icinga.com>
This commit is contained in:
Gunnar Beutner 2017-10-16 15:32:57 +02:00 committed by Michael Friedrich
parent c514ff061a
commit f2d437e96c
18 changed files with 216 additions and 48 deletions

View File

@ -175,11 +175,11 @@ By default Icinga 2 uses the following files and directories:
/usr/lib\*/icinga2 | Libraries and the Icinga 2 binary (use `find /usr -type f -name icinga2` to locate the binary path). /usr/lib\*/icinga2 | Libraries and the Icinga 2 binary (use `find /usr -type f -name icinga2` to locate the binary path).
/usr/share/doc/icinga2 | Documentation files that come with Icinga 2. /usr/share/doc/icinga2 | Documentation files that come with Icinga 2.
/usr/share/icinga2/include | The Icinga Template Library and plugin command configuration. /usr/share/icinga2/include | The Icinga Template Library and plugin command configuration.
/var/lib/icinga2 | Icinga 2 state file, cluster log, master CA, node certificates and configuration files (cluster, api).
/var/run/icinga2 | PID file. /var/run/icinga2 | PID file.
/var/run/icinga2/cmd | Command pipe and Livestatus socket. /var/run/icinga2/cmd | Command pipe and Livestatus socket.
/var/cache/icinga2 | status.dat/objects.cache, icinga2.debug files /var/cache/icinga2 | status.dat/objects.cache, icinga2.debug files
/var/spool/icinga2 | Used for performance data spool files. /var/spool/icinga2 | Used for performance data spool files.
/var/lib/icinga2 | Icinga 2 state file, cluster log, local CA and configuration files (cluster, api).
/var/log/icinga2 | Log file location and compat/ directory for the CompatLogger feature. /var/log/icinga2 | Log file location and compat/ directory for the CompatLogger feature.
FreeBSD uses slightly different paths: FreeBSD uses slightly different paths:
@ -194,11 +194,11 @@ By default Icinga 2 uses the following files and directories:
/usr/local/lib/icinga2 | Libraries and the Icinga 2 binary. /usr/local/lib/icinga2 | Libraries and the Icinga 2 binary.
/usr/local/share/doc/icinga2 | Documentation files that come with Icinga 2. /usr/local/share/doc/icinga2 | Documentation files that come with Icinga 2.
/usr/local/share/icinga2/include | The Icinga Template Library and plugin command configuration. /usr/local/share/icinga2/include | The Icinga Template Library and plugin command configuration.
/var/lib/icinga2 | Icinga 2 state file, cluster log, master CA, node certificates and configuration files (cluster, api).
/var/run/icinga2 | PID file. /var/run/icinga2 | PID file.
/var/run/icinga2/cmd | Command pipe and Livestatus socket. /var/run/icinga2/cmd | Command pipe and Livestatus socket.
/var/cache/icinga2 | status.dat/objects.cache, icinga2.debug files /var/cache/icinga2 | status.dat/objects.cache, icinga2.debug files
/var/spool/icinga2 | Used for performance data spool files. /var/spool/icinga2 | Used for performance data spool files.
/var/lib/icinga2 | Icinga 2 state file, cluster log, local CA and configuration files (cluster, api).
/var/log/icinga2 | Log file location and compat/ directory for the CompatLogger feature. /var/log/icinga2 | Log file location and compat/ directory for the CompatLogger feature.
## Setting up Check Plugins <a id="setting-up-check-plugins"></a> ## Setting up Check Plugins <a id="setting-up-check-plugins"></a>
@ -877,5 +877,6 @@ popular addons is available in the
Ensure to include the following in your backups: Ensure to include the following in your backups:
* Configuration files in `/etc/icinga2` * Configuration files in `/etc/icinga2`
* Runtime files in `/var/lib/icinga2` (the master's CA is stored here as well) * Certificate files in `/var/lib/icinga2/ca` (Master CA key pair) and `/var/lib/icinga2/certs` (node certificates)
* Runtime files in `/var/lib/icinga2`
* Optional: IDO database backup * Optional: IDO database backup

View File

@ -553,6 +553,11 @@ The setup wizard will ensure that the following steps are taken:
You can verify that the certificate files are stored in the `/var/lib/icinga2/certs` directory. You can verify that the certificate files are stored in the `/var/lib/icinga2/certs` directory.
> **Note**
>
> The certificate location changed in v2.8 to `/var/lib/icinga2/certs`. Please read the [upgrading chapter](16-upgrading-icinga-2.md#upgrading-to-2-8-certificate-paths)
> for more details.
> **Note** > **Note**
> >
> If the client is not directly connected to the certificate signing master, > If the client is not directly connected to the certificate signing master,
@ -2358,6 +2363,11 @@ 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 [root@icinga2-master1.localdomain /root]# icinga2 pki sign-csr --csr icinga2-master1.localdomain.csr --cert icinga2-master1.localdomain
> **Note**
>
> The certificate location changed in v2.8 to `/var/lib/icinga2/certs`. Please read the [upgrading chapter](16-upgrading-icinga-2.md#upgrading-to-2-8-certificate-paths)
> for more details.
Copy the host's certificate files and the public CA certificate to `/var/lib/icinga2/certs`: Copy the host's certificate files and the public CA certificate to `/var/lib/icinga2/certs`:
[root@icinga2-master1.localdomain /root]# mkdir -p /var/lib/icinga2/certs [root@icinga2-master1.localdomain /root]# mkdir -p /var/lib/icinga2/certs
@ -2444,7 +2454,12 @@ host/port you can specify it like this:
#### Node Setup with Satellites/Clients <a id="distributed-monitoring-automation-cli-node-setup-satellite-client"></a> #### Node Setup with Satellites/Clients <a id="distributed-monitoring-automation-cli-node-setup-satellite-client"></a>
Make sure that the `/var/lib/icinga2/certs` exists and is owned by the `icinga` > **Note**
>
> The certificate location changed in v2.8 to `/var/lib/icinga2/certs`. Please read the [upgrading chapter](16-upgrading-icinga-2.md#upgrading-to-2-8-certificate-paths)
> for more details.
Make sure that the `/var/lib/icinga2/certs` directory exists and is owned by the `icinga`
user (or the user Icinga 2 is running as). user (or the user Icinga 2 is running as).
[root@icinga2-client1.localdomain /]# mkdir -p /var/lib/icinga2/certs [root@icinga2-client1.localdomain /]# mkdir -p /var/lib/icinga2/certs
@ -2499,7 +2514,7 @@ Pass the following details to the `node setup` CLI command:
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)). 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)).
Accept commands | **Optional.** Whether this node accepts command execution messages from the master node (required for [command endpoint mode](06-distributed-monitoring.md#distributed-monitoring-top-down-command-endpoint)). Accept commands | **Optional.** Whether this node accepts command execution messages from the master node (required for [command endpoint mode](06-distributed-monitoring.md#distributed-monitoring-top-down-command-endpoint)).
Example: Example for Icinga 2 v2.8:
[root@icinga2-client1.localdomain /]# icinga2 node setup --ticket ead2d570e18c78abf285d6b85524970a0f69c22d \ [root@icinga2-client1.localdomain /]# icinga2 node setup --ticket ead2d570e18c78abf285d6b85524970a0f69c22d \
--cn icinga2-client1.localdomain \ --cn icinga2-client1.localdomain \

View File

@ -41,9 +41,8 @@ Example:
``` ```
object ApiListener "api" { object ApiListener "api" {
cert_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".crt" accept_commands = true
key_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".key" accept_config = true
ca_path = LocalStateDir + "/lib/icinga2/certs/ca.crt"
ticket_salt = TicketSalt ticket_salt = TicketSalt
} }
@ -53,9 +52,9 @@ Configuration Attributes:
Name | Type | Description Name | Type | Description
--------------------------------------|-----------------------|---------------------------------- --------------------------------------|-----------------------|----------------------------------
cert\_path | String | **Required.** Path to the public key. cert\_path | String | **Deprecated.** Path to the public key.
key\_path | String | **Required.** Path to the private key. key\_path | String | **Deprecated.** Path to the private key.
ca\_path | String | **Required.** Path to the CA certificate file. ca\_path | String | **Deprecated.** Path to the CA certificate file.
ticket\_salt | String | **Optional.** Private key for [CSR auto-signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing). **Required** for a signing master instance. ticket\_salt | String | **Optional.** Private key for [CSR auto-signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing). **Required** for a signing master instance.
crl\_path | String | **Optional.** Path to the CRL file. crl\_path | String | **Optional.** Path to the CRL file.
bind\_host | String | **Optional.** The IP address the api listener should be bound to. Defaults to `0.0.0.0`. bind\_host | String | **Optional.** The IP address the api listener should be bound to. Defaults to `0.0.0.0`.
@ -69,6 +68,20 @@ Configuration Attributes:
access\_control\_allow\_headers | String | **Optional.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers) access\_control\_allow\_headers | String | **Optional.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
access\_control\_allow\_methods | String | **Optional.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods) access\_control\_allow\_methods | String | **Optional.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
The ApiListener type expects its certificate files to be in the following locations:
Type | Location
---------------------|-------------------------------------
Private key | `LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".key"`
Certificate file | `LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".crt"`
CA certificate file | `LocalStateDir + "/lib/icinga2/certs/ca.crt"`
If the deprecated attributes `cert_path`, `key_path` and/or `ca_path` are specified Icinga 2
copies those files to the new location in `LocalStateDir + "/lib/icinga2/certs"` unless the
file(s) there are newer.
Please check the [upgrading chapter](16-upgrading-icinga-2.md#upgrading-to-2-8-certificate-paths) for more details.
## ApiUser <a id="objecttype-apiuser"></a> ## ApiUser <a id="objecttype-apiuser"></a>
ApiUser objects are used for authentication against the [Icinga 2 API](12-icinga2-api.md#icinga2-api-authentication). ApiUser objects are used for authentication against the [Icinga 2 API](12-icinga2-api.md#icinga2-api-authentication).

View File

@ -10,19 +10,103 @@ are scheme updates for the IDO database.
The default certificate path was changed from `/etc/icinga2/pki` to The default certificate path was changed from `/etc/icinga2/pki` to
`/var/lib/icinga2/certs`. `/var/lib/icinga2/certs`.
Old Path | New Path
---------------------------------------------------|---------------------------------------------------
`/etc/icinga2/pki/icinga2-client1.localdomain.crt` | `/var/lib/icinga2/certs/icinga2-client1.localdomain.crt`
`/etc/icinga2/pki/icinga2-client1.localdomain.key` | `/var/lib/icinga2/certs/icinga2-client1.localdomain.key`
`/etc/icinga2/pki/ca.crt` | `/var/lib/icinga2/certs/ca.crt`
This applies to Windows clients in the same way: `%ProgramData%\etc\icinga2\pki` This applies to Windows clients in the same way: `%ProgramData%\etc\icinga2\pki`
was moved to `%ProgramData%`\var\lib\icinga2\certs`. was moved to `%ProgramData%\var\lib\icinga2\certs`.
Old Path | New Path
----------------------------------------------------------------|----------------------------------------------------------------
`%ProgramData%\etc\icinga2\pki\icinga2-client1.localdomain.crt` | `%ProgramData%\var\lib\icinga2\certs\icinga2-client1.localdomain.crt`
`%ProgramData%\etc\icinga2\pki\icinga2-client1.localdomain.key` | `%ProgramData%\var\lib\icinga2\certs\icinga2-client1.localdomain.key`
`%ProgramData%\etc\icinga2\pki\ca.crt` | `%ProgramData%\var\lib\icinga2\certs\ca.crt`
> **Note**
>
> The default expected path for client certificates is `/var/lib/icinga2/certs/ + NodeName + {.crt,.key}`.
> The `NodeName` constant is usually the FQDN and certificate common name (CN). Check the [conventions](06-distributed-monitoring.md#distributed-monitoring-conventions)
> section inside the Distributed Monitoring chapter.
The [setup CLI commands](06-distributed-monitoring.md#distributed-monitoring-setup-master) and the The [setup CLI commands](06-distributed-monitoring.md#distributed-monitoring-setup-master) and the
default [ApiListener configuration](06-distributed-monitoring.md#distributed-monitoring-apilistener) default [ApiListener configuration](06-distributed-monitoring.md#distributed-monitoring-apilistener)
have been adjusted to these paths too. have been adjusted to these paths too.
The [ApiListener](09-object-types.md#objecttype-apilistener) object attributes `cert_path`, `key_path`
and `ca_path` have been deprecated and removed from the example configuration.
#### Migration Path <a id="upgrading-to-2-8-certificate-paths-migration-path"></a>
> **Note**
>
> Icinga 2 automatically migrates the certificates to the new default location if they
> are configured and detected in `/etc/icinga2/pki`.
During startup, the migration kicks in and ensures to copy the certificates to the new
location. This will also happen if someone updates the certificate files in `/etc/icinga2/pki`
to ensure that the new certificate location always has the latest files.
This has been implemented in the Icinga 2 binary to ensure it works on both Linux/Unix
and the Windows platform.
If you are not using the built-in CLI commands and setup wizards to deploy the client certificates,
please ensure to update your deployment tools/scripts. This mainly affects
* Puppet modules
* Ansible playbooks
* Chef cookbooks
* Salt recipes
* Custom scripts, e.g. Windows Powershell or self-made implementations
In order to support a smooth migration between versions older than 2.8 and future releases,
the built-in certificate migration path is planned to exist as long as the deprecated
`ApiListener` object attributes exist.
You are safe to use the existing configuration paths inside the `api` feature. If you plan your migration,
look at the following example taken from the Director Linux deployment script for clients.
* Ensure that the default certificate path is changed from `/etc/icinga2/pki` to `/var/lib/icinga2/certs`.
```
-ICINGA2_SSL_DIR="${ICINGA2_CONF_DIR}/pki"
+ICINGA2_SSL_DIR="${ICINGA2_STATE_DIR}/lib/icinga2/certs"
```
* Remove the ApiListener configuration attributes.
```
object ApiListener "api" {
- cert_path = SysconfDir + "/icinga2/pki/${ICINGA2_NODENAME}.crt"
- key_path = SysconfDir + "/icinga2/pki/${ICINGA2_NODENAME}.key"
- ca_path = SysconfDir + "/icinga2/pki/ca.crt"
accept_commands = true
accept_config = true
}
```
Test the script with a fresh client installation before putting it into production.
> **Tip**
>
> Please support module and script developers in their migration. If you find
> any project which would require these changes, create an issue or a patchset in a PR
> and help them out. Thanks in advance!
### Removed Bottom Up Client Mode <a id="upgrading-to-2-8-removed-bottom-up-client-mode"></a> ### Removed Bottom Up Client Mode <a id="upgrading-to-2-8-removed-bottom-up-client-mode"></a>
This client mode was deprecated in 2.6 and was removed in 2.8. This client mode was deprecated in 2.6 and was removed in 2.8.
The node CLI command does not provide `list` or `update-config` anymore. The node CLI command does not provide `list` or `update-config` anymore.
> **Note**
>
> The old migration guide can be found on [GitHub](https://github.com/Icinga/icinga2/blob/v2.7.0/doc/06-distributed-monitoring.md#bottom-up-migration-to-top-down-).
The clients don't need to have a local `conf.d` directory included. The clients don't need to have a local `conf.d` directory included.
The setup wizards for Linux and Windows attempt to disable this by default. The setup wizards for Linux and Windows attempt to disable this by default.
@ -31,6 +115,7 @@ You are advised to [migrate](https://github.com/Icinga/icinga2/issues/4798)
any existing configuration to the "top down" mode with the help of the any existing configuration to the "top down" mode with the help of the
Icinga Director or config management tools such as Puppet, Ansible, etc. Icinga Director or config management tools such as Puppet, Ansible, etc.
### Removed Classic UI Config Package <a id="upgrading-to-2-8-removed-classicui-config-package"></a> ### Removed Classic UI Config Package <a id="upgrading-to-2-8-removed-classicui-config-package"></a>
The config meta package `classicui-config` and the configuration files The config meta package `classicui-config` and the configuration files

View File

@ -3,9 +3,8 @@
*/ */
object ApiListener "api" { object ApiListener "api" {
cert_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".crt" //accept_config = false
key_path = LocalStateDir + "/lib/icinga2/certs/" + NodeName + ".key" //accept_commands = false
ca_path = LocalStateDir + "/lib/icinga2/certs/ca.crt"
ticket_salt = TicketSalt ticket_salt = TicketSalt
} }

View File

@ -28,7 +28,7 @@ abstract class Function
{ {
[config] String "name"; [config] String "name";
[config] bool side_effect_free; [config] bool side_effect_free;
[config] bool deprecated; [config] bool "deprecated";
[config] Array::Ptr arguments; [config] Array::Ptr arguments;
}; };

View File

@ -39,7 +39,8 @@ enum FieldAttribute
FARequired = 256, FARequired = 256,
FANavigation = 512, FANavigation = 512,
FANoUserModify = 1024, FANoUserModify = 1024,
FANoUserView = 2048 FANoUserView = 2048,
FADeprecated = 4096,
}; };
class Type; class Type;

View File

@ -816,6 +816,10 @@ void Utility::CollectPaths(const String& path, std::vector<String>& paths)
paths.push_back(path); paths.push_back(path);
} }
/*
* Copies a source file to a target location.
* Caller must ensure that the target's base directory exists and is writable.
*/
void Utility::CopyFile(const String& source, const String& target) void Utility::CopyFile(const String& source, const String& target)
{ {
std::ifstream ifs(source.CStr(), std::ios::binary); std::ifstream ifs(source.CStr(), std::ios::binary);

View File

@ -173,10 +173,7 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
<< " */\n" << " */\n"
<< "object ApiListener \"api\" {\n" << "object ApiListener \"api\" {\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")) { if (vm.count("listen")) {
std::vector<String> tokens; std::vector<String> tokens;
@ -378,10 +375,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
<< " */\n" << " */\n"
<< "object ApiListener \"api\" {\n" << "object ApiListener \"api\" {\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")) { if (vm.count("listen")) {
std::vector<String> tokens; std::vector<String> tokens;

View File

@ -472,10 +472,6 @@ wizard_ticket:
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
<< " */\n" << " */\n"
<< "object ApiListener \"api\" {\n" << "object ApiListener \"api\" {\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"
<< "\n"
<< " accept_config = " << acceptConfig << "\n" << " accept_config = " << acceptConfig << "\n"
<< " accept_commands = " << acceptCommands << "\n"; << " accept_commands = " << acceptCommands << "\n";
@ -629,10 +625,7 @@ int NodeWizardCommand::MasterSetup(void) const
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
<< " */\n" << " */\n"
<< "object ApiListener \"api\" {\n" << "object ApiListener \"api\" {\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 (!bindHost.IsEmpty()) if (!bindHost.IsEmpty())
fp << " bind_host = \"" << bindHost << "\"\n"; fp << " bind_host = \"" << bindHost << "\"\n";

View File

@ -75,6 +75,34 @@ String ApiListener::GetCertificateRequestsDir(void)
return Application::GetLocalStateDir() + "/lib/icinga2/certificate-requests/"; return Application::GetLocalStateDir() + "/lib/icinga2/certificate-requests/";
} }
String ApiListener::GetDefaultCertPath(void)
{
return GetCertsDir() + "/" + ScriptGlobal::Get("NodeName") + ".crt";
}
String ApiListener::GetDefaultKeyPath(void)
{
return GetCertsDir() + "/" + ScriptGlobal::Get("NodeName") + ".key";
}
String ApiListener::GetDefaultCaPath(void)
{
return GetCertsDir() + "/ca.crt";
}
void ApiListener::CopyCertificateFile(const String& oldCertPath, const String& newCertPath)
{
struct stat st1, st2;
if (!oldCertPath.IsEmpty() && stat(oldCertPath.CStr(), &st1) >= 0 && (stat(newCertPath.CStr(), &st2) < 0 || st1.st_mtime > st2.st_mtime)) {
Log(LogWarning, "ApiListener")
<< "Copying '" << oldCertPath << "' certificate file to '" << newCertPath << "'";
Utility::MkDirP(Utility::DirName(newCertPath), 0700);
Utility::CopyFile(oldCertPath, newCertPath);
}
}
void ApiListener::OnConfigLoaded(void) void ApiListener::OnConfigLoaded(void)
{ {
if (m_Instance) if (m_Instance)
@ -82,20 +110,37 @@ void ApiListener::OnConfigLoaded(void)
m_Instance = this; m_Instance = this;
String defaultCertPath = GetDefaultCertPath();
String defaultKeyPath = GetDefaultKeyPath();
String defaultCaPath = GetDefaultCaPath();
/* Migrate certificate location < 2.8 to the new default path. */
String oldCertPath = GetCertPath();
String oldKeyPath = GetKeyPath();
String oldCaPath = GetCaPath();
CopyCertificateFile(oldCertPath, defaultCertPath);
CopyCertificateFile(oldKeyPath, defaultKeyPath);
CopyCertificateFile(oldCaPath, defaultCaPath);
if (!oldCertPath.IsEmpty() && !oldKeyPath.IsEmpty() && !oldCaPath.IsEmpty()) {
Log(LogWarning, "ApiListener", "Please read the upgrading documentation for v2.8: https://www.icinga.com/docs/icinga2/latest/doc/16-upgrading-icinga-2/");
}
/* set up SSL context */ /* set up SSL context */
boost::shared_ptr<X509> cert; boost::shared_ptr<X509> cert;
try { try {
cert = GetX509Certificate(GetCertPath()); cert = GetX509Certificate(defaultCertPath);
} catch (const std::exception&) { } catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot get certificate from cert path: '" BOOST_THROW_EXCEPTION(ScriptError("Cannot get certificate from cert path: '"
+ GetCertPath() + "'.", GetDebugInfo())); + defaultCertPath + "'.", GetDebugInfo()));
} }
try { try {
SetIdentity(GetCertificateCN(cert)); SetIdentity(GetCertificateCN(cert));
} catch (const std::exception&) { } catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot get certificate common name from cert path: '" BOOST_THROW_EXCEPTION(ScriptError("Cannot get certificate common name from cert path: '"
+ GetCertPath() + "'.", GetDebugInfo())); + defaultCertPath + "'.", GetDebugInfo()));
} }
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
@ -109,10 +154,10 @@ void ApiListener::UpdateSSLContext(void)
boost::shared_ptr<SSL_CTX> context; boost::shared_ptr<SSL_CTX> context;
try { try {
context = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath()); context = MakeSSLContext(GetDefaultCertPath(), GetDefaultKeyPath(), GetDefaultCaPath());
} catch (const std::exception&) { } catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '" BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '"
+ GetCertPath() + "' key path: '" + GetKeyPath() + "' ca path: '" + GetCaPath() + "'.", GetDebugInfo())); + GetDefaultCertPath() + "' key path: '" + GetDefaultKeyPath() + "' ca path: '" + GetDefaultCaPath() + "'.", GetDebugInfo()));
} }
if (!GetCrlPath().IsEmpty()) { if (!GetCrlPath().IsEmpty()) {
@ -420,7 +465,7 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
identity = GetCertificateCN(cert); identity = GetCertificateCN(cert);
} catch (const std::exception&) { } catch (const std::exception&) {
Log(LogCritical, "ApiListener") Log(LogCritical, "ApiListener")
<< "Cannot get certificate common name from cert path: '" << GetCertPath() << "'."; << "Cannot get certificate common name from cert path: '" << GetDefaultCertPath() << "'.";
return; return;
} }

View File

@ -104,6 +104,10 @@ public:
static bool IsHACluster(void); static bool IsHACluster(void);
static String GetFromZoneName(const Zone::Ptr& fromZone); static String GetFromZoneName(const Zone::Ptr& fromZone);
static String GetDefaultCertPath(void);
static String GetDefaultKeyPath(void);
static String GetDefaultCaPath(void);
protected: protected:
virtual void OnConfigLoaded(void) override; virtual void OnConfigLoaded(void) override;
virtual void OnAllConfigLoaded(void) override; virtual void OnAllConfigLoaded(void) override;
@ -157,6 +161,8 @@ private:
static void LogGlobHandler(std::vector<int>& files, const String& file); static void LogGlobHandler(std::vector<int>& files, const String& file);
void ReplayLog(const JsonRpcConnection::Ptr& client); void ReplayLog(const JsonRpcConnection::Ptr& client);
static void CopyCertificateFile(const String& oldCertPath, const String& newCertPath);
/* filesync */ /* filesync */
static ConfigDirInformation LoadConfigDir(const String& dir); static ConfigDirInformation LoadConfigDir(const String& dir);
static Dictionary::Ptr MergeConfigUpdate(const ConfigDirInformation& config); static Dictionary::Ptr MergeConfigUpdate(const ConfigDirInformation& config);

View File

@ -28,9 +28,9 @@ namespace icinga
class ApiListener : ConfigObject class ApiListener : ConfigObject
{ {
[config, required] String cert_path; [config, deprecated] String cert_path;
[config, required] String key_path; [config, deprecated] String key_path;
[config, required] String ca_path; [config, deprecated] String ca_path;
[config] String crl_path; [config] String crl_path;
[config] String cipher_list { [config] String cipher_list {
default {{{ return "ALL:!LOW:!WEAK:!MEDIUM:!EXP:!NULL"; }}} default {{{ return "ALL:!LOW:!WEAK:!MEDIUM:!EXP:!NULL"; }}}

View File

@ -56,7 +56,7 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
cert = StringToCertificate(certText); cert = StringToCertificate(certText);
ApiListener::Ptr listener = ApiListener::GetInstance(); ApiListener::Ptr listener = ApiListener::GetInstance();
boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetCaPath()); boost::shared_ptr<X509> cacert = GetX509Certificate(listener->GetDefaultCaPath());
String cn = GetCertificateCN(cert); String cn = GetCertificateCN(cert);
@ -183,7 +183,7 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
* a certificate it wouldn't be able to use to connect to us anyway) */ * a certificate it wouldn't be able to use to connect to us anyway) */
if (!VerifyCertificate(cacert, newcert)) { if (!VerifyCertificate(cacert, newcert)) {
Log(LogWarning, "JsonRpcConnection") Log(LogWarning, "JsonRpcConnection")
<< "The CA in '" << listener->GetCaPath() << "' does not match the CA which Icinga uses " << "The CA in '" << listener->GetDefaultCaPath() << "' does not match the CA which Icinga uses "
<< "for its own cluster connections. This is most likely a configuration problem."; << "for its own cluster connections. This is most likely a configuration problem.";
goto delayed_request; goto delayed_request;
} }
@ -285,7 +285,7 @@ Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionar
if (!listener) if (!listener)
return Empty; return Empty;
boost::shared_ptr<X509> oldCert = GetX509Certificate(listener->GetCertPath()); boost::shared_ptr<X509> oldCert = GetX509Certificate(listener->GetDefaultCertPath());
boost::shared_ptr<X509> newCert = StringToCertificate(cert); boost::shared_ptr<X509> newCert = StringToCertificate(cert);
String cn = GetCertificateCN(newCert); String cn = GetCertificateCN(newCert);
@ -328,7 +328,7 @@ Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionar
} }
/* Update CA certificate. */ /* Update CA certificate. */
String caPath = listener->GetCaPath(); String caPath = listener->GetDefaultCaPath();
Log(LogInformation, "JsonRpcConnection") Log(LogInformation, "JsonRpcConnection")
<< "Updating CA certificate in '" << caPath << "'."; << "Updating CA certificate in '" << caPath << "'.";
@ -350,7 +350,7 @@ Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionar
} }
/* Update signed certificate. */ /* Update signed certificate. */
String certPath = listener->GetCertPath(); String certPath = listener->GetDefaultCertPath();
Log(LogInformation, "JsonRpcConnection") Log(LogInformation, "JsonRpcConnection")
<< "Updating client certificate for CN '" << cn << "' in '" << certPath << "'."; << "Updating client certificate for CN '" << cn << "' in '" << certPath << "'.";

View File

@ -149,6 +149,7 @@ bool TypeQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& requ
attributeInfo->Set("navigation", static_cast<bool>(field.Attributes & FANavigation)); attributeInfo->Set("navigation", static_cast<bool>(field.Attributes & FANavigation));
attributeInfo->Set("no_user_modify", static_cast<bool>(field.Attributes & FANoUserModify)); attributeInfo->Set("no_user_modify", static_cast<bool>(field.Attributes & FANoUserModify));
attributeInfo->Set("no_user_view", static_cast<bool>(field.Attributes & FANoUserView)); attributeInfo->Set("no_user_view", static_cast<bool>(field.Attributes & FANoUserView));
attributeInfo->Set("deprecated", static_cast<bool>(field.Attributes & FADeprecated));
} }
} }

View File

@ -147,6 +147,7 @@ protected { yylval->num = FAGetProtected | FASetProtected; return T_FIELD_ATTR
no_storage { yylval->num = FANoStorage; return T_FIELD_ATTRIBUTE; } no_storage { yylval->num = FANoStorage; return T_FIELD_ATTRIBUTE; }
no_user_modify { yylval->num = FANoUserModify; return T_FIELD_ATTRIBUTE; } no_user_modify { yylval->num = FANoUserModify; return T_FIELD_ATTRIBUTE; }
no_user_view { yylval->num = FANoUserView; return T_FIELD_ATTRIBUTE; } no_user_view { yylval->num = FANoUserView; return T_FIELD_ATTRIBUTE; }
deprecated { yylval->num = FADeprecated; return T_FIELD_ATTRIBUTE; }
navigation { return T_NAVIGATION; } navigation { return T_NAVIGATION; }
validator { return T_VALIDATOR; } validator { return T_VALIDATOR; }
required { return T_REQUIRED; } required { return T_REQUIRED; }

View File

@ -510,6 +510,15 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
m_Impl << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), boost::assign::list_of(\"" << field.Name << "\"), \"Attribute must not be empty.\"));" << std::endl << std::endl; m_Impl << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), boost::assign::list_of(\"" << field.Name << "\"), \"Attribute must not be empty.\"));" << std::endl << std::endl;
} }
if (field.Attributes & FADeprecated) {
if (field.Type.GetRealType().find("::Ptr") != std::string::npos)
m_Impl << "\t" << "if (" << argName << ")" << std::endl;
else
m_Impl << "\t" << "if (!" << argName << ".IsEmpty())" << std::endl;
m_Impl << "\t\t" << "Log(LogWarning, \"" << klass.Name << "\") << \"Attribute '" << field.Name << "' for object '\" << dynamic_cast<ConfigObject *>(this)->GetName() << \"' of type '\" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << \"' is deprecated and should not be used.\";" << std::endl;
}
if (field.Type.ArrayRank > 0) { if (field.Type.ArrayRank > 0) {
m_Impl << "\t" << "if (avalue) {" << std::endl m_Impl << "\t" << "if (avalue) {" << std::endl
<< "\t\t" << "ObjectLock olock(avalue);" << std::endl << "\t\t" << "ObjectLock olock(avalue);" << std::endl

View File

@ -72,7 +72,8 @@ enum FieldAttribute
FARequired = 256, FARequired = 256,
FANavigation = 512, FANavigation = 512,
FANoUserModify = 1024, FANoUserModify = 1024,
FANoUserView = 2048 FANoUserView = 2048,
FADeprecated = 4096
}; };
struct FieldType struct FieldType